diff --git a/front/Manifest.toml b/front/Manifest.toml index 3de4c3baa3ba5bb3930c2adde55d3ba205fee0cf..bb7f14771020bcaa002752f4435a92a481dd129a 100644 --- a/front/Manifest.toml +++ b/front/Manifest.toml @@ -427,7 +427,7 @@ version = "1.0.0" [[deps.NyxPlots]] deps = ["Bonito", "Observables", "PlotlyBase"] -git-tree-sha1 = "86ee6e2a8996e9bff6bfce8c41a71afcedd420a0" +git-tree-sha1 = "e54fcb78c5337200724a3f1b6c6401e645a5263f" repo-rev = "main" repo-url = "https://gitlab.com/dbc-nyx/NyxPlots.jl" uuid = "e8b8ccdb-0776-4145-b74f-57bbbfff4409" diff --git a/src/apps/muscles/MuscleWidgets.jl b/src/apps/muscles/MuscleWidgets.jl index 398c251c2979d40f5c6022bb87357b43a6b840b5..786a5c7cabae2aa0dc61af73aa9ef48cf2f36a9f 100644 --- a/src/apps/muscles/MuscleWidgets.jl +++ b/src/apps/muscles/MuscleWidgets.jl @@ -1,5 +1,6 @@ module MuscleWidgets +using Base: has_nondefault_cmd_flags using NyxWidgets.Players, NyxWidgets.Muscles, NyxWidgets.AnimatedLayers import NyxWidgets.Base: lowerdom, dom_id, dom_id!, Div using NyxPlots @@ -49,7 +50,7 @@ function MuscleWidget(sequence) segmentlabel=true, style="width: 40%; margin-top: 2rem;") for segment in segments(overview)] - moveto = nothing + moveto = clipboard = nothing heatmaps = Heatmap[] for segment in individualsegments halfsegment = sequence[][segment.segment, segment.side] @@ -59,8 +60,11 @@ function MuscleWidget(sequence) colorscale=colormap, width=606, height=800, zmin=0., zmax=1., - click=moveto) - moveto = heatmap.click + pick=moveto, + clipboard=clipboard, + setvalue=setvalue) + moveto = heatmap.pick + clipboard = heatmap.clipboard push!(heatmaps, heatmap) end detailedviews = [Div(widget, heatmap; style=:row) @@ -111,15 +115,10 @@ function MuscleWidget(sequence) @info "Click on muscle" name=xyz[2] step value=xyz[3] end for (segment, heatmap) in zip(individualsegments, heatmaps) - on(heatmap.select) do coord - x, y = coord - @assert !isempty(y) + onany(heatmap.assign, heatmap.paste) do _, _ seq = sequence[][segment.segment, segment.side] - t0, t1 = [findfirst(==(convert(Float64, t)), seq.times) for t in x] - foreach(y) do muscle - seq[muscle, t0:t1] = setvalue[] - end - Heatmaps.update(heatmap, convert(Matrix, seq)) + @debug "heatmap" heatmap.z[] + seq.activity = heatmap.z[] end end on(colormap) do colorscale @@ -129,13 +128,13 @@ function MuscleWidget(sequence) on(animation.switchlayer) do layers current, _, _ = layers if 1 < current - heatmaps[current-1].selecting[] = editmode[] + heatmaps[current-1].editing[] = editmode[] end end on(editmode) do b layer = activelayer(Int, animation) if 1 < layer - heatmaps[layer-1].selecting[] = b + heatmaps[layer-1].editing[] = b end end return MuscleWidget(sequence, colormap, colorscheme, setvalue, overview, @@ -225,4 +224,15 @@ function clear(widget) end end +clipboard(widget::MuscleWidget) = clipboard(widget.heatmaps[1]) + +clipboard(heatmap::Heatmap) = heatmap.clipboard + +function areaselect(widget::MuscleWidget) + obs = Observable(true) + f(values...) = notify(obs) + onany(f, [h.select for h in widget.heatmaps]...) + return obs +end + end diff --git a/src/apps/muscles/app.jl b/src/apps/muscles/app.jl index 619b12b911ae9b4b8d7e7b64850f926054e35ff4..b24ccc970093d408c5bd7e24ecb17515264c34e1 100644 --- a/src/apps/muscles/app.jl +++ b/src/apps/muscles/app.jl @@ -1,6 +1,7 @@ module MuscleApp using NyxUI, NyxUI.Storage +using Observables using Genie, GenieSession, Stipple, StippleUI import Stipple: @app, @init, @private, @in, @out, @onchange, @onbutton, @notify @@ -9,9 +10,11 @@ using .Backbone export muscle_view -const default_colormap = Backbone.MuscleWidgets.__default_colormap__ +const MuscleWidgets = Backbone.MuscleWidgets -const colormaps = Backbone.MuscleWidgets.__colormaps__ +const default_colormap = MuscleWidgets.__default_colormap__ + +const colormaps = MuscleWidgets.__colormaps__ const maxlength = getconfig("muscle-activity", "maxlength") @@ -41,6 +44,9 @@ const bonito_app = NamedApp(:inherit, Backbone.app) @in time_interval = 0.05 @in start_time = 0.0 + @out area_selected = false + @out new_clipboard_item = false + @onchange isready begin _, url = init_model(__model__) run(__model__, """ @@ -51,12 +57,12 @@ const bonito_app = NamedApp(:inherit, Backbone.app) iframe.src = '$url'; """) session_id = back_session_id + model = getmodel(session_id) if true # TODO: diagnose missing Stipple initialization # restore state after page reload (Ctrl+R) notify(sequence_names) selected_sequence_name = name = getsequence(session_id).program_name selected_sequence_index = @something findfirst(==(name), sequence_names[!]) 0 - model = getmodel(session_id) colormap = model.colormap[] editmode = model.editmode[] sequence_name = name @@ -66,6 +72,14 @@ const bonito_app = NamedApp(:inherit, Backbone.app) time_interval = step(seq.times) start_time = first(seq.times) end + on(MuscleWidgets.clipboard(model)) do _ + new_clipboard_item[!] = false + new_clipboard_item = true + end + on(MuscleWidgets.areaselect(model)) do _ + area_selected[!] = false + area_selected = true + end end @onchange start_time, time_interval, series_length begin @@ -77,21 +91,21 @@ const bonito_app = NamedApp(:inherit, Backbone.app) if time_interval < 0 # do not trigger on 0, because editing the corresponding field often implies # typing "0." first - @notify "time interval must be strictly positive" :info + @notify "Time interval must be strictly positive" :error time_interval = step(seq.times) end valid = false end if series_length < 1 - @notify "time series length must be strictly positive" :info + @notify "Time series length must be strictly positive" :error series_length = length(seq.times) valid = false elseif round(series_length) != series_length - @notify "time series length must be integer" :info + @notify "Time series length must be an integer" :error series_length = round(series_length) valid = false elseif !isnothing(maxlength) && maxlength < series_length - @notify "time series is too long" :info + @notify "Time series is too long" :error series_length = length(seq.times) valid = false end @@ -176,7 +190,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app) sequence_names[!][selected_sequence_index] = sequence_name notify(sequence_names) else - @notify "Motor program name contains prohibited characters" :info + @notify "Motor program name contains prohibited characters" :error sequence_name = sequence[!].program_name end end @@ -200,7 +214,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app) @onbutton export_sequence_click begin filepath = exportsequence(back_session_id) if isempty(filepath) - @notify "No available motor programs; create one first" :info + @notify "No available motor programs; create one first" :error else filename = basename(filepath) mkpath("./public/$back_session_id") @@ -242,6 +256,13 @@ const bonito_app = NamedApp(:inherit, Backbone.app) fileuploads = empty!(fileuploads) end end + + @onchange new_clipboard_item begin + @notify "Selection copied! Press Ctrl while clicking to paste" :info + end + @onchange area_selected begin + @notify "Click inside the selected area to assign the setvalue, or press Ctrl while clicking to copy the selection" :info + end end function dropdown(key, options, label; margin=true, class=nothing)