Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • nyx/larvatagger.jl
1 result
Show changes
Commits on Source (7)
......@@ -7,3 +7,4 @@
*.outline
*.spine
*.mat
*.label
# This file is machine-generated - editing it directly is not advised
julia_version = "1.11.1"
julia_version = "1.11.3"
manifest_format = "2.0"
project_hash = "fccf84810b56f9ba8947369bbaf414d318bc47f2"
project_hash = "423cfe5e2b370a6df6f85f87fd1406fd07136176"
[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]
......@@ -985,11 +985,11 @@ version = "1.2.0"
[[deps.NyxWidgets]]
deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
git-tree-sha1 = "e18ab14817871c54419e4cef12f9fc4dc589f6fe"
repo-rev = "main"
repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
version = "0.1.1"
version = "0.2.0"
[[deps.Observables]]
git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
......
# This file is machine-generated - editing it directly is not advised
julia_version = "1.10.6"
julia_version = "1.10.8"
manifest_format = "2.0"
project_hash = "0fb14e688a33d3d8ba0bbce1542d1ada113967c7"
project_hash = "029a32b6e21d0f2c52e6dccd0a009e9681bdff18"
[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]
......@@ -971,11 +971,11 @@ version = "1.2.0"
[[deps.NyxWidgets]]
deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
git-tree-sha1 = "e18ab14817871c54419e4cef12f9fc4dc589f6fe"
repo-rev = "main"
repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
version = "0.1.1"
version = "0.2.0"
[[deps.Observables]]
git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
......
name = "LarvaTagger"
uuid = "8b3b36f1-dfed-446e-8561-ea19fe966a4d"
authors = ["François Laurent", "Institut Pasteur"]
version = "0.19.0"
version = "0.19.1"
[deps]
Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
......@@ -27,7 +27,7 @@ WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
[compat]
Bonito = "< 4.0.0"
NyxWidgets = "0.1.1"
NyxWidgets = ">= 0.2.0"
ObservationPolicies = "0.2.4"
PlanarLarvae = ">= 0.11.2"
TidyObservables = "0.1.1"
......
FROM julia:1.10.6-bullseye AS base
FROM julia:1.10.8-bullseye AS base
ARG PROJECT_DIR=/app
ARG BRANCH=main
......
......@@ -52,7 +52,7 @@ import|merge|--version|-V)
LarvaTagger
Usage:
larvatagger open [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
larvatagger open [<file-path>] [--backends=<path>] [--port=<number>] [--server-url=<url>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
larvatagger import <input-path> [<output-file>] [--id=<id>] [--framerate=<fps>] [--pixelsize=<μm>] [--overrides=<comma-separated-list>] [--default-label=<label>] [--manual-label=<label>] [--decode] [--copy-labels]
larvatagger train <backend-path> <data-path> <model-instance> [--pretrained-model=<instance>] [--labels=<comma-separated-list>] [--sample-size=<N>] [--balancing-strategy=<strategy>] [--class-weights=<csv>] [--manual-label=<label>] [--layers=<N>] [--iterations=<N>] [--seed=<seed>] [--debug]
larvatagger train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
......@@ -71,6 +71,7 @@ Options:
--pixelsize=<μm> Camera pixel size, in micrometers.
--backends=<path> Path to backend repository.
--port=<number> Port number the server listens to.
--server-url=<url> Server address, for remote access.
--viewer Disable editing capabilities.
--browser Automatically open a browser tab at the served location.
--view-factor=<real> Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
......@@ -104,6 +105,8 @@ Commands:
The optional positional argument <file-path> can also be the data root directory.
Backends defined in LarvaTagger project root directory are automatically found. Other
backend locations can be specified with the --backends argument.
By default, if --server-url is specified, the port is appended to the ip address or
domain name. To prevent this behavior, include the desired port number in the url.
import Generate a label file and store metadata.
......
......@@ -9,7 +9,7 @@ export main
usage = """LarvaTagger.jl - launch the server-based GUI.
Usage:
larvatagger-gui.jl [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
larvatagger-gui.jl [<file-path>] [--backends=<path>] [--port=<number>] [--server-url=<url>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
larvatagger-gui.jl -h | --help
Options:
......@@ -17,6 +17,7 @@ Options:
-q --quiet Do not show instructions.
--backends=<path> Path to backend repository.
--port=<number> Port number the server listens to.
--server-url=<url> Server address, for remote access.
--viewer Disable editing capabilities.
--browser Automatically open a browser tab at the served location.
--view-factor=<real> Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
......@@ -25,6 +26,9 @@ Options:
The optional positional argument <file-path> can also be the data root directory.
Backends defined in LarvaTagger project root directory are automatically found. Other
backend locations can be specified with the --backends argument.
By default, if --server-url is specified, the port is appended to the ip address or domain
name. To prevent this behavior, include the desired port number in the url.
"""
function main(args=ARGS; exit_on_error=false)
......@@ -82,12 +86,30 @@ function main(args=ARGS; exit_on_error=false)
#
port = parsed_args["--port"]
port = isnothing(port) ? 9284 : parse(Int, port)
server = Server(app, "0.0.0.0", port)
proxy_url = parsed_args["--server-url"]
if isnothing(proxy_url)
proxy_url = ""
elseif !startswith(proxy_url, "http")
proxy_url = "http://$(proxy_url)"
end
server = Server(app, "0.0.0.0", port; proxy_url=proxy_url)
port = server.port
if !isempty(proxy_url)
protocol, remainder = split(proxy_url, "://")
if !(':' in remainder)
server.proxy_url = proxy_url = if '/' in remainder
server_name, path = split(remainder, '/'; limit=2)
"$(protocol)://$(server_name):$(port)/$(path)"
else
"$(protocol)://$(remainder):$(port)"
end
end
end
if parsed_args["--browser"]
Bonito.HTTPServer.openurl("http://127.0.0.1:$(port)")
end
if verbose
@info "The server is ready at http://127.0.0.1:$(port)"
@info "The server is ready at $(Bonito.HTTPServer.online_url(server, ""))"
end
if verbose
@info "Press Ctrl+D to stop the server (or Ctrl+C if in PowerShell)"
......
......@@ -100,36 +100,6 @@ function createtag!(controller, tag::ObservableTag)
return true
end
function renametag!(controller, tag::ObservableTag, newtag::Union{Nothing, UserTag}=nothing)
tagname = tag.name[]
newtag = @something newtag tagname
if isempty(newtag)
@warn "empty tag name"
return false
elseif newtag != tagname
if exists(tag, controller)
renamefailed!(controller, tag, newtag)
return false
else
@info "Tag \"$(tagname)\" renamed \"$(newtag)\""
transaction = tagname => newtag
push!(transactions(controller), RenameTag(transaction))
tagevents(controller).renamed[] = transaction
end
end
return true
end
function renamefailed!(controller,
current::ObservableTag,
failure::UserTag,
)
tagname = current.name[]
@info "Renaming tag \"$(tagname)\" failed"
tagevents(controller).renamefailed[] = tagname => failure
notify(gettags(controller))
end
function activatetag!(controller, tag::ObservableTag)
tag.active[] && return false
tagname = tag.name[]
......@@ -161,6 +131,8 @@ function exists(tagname::String, tagsource, lut)
return false
end
exists(tagsource::ObservableTag, lut) = exists(tagsource.name[], lut)
function assignmentfailed!(controller, reason)
taggingevents(controller).assignmentfailed[] = reason
end
......@@ -224,6 +196,15 @@ getplayer(hub::ControllerHub) = haskey(hub, :player) ? hub[:player] : getplayer(
gettags(c) = gettags(gethub(c)[:larva])
gettags(c::LarvaController) = c.tag_lut
getmanualtag(c) = getmanualtag(gethub(c))
function getmanualtag(c::ControllerHub)
tag = get(c, :manualtag, nothing)
if tag isa Symbol
tag = string(tag)
end
return tag
end
# events
function hoverlarva!(controller::LarvaController, larva_id::LarvaID)
......@@ -375,15 +356,22 @@ function setevents!(controller, tagvalidator::ValidatorBundle{ObservableTag})
deactivatetag!(controller, tag)
end
end
on(modelfailed(tagvalidator, :name)) do (_, failure)
@error "Tag name update failure handled on the model side" tag.name[] failure
# this fails to trigger evaljs
tagevents(controller).renamefailed[] = (tag.name[] => failure)
end
on(viewfailed(tagvalidator, :name)) do (_, failure)
tagevents(controller).renamefailed[] = (tag.name[] => failure)
end
on(tagvalidator, :name) do newname, curname
#renametag!(controller, tag)
transaction = curname => newname
push!(transactions(controller), RenameTag(transaction))
tagevents(controller).renamed[] = transaction
return Validate
previousname = Ref(tag.name[])
on(tag.name) do newname
prevname = previousname[]
@assert newname != prevname
@info "Tag \"$(prevname)\" renamed \"$(newname)\""
transaction = prevname => newname
push!(transactions(controller), RenameTag(transaction))
previousname[] = newname
end
onany(tag.active, tag.name, tag.color) do _, _, _
notify(gettags(controller))
......
......@@ -35,11 +35,18 @@ function larvaeditor(path=nothing;
backend_directory::AbstractString=projectdir,
manualtag::Union{Nothing, String, Symbol}="edited",
title="LarvaTagger",
root_directory=nothing,
enable_uploads=false,
enable_downloads=false,
prepare_download=nothing,
enable_new_directories=false,
enable_delete=false,
kwargs...)
# to (re-)load a file, the app is reloaded with the filepath as sole information
# from previous session
input = Ref{Union{Nothing, String}}(path)
T = Ref{Union{Nothing, String}}
input = path isa T ? path : T(path)
App(title=title) do session::Session
......@@ -47,6 +54,12 @@ function larvaeditor(path=nothing;
# used for method dispatch (here `Session` is taken to represent Bonito)
controller[:frontend] = Session
controller[:manualtag] = manualtag
if !isnothing(root_directory)
controller[:workingdirectory] = workingdir(controller, root_directory)
end
tryopenfile(controller, input)
editor = EditorView(larvaviewer(controller;
......@@ -54,9 +67,13 @@ function larvaeditor(path=nothing;
multipletags=allow_multiple_tags,
kwargs...),
larvafilter(controller),
tagfilter(controller; manualtag=manualtag),
tagfilter(controller),
metadataeditor(controller),
filemenu(controller),
filemenu(controller; upload_button=enable_uploads,
download_button=enable_downloads,
prepare_download=prepare_download,
create_directory_button=enable_new_directories,
delete_button=enable_delete),
backendmenu(controller, backend_directory),
loadanimation(controller),
twooptiondialog(controller))
......
......@@ -7,7 +7,9 @@ struct WorkingDirectory
content::Observable{Vector{String}}
end
WorkingDirectory(controller, root::String, path::String="") = WorkingDirectory(controller, root, Validator(path), Observable(String[]))
function WorkingDirectory(controller, root::String, path::String="")
WorkingDirectory(controller, root, Validator(path), Observable(String[]))
end
function workingdir(controller, root::String, path::String=""; secure::Bool=true)
if secure
......@@ -107,7 +109,7 @@ function tryopenfile(controller, path; reload::Bool=false)
@info "Cannot load file" file=path
return
elseif haskey(hub, :input)
hub[:input][] = isabspath(path) ? relpath(path) : path
hub[:input][] = path
end
# tag_lut
fallback_color = theme[:LarvaPlot][:fallback_color]
......@@ -185,8 +187,9 @@ function tryopenfile(controller, path; reload::Bool=false)
end
secondarytags = records[:secondarytags]
if !isnothing(secondarytags)
manualtag = getmanualtag(hub)
for tag in secondarytags
original = TagModel(tag, true)
original = TagModel(tag, true; frozen=tag == manualtag)
push!(tag_lut, ObservableTag(original; color=fallback_color, active=true))
end
end
......@@ -274,7 +277,7 @@ function savetofile(controller, file; datafile=nothing, merge=false)
@assert length(dataset) == 1
run = first(values(dataset))
lut = gettags(controller)[]
manualtag = gethub(controller)[:tag].manualtag
manualtag = getmanualtag(controller)
has_explicit_editions = false
if !explicit_editions_needed(controller, manualtag)
manualtag = nothing
......@@ -356,6 +359,7 @@ function savetofile(controller, file; datafile=nothing, merge=false)
end
Datasets.to_json_file(filepath, dataset)
Taggers.check_permissions(filepath)
notify(FileBrowsers.workingdir(gethub(controller)[:browser]))
end
end
......
......@@ -40,7 +40,7 @@ canvas {
.cp-tab .cp-tab-switch:checked ~ .control-panel {
display: block;
}
.cp-tab .cp-tab-switch:checked ~ .control-panel div {
.cp-tab .cp-tab-switch:checked ~ .control-panel > div {
background: var(--theme-main-color);
}
.control-panel {
......@@ -63,6 +63,10 @@ canvas {
background-color: var(--nyx-icon-fill-color);
}
.nyx-filebrowser-entrycontrols {
margin-left: -2.25rem;
}
div.scrollable {
position: relative;
margin-right: 0;
......
......@@ -19,10 +19,22 @@ struct TagModel <: AbstractTag
color::OptionalColor
active::Union{Nothing, Bool}
secondary::Bool
frozen::Bool
end
TagModel(name::Name, secondary::Bool=false) = TagModel(name, nothing, nothing, secondary)
TagModel(name::Name, color::OptionalColor, active::Union{Nothing, Bool}) = TagModel(name, color, active, false)
function TagModel(name::Name, secondary::Bool=false; frozen::Bool=false)
TagModel(name, nothing, nothing, secondary, frozen)
end
function TagModel(name::Name,
color::OptionalColor,
active::Union{Nothing, Bool},
secondary::Bool=false,
)
TagModel(name, color, active, secondary, false)
end
isfrozen(tag::TagModel) = tag.frozen
isfrozen(::Nothing) = false
struct ObservableTag <: AbstractTag
name::AbstractObservable{String}
......@@ -54,6 +66,8 @@ end
ObservableTag() = ObservableTag("", "#000000")
isfrozen(t::ObservableTag) = isfrozen(t.original)
isnative(t::ObservableTag) = !isnothing(t.original)
const TagLUT = Vector{ObservableTag}
......@@ -204,8 +218,9 @@ function TidyObservables.newcontroller(tag::ObservableTag, taglut::AbstractObser
on(ctrl, :name) do name, current
if isempty(name)
Invalidate(; revert=!isempty(current))
elseif isfrozen(tag)
Invalidate(; revert=name != current)
elseif exists(name, tag, taglut)
@info (name, current, lookup(taglut[], name))
Invalidate()
else
Validate(name)
......
......@@ -450,9 +450,9 @@ function setkeyboardevents!(scene::Scene, controller)
on(events(scene).keyboardbutton) do event
if event.action == Keyboard.press
if event.key in (Keyboard.left, Keyboard.down)
stepbackward!(player)
Players.stepbackward(player)
elseif event.key in (Keyboard.right, Keyboard.up)
stepforward!(player)
Players.stepforward(player)
end
end
end
......@@ -562,7 +562,8 @@ function DecoratedLarvae(larvae::Vector{DecoratedLarva})
@assert hovering_active[]
i = current_larva[]
if i == j
@warn "moving too fast? - please file an issue at https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues"
@warn "Moving too fast?"
return
end
if 0 < i
i_decorated = larvae[i].decorated
......
getsession(c) = gethub(c)[:session]
setsession!(c, session) = (gethub(c)[:session] = session)
using Makie: label_info
const r = Bonito.jsrender
......@@ -431,6 +432,7 @@ mutable struct TagController
hub::ControllerHub
view
fallback_color::String
# deprecation: manualtag is now stored in `hub` as well with key :manualtag
manualtag
end
......@@ -451,21 +453,28 @@ function TagController(controller::ControllerHub;
manualtag)
end
getmanualtag(c) = gethub(c)[:tag].manualtag
#getmanualtag(c::TagController) = c.manualtag
newtagname(::TagController) = NoTag
isfrozen(c::TagController, tag) = isfrozen(lookup(gettags(c)[], tag, true))
struct IndividualTagView
controller::TagController
tag::ObservableTag # view
frozen::Bool
dom_id
end
function IndividualTagView(controller::TagController, tag::ObservableTag)
IndividualTagView(controller, tag, dom_id())
function IndividualTagView(controller::TagController, tag::ObservableTag, frozen::Bool)
IndividualTagView(controller, tag, frozen, dom_id())
end
function tagview(controller::TagController, tag::ObservableTag)
IndividualTagView(controller, tag, isfrozen(tag))
end
tagview(controller, tag) = IndividualTagView(controller, tag)
isfrozen(tag::IndividualTagView) = tag.frozen
function lowerdom(ti::IndividualTagView)
tag = ti.tag
......@@ -497,8 +506,9 @@ function Bonito.jsrender(session::Session, ti::IndividualTagView)
tag = ti.tag
selector = dom_selector(ti) * " .tag-name"
events = tagevents(ti.controller)
on(events.renamefailed) do (name, failure)
name == tag.name[] || return
on(session, events.renamefailed) do (name, failure)
# tag.name[] == failure || return # if event triggered by model failure
tag.name[] == name || return # if event triggered by view failure
@info "Reverting to \"$(name)\" after failure: \"$(failure)\""
evaljs(session, js"document.querySelector($selector).value = $name;")
end
......@@ -542,8 +552,10 @@ function tagfilter(controller; manualtag=nothing)
end
function lowerdom(tf::TagFilter)
manualtag = getmanualtag(tf.controller)
tags = newview!(gethub(tf.controller)[:taghooks])
for tag in reverse(tags)
tag.name[] == manualtag && continue
push!(tf.views, tagview(tf.controller, tag))
end
return DOM.div(scrollable(tf.views...),
......@@ -609,49 +621,47 @@ function TagSelector(controller::TagController,
multiple::Union{Nothing, Bool}=nothing,
)
tag_lut = gettags(controller)
getnames(tags) = [tag.name[] for tag in tags if tag.active[]]
tagnames = Observable(getnames(tag_lut[]))
on(tag_lut) do tags
tagnames[] = getnames(tags)
manualtag = getmanualtag(controller)
tagnames = map(tag_lut) do tags
[tag.name[] for tag in tags if tag.active[] && tag.name[] != manualtag]
end
registered_observables = Observable{Bool}[]
selectedtags = Observable(Tuple{String, Observable{Bool}}[])
selectedtags = map(tagnames; ignore_equal_values=true) do names
for i in length(registered_observables)+1:length(names)
newobs = Observable(false)
on(newobs) do b
@debug "Tag $(i) $(b ? "" : "un")activated"
end
push!(registered_observables, newobs)
end
collect(zip(names, registered_observables[1:length(names)]))
end
refresh_view = map(selectedtags) do _
true
end
multitag = filterevents(controller).allow_multiple_tags
if !isnothing(multiple)
multitag[] = multiple
end
fromjs = nothing
if selectable
fromjs = Observable("")
on(fromjs) do actuatedtag
for (tag, selected) in selectedtags[]
if tag == actuatedtag
toggletag!(controller, tag, selected, selectedtags, selectable)
flag_active_larva_as_edited(controller)
if isfrozen(controller, tag)
@warn "Actuated tag is frozen" actuatedtag
selected[] = !selected[] # this observable reflects the state of the
# UI; the option element is in an improper state, which will be
# undone on the next update of the selector
else
toggletag!(controller, tag, selected, selectedtags, selectable)
flag_active_larva_as_edited(controller)
end
break
end
end
end
else
fromjs = nothing
end
ctrl = TagSelector(controller,
tagnames,
selectedtags,
selectable,
isnothing(multiple),
fromjs,
dom_id())
refresh_view = Observable(false)
on(tagnames) do names
for i in length(registered_observables)+1:length(names)
newobs = Observable(false)
on(newobs) do b
@debug "Tag $(i) $(b ? "" : "un")activated"
end
push!(registered_observables, newobs)
end
selectedtags[] = collect(zip(names, registered_observables[1:length(names)]))
notify(refresh_view)
end
activelarva = getactivelarva(controller)
onany(gettimestep(controller),
......@@ -685,12 +695,22 @@ function TagSelector(controller::TagController,
#notify(refresh_view) # applies only to the tag selector
notify(gettimestep(controller)) # full refresh
end
return ctrl
return TagSelector(controller,
tagnames,
selectedtags,
selectable,
isnothing(multiple),
fromjs,
dom_id())
end
function refresh_selected_tags(controller, id, timestep, taglut, selectedtags, selectable)
larva = getlarva(controller, id)
tags = getusertags(larva, timestep; lut=taglut)
refresh_selected_tags(controller, tags, selectedtags, selectable)
end
function refresh_selected_tags(controller, tags::UserTags, selectedtags, selectable)
tagnames = [tag.name[] for tag in tags]
jscode = String[]
for (name, selected) in Observables.to_value(selectedtags)
......@@ -741,32 +761,31 @@ function refresh_selected_tag(controller,
end
function toggletag!(controller, tagname, selected, selectedtags, selectable)
selected[] = !selected[]
multitag = filterevents(controller).allow_multiple_tags[]
multitag || selected[] || @debug "Deselecting a tag in a single tag setting"
#
larva_id = getactivelarva(controller)[]
larva = getlarva(controller, larva_id)
timestep = gettimestep(controller)[]
taglut = gettags(controller)[]
taglut = gettags(controller)[] # should not be mutated here, therefore we use the model
# instead of the view; otherwise use the view gethub(controller)[:taghooks]
tags = getusertags!(larva, timestep; lut=taglut)
tag = lookup(taglut, tagname)
isnothing(tag) && throw(KeyError(tagname))
#
if selected[]
# toggle
selected′= !selected[]
transaction = if selected′
if multitag || isempty(tags)
transaction = AddTag(larva_id, timestep, tagname)
settag!(tags, tag)
refresh_selected_tags(controller, tags, selectedtags, selectable)
AddTag(larva_id, timestep, tagname)
else
transaction = OverrideTags(larva_id, timestep, tagname, collect(tags))
settag!(tags, tag; single=true)
#
refresh_selected_tag(controller, tag, selectedtags, selectable)
OverrideTags(larva_id, timestep, tagname, collect(tags))
end
else
@assert !isempty(tags)
transaction = RemoveTag(larva_id, timestep, tagname)
deletetag!(tags, tag)
RemoveTag(larva_id, timestep, tagname)
end
push!(transactions(controller), transaction)
notify(larva.usertags)
......@@ -808,11 +827,7 @@ function Bonito.jsrender(session::Session, ts::TagSelector)
if ts.selectable
evaljs(session, js"LarvaTagger.setTagSelector($(ts.fromjs))")
end
selectedtags = ts.selected
prevtags = Ref(empty(selectedtags[]))
on(session, selectedtags) do selected
selected == prevtags[] && return
prevtags[] = selected
on(session, ts.selected) do selected
jsdom = r(session, lowertags(selected, ts.selectable))
htmldom = replace(string(jsdom), "'" => "\\'")
evaljs(session, js"""
......@@ -831,7 +846,8 @@ function tagselector(controller;
multipletags::Union{Nothing, Bool}=nothing,
)
c = gethub(controller)
TagSelector(haskey(c, :tag) ? c[:tag] : TagController(c), editabletags, multipletags)
controller = haskey(c, :tag) ? c[:tag] : TagController(c)
TagSelector(controller, editabletags, multipletags)
end
mutable struct TrackViewer
......@@ -902,10 +918,17 @@ struct FileMenu
browser::FileBrowser
end
function filemenu(controller; kwargs...)
function filemenu(controller; upload_button=false, download_button=false,
prepare_download=nothing, create_directory_button=false, delete_button=false,
kwargs...)
wd = getworkingdir(controller)
dir = joinpath(wd.root, wd.path[])
browser = FileBrowser(dir; root=wd.root, upload_button=false)
browser = FileBrowser(dir; root=wd.root, upload_button=upload_button,
download_button=download_button,
prepare_download=prepare_download,
create_directory_button=create_directory_button,
delete_button=delete_button)
gethub(controller)[:browser] = browser.model
on(FileBrowsers.selectedfile(browser)) do file
tryopenfile(controller, file; reload=true)
end
......@@ -920,9 +943,10 @@ function filemenu(controller; kwargs...)
on(FilePickers.uploadedfile(browser)) do fileinfo
filepath = joinpath(workingdir[], fileinfo["name"])
saveinputfile(filepath, fileinfo["content"])
notify(workingdir)
end
end
return FileMenu(gethub(controller), browser)
return FileMenu(controller, browser)
end
function Bonito.jsrender(session::Session, fm::FileMenu)
......