Skip to content
Snippets Groups Projects
Commit ac7720d8 authored by François  LAURENT's avatar François LAURENT
Browse files

Merge branch 'dev' into 'main'

Set of commits to be tagged v0.2

See merge request !5
parents 611d1054 bee7ecd1
Branches
Tags v0.2
1 merge request!5Set of commits to be tagged v0.2
Pipeline #150520 waiting for manual action
Showing with 1508 additions and 140 deletions
...@@ -73,7 +73,7 @@ build dev on gitlab.pasteur.fr: ...@@ -73,7 +73,7 @@ build dev on gitlab.pasteur.fr:
--docker-password="$DOCKER_TOKEN" --docker-password="$DOCKER_TOKEN"
--docker-email=kubernetes@pasteur.fr --docker-email=kubernetes@pasteur.fr
--dry-run=client --dry-run=client
-n "$NAMESPACE" -o yaml | kubectl apply -f - -o yaml | kubectl apply -f -
- helmfile lint - helmfile lint
- helmfile template - helmfile template
- helmfile sync - helmfile sync
......
name = "NyxUI" name = "NyxUI"
uuid = "2c32e805-e4ca-4d0f-96f6-9d2d6204339d" uuid = "2c32e805-e4ca-4d0f-96f6-9d2d6204339d"
authors = ["François Laurent <francois.laurent@pasteur.fr>"] authors = ["François Laurent <francois.laurent@pasteur.fr>"]
version = "0.1.0" version = "0.2.0"
[deps] [deps]
Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
...@@ -11,14 +11,18 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" ...@@ -11,14 +11,18 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Genie = "c43c736e-a2d1-11e8-161f-af95117fbd1e" Genie = "c43c736e-a2d1-11e8-161f-af95117fbd1e"
GenieSession = "03cc5b98-4f21-4eb6-99f2-22eced81f962" GenieSession = "03cc5b98-4f21-4eb6-99f2-22eced81f962"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
LarvaTagger = "8b3b36f1-dfed-446e-8561-ea19fe966a4d"
LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
NyxPlots = "e8b8ccdb-0776-4145-b74f-57bbbfff4409" NyxPlots = "e8b8ccdb-0776-4145-b74f-57bbbfff4409"
NyxWidgets = "c288fd06-43d3-4b04-8307-797133353e2e" NyxWidgets = "c288fd06-43d3-4b04-8307-797133353e2e"
Observables = "510215fc-4207-5dde-b226-833fc4488ee2" Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
ObservationPolicies = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
PlanarLarvae = "c2615984-ef14-4d40-b148-916c85b43307"
PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5" PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998" Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998"
StippleUI = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3" StippleUI = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
TidyObservables = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
...@@ -2,11 +2,12 @@ The Nyx project focuses on the neural activity and locomotion of the *Drosophila ...@@ -2,11 +2,12 @@ The Nyx project focuses on the neural activity and locomotion of the *Drosophila
# NyxUI.jl # NyxUI.jl
Web interface meant to be served at [nyx.pasteur.cloud](https://nyx.pasteur.cloud/). Web interface meant to be served at [nyx.pasteur.cloud](https://nyx.pasteur.cloud/) (public) and [nyx.dev.pasteur.cloud](https://nyx.dev.pasteur.cloud/) (internal).
It features an app catalog, with currently a single available app: It features an app catalog, with the following apps:
* an editor for muscular activity programs. * an editor for muscular activity programs,
* [LarvaTagger.jl](https://gitlab.pasteur.fr/nyx/larvatagger.jl) without any automating tagging backends.
## Local installation ## Local installation
...@@ -29,3 +30,9 @@ julia --project=. routes.jl ...@@ -29,3 +30,9 @@ julia --project=. routes.jl
You may be asked whether to authorize ports 9284 and 9285; please give the app permission. You may be asked whether to authorize ports 9284 and 9285; please give the app permission.
From there, in a web browser, open http://localhost:9284/ to access the app. From there, in a web browser, open http://localhost:9284/ to access the app.
Note that the generated files can be found somewhere in directory *storage/exports*.
The download buttons in the LarvaTagger app will not work in the web browser.
To make them work, the reverse proxy setup in the container image called *front* is required.
If you have [Podman](https://podman.io/), as an alternative to the above installation procedure, you can simply run `front/build.sh --now` and, once the container is up and running, connect to http://localhost:8080/.
...@@ -5,7 +5,10 @@ COPY . /app ...@@ -5,7 +5,10 @@ COPY . /app
WORKDIR /app WORKDIR /app
RUN git rev-parse --short HEAD > src/version.txt \ RUN git rev-parse --short HEAD > src/version.txt \
&& rm -rf .git public storage && rm -rf .git public storage \
&& git clone --branch dev https://gitlab.pasteur.fr/nyx/LarvaTagger.jl /app/LarvaTagger.jl \
&& (cd LarvaTagger.jl && git rev-parse --short HEAD) > src/apps/larvatagger/version.txt \
&& rm -rf LarvaTagger.jl/.git
FROM docker.io/nginxinc/nginx-unprivileged:1.27 FROM docker.io/nginxinc/nginx-unprivileged:1.27
...@@ -13,7 +16,7 @@ ENV JULIA_PROJECT /app ...@@ -13,7 +16,7 @@ ENV JULIA_PROJECT /app
ENV JULIA_DEPOT_PATH /app/julia ENV JULIA_DEPOT_PATH /app/julia
ENV JULIAUP_DEPOT_PATH /app/juliaup ENV JULIAUP_DEPOT_PATH /app/juliaup
ARG JULIA_VERSION=1.10.7 ARG JULIA_VERSION=1.10.8
# UID/GID should match with same arguments defined in: # UID/GID should match with same arguments defined in:
# https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/mainline/debian/Dockerfile # https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/mainline/debian/Dockerfile
...@@ -30,9 +33,10 @@ ENV PATH "$PATH:$JULIA_PROJECT/.juliaup/bin" ...@@ -30,9 +33,10 @@ ENV PATH "$PATH:$JULIA_PROJECT/.juliaup/bin"
RUN cd "$JULIA_PROJECT" \ RUN cd "$JULIA_PROJECT" \
&& cp front/Manifest.toml . \ && cp front/Manifest.toml . \
&& julia -e 'using Pkg; Pkg.instantiate()' \ && julia -e 'using Pkg; Pkg.instantiate(); Pkg.develop(PackageSpec(path="LarvaTagger.jl"))' \
&& mkdir -p public \ && mkdir -p public \
&& chmod a+x front/entrypoint.sh && chmod a+x front/entrypoint.sh \
&& juliaup config versionsdbupdateinterval 0
ARG PUBLIC_URL ARG PUBLIC_URL
......
...@@ -6,7 +6,7 @@ COPY ./NyxUI.jl/front/proxy.conf /etc/nginx/conf.d/default.conf ...@@ -6,7 +6,7 @@ COPY ./NyxUI.jl/front/proxy.conf /etc/nginx/conf.d/default.conf
ENV JULIAUP_DEPOT_PATH /juliaup ENV JULIAUP_DEPOT_PATH /juliaup
ENV JULIA_DEPOT_PATH /julia ENV JULIA_DEPOT_PATH /julia
ARG JULIA_VERSION=1.10.7 ARG JULIA_VERSION=1.10.8
RUN curl -fsSL https://install.julialang.org \ RUN curl -fsSL https://install.julialang.org \
| sh -s -- --yes --default-channel $JULIA_VERSION | sh -s -- --yes --default-channel $JULIA_VERSION
...@@ -16,14 +16,16 @@ ENV PATH "$PATH:/root/.juliaup/bin" ...@@ -16,14 +16,16 @@ ENV PATH "$PATH:/root/.juliaup/bin"
COPY ./NyxWidgets.jl /app/NyxWidgets.jl COPY ./NyxWidgets.jl /app/NyxWidgets.jl
COPY ./NyxPlots.jl /app/NyxPlots.jl COPY ./NyxPlots.jl /app/NyxPlots.jl
COPY ./NyxUI.jl /app/NyxUI.jl COPY ./NyxUI.jl /app/NyxUI.jl
COPY ./LarvaTagger.jl /app/LarvaTagger.jl
ENV JULIA_PROJECT /app/NyxUI.jl ENV JULIA_PROJECT /app/NyxUI.jl
RUN cd /app/NyxUI.jl \ RUN cd /app/NyxUI.jl \
&& julia -e 'using Pkg; Pkg.add([PackageSpec(path="../NyxWidgets.jl"), PackageSpec(path="../NyxPlots.jl")]); Pkg.instantiate();' \ && julia -e 'using Pkg; Pkg.add([PackageSpec(path="../NyxWidgets.jl"), PackageSpec(path="../NyxPlots.jl"), PackageSpec(url="https://gitlab.pasteur.fr/nyx/PlanarLarvae.jl"), PackageSpec(url="https://gitlab.com/dbc-nyx/ObservationPolicies.jl"), PackageSpec(url="https://gitlab.com/dbc-nyx/TidyObservables.jl"), PackageSpec(path="../LarvaTagger.jl")]); Pkg.instantiate();' \
&& mkdir -p public \ && mkdir -p public \
&& chmod a+x front/entrypoint.sh \ && chmod a+x front/entrypoint.sh \
&& sed -i -E 's/worker_processes .*;/worker_processes 2;/' /etc/nginx/nginx.conf && sed -i -E 's/worker_processes .*;/worker_processes 2;/' /etc/nginx/nginx.conf \
&& juliaup config versionsdbupdateinterval 0
ENTRYPOINT ["/app/NyxUI.jl/front/entrypoint.sh"] ENTRYPOINT ["/app/NyxUI.jl/front/entrypoint.sh"]
CMD [] CMD []
This diff is collapsed.
...@@ -11,13 +11,14 @@ IMAGE=nyx-dev ...@@ -11,13 +11,14 @@ IMAGE=nyx-dev
podman rmi -f $IMAGE || true podman rmi -f $IMAGE || true
rm -rf ../LarvaTagger.jl; cp -Rp ../../LarvaTagger/LarvaTagger.jl ../
podman build --tag $IMAGE -f "$CONTAINERFILE" .. # --no-cache podman build --tag $IMAGE -f "$CONTAINERFILE" .. # --no-cache
#mkdir -p public #mkdir -p public
podman run -d -p 8081:80 -p 9484:9284 -p 9485:9285 \ podman run -d -p 8081:80 -p 9484:9284 -p 9485:9285 \
$IMAGE $IMAGE
# -v `realpath ./public`:/usr/share/nginx/html \
CONTAINER=`podman ps | grep $IMAGE | cut -d' ' -f1` CONTAINER=`podman ps | grep $IMAGE | cut -d' ' -f1`
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
set -m set -m
/docker-entrypoint.sh nginx -g "daemon off;" & /docker-entrypoint.sh nginx -g "daemon off;" &>/dev/null &
NGINX_PID=$! NGINX_PID=$!
......
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}-front
labels:
app: $APP_NAME
spec:
replicas: 1
selector:
matchLabels:
app: $APP_NAME
template:
metadata:
labels:
app: $APP_NAME
nyxui/component: front
spec:
containers:
- name: $APP_NAME
image: $FQ_IMAGE_NAME
resources:
limits:
cpu: "2"
ephemeral-storage: 1Gi
memory: 4Gi
requests:
cpu: "2"
ephemeral-storage: 1Gi
memory: 4Gi
ports:
- name: http
containerPort: 8080
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
\ No newline at end of file
apiVersion: v1
kind: Service
metadata:
name: ${APP_NAME}-front
spec:
selector:
app: $APP_NAME
nyxui/component: front
ports:
- protocol: TCP
port: 80
targetPort: http
name: http
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: $APP_NAME
labels:
app: $APP_NAME
spec:
ingressClassName: internal
rules:
- host: $PUBLIC_URL
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ${APP_NAME}-front
port:
number: 80
...@@ -15,10 +15,10 @@ type: application ...@@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0 version: 0.2.0
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "0.1" appVersion: "0.2.0"
...@@ -31,7 +31,7 @@ if Genie.Configuration.isprod() ...@@ -31,7 +31,7 @@ if Genie.Configuration.isprod()
end end
function modelview(model, view) function modelview(model, view)
page(model, NyxUI.add_footer(view); title=nyxui_title) page(model, view; title=nyxui_title)
end end
Stipple.Layout.add_css(nyxui_css) Stipple.Layout.add_css(nyxui_css)
...@@ -49,6 +49,13 @@ route("/muscles") do ...@@ -49,6 +49,13 @@ route("/muscles") do
MuscleApp.handler(modelview) MuscleApp.handler(modelview)
end end
include("src/apps/larvatagger/app.jl")
LarvaTagger.bonito_app.name = "larvatagger"
route("/larvatagger") do
LarvaTagger.handler(modelview)
end
run_as_script = isinteractive() run_as_script = isinteractive()
up(; async=run_as_script) up(; async=run_as_script)
#display(NyxUI.bonitoserver()) #display(NyxUI.bonitoserver())
......
...@@ -55,14 +55,21 @@ function route(session::AbstractString, suffix::AbstractString="", ...@@ -55,14 +55,21 @@ function route(session::AbstractString, suffix::AbstractString="",
return join(parts, "/") return join(parts, "/")
end end
addsession(args...; kwargs...) = addsession(bonitoserver(; kwargs...), args...) function addsession(args...; restart=false, kwargs...)
addsession(bonitoserver(; kwargs...), args...; restart=restart)
end
function addsession(server::Bonito.Server, named_app, session_id) function addsession(server::Bonito.Server, named_app, session_id; restart=false)
appname = name(named_app) app_name = name(named_app)
if !Bonito.has_route(server.routes, route(session_id, appname)) app_route = route(session_id, app_name)
@info "New user session" session_id appname if restart || !Bonito.has_route(server.routes, app_route)
if Bonito.has_route(server.routes, app_route)
@info "New handler" session_id app_name
else
@info "New user session" session_id app_name
end
app = named_app.app app = named_app.app
Bonito.route!(server, route(session_id, appname) => app(session_id)) Bonito.route!(server, app_route => app(session_id))
Bonito.HTTPServer.start(server) Bonito.HTTPServer.start(server)
end end
end end
......
...@@ -2,7 +2,7 @@ module GenieExtras ...@@ -2,7 +2,7 @@ module GenieExtras
import Genie.Renderer.Html import Genie.Renderer.Html
export publish_css, add_footer export publish_css, appinfo, add_footer
const project_root = dirname(Base.current_project()) const project_root = dirname(Base.current_project())
...@@ -30,7 +30,7 @@ function publish_css(; clear=true) ...@@ -30,7 +30,7 @@ function publish_css(; clear=true)
return css_dir return css_dir
end end
function footer() function appinfo()
version = string(pkgversion(@__MODULE__)) version = string(pkgversion(@__MODULE__))
if endswith(version, ".0") if endswith(version, ".0")
version = version[1:end-2] version = version[1:end-2]
...@@ -39,12 +39,14 @@ function footer() ...@@ -39,12 +39,14 @@ function footer()
if isfile(versionfile) if isfile(versionfile)
version = join((version, readchomp(versionfile)), "-") version = join((version, readchomp(versionfile)), "-")
end end
tagurl = "https://gitlab.com/dbc-nyx/NyxUI.jl/-/tags" tagurl = "https://gitlab.pasteur.fr/nyx/NyxUI/-/tags"
Html.footer("NyxUI.jl <a href=\"$tagurl\">v$version</a>") return "NyxUI.jl <a href=\"$tagurl\">v$version</a>"
end end
add_footer(ui::AbstractString) = "$ui$(footer())" footer() = Html.footer(appinfo())
add_footer(ui::Function) = () -> add_footer(ui()) add_footer(ui::AbstractString, footer=footer) = "$ui$(footer())"
add_footer(ui::Function, footer=footer) = () -> add_footer(ui(), footer)
end end
...@@ -3,7 +3,7 @@ module Storage ...@@ -3,7 +3,7 @@ module Storage
using Dates using Dates
using TOML using TOML
export getconfig, getbucket, clear_oldest export getconfig, getbucket, clear_oldest, purge_appdata
const __storage_location__ = get(ENV, "STORAGE", const __storage_location__ = get(ENV, "STORAGE",
joinpath(dirname(Base.current_project()), "storage")) joinpath(dirname(Base.current_project()), "storage"))
...@@ -32,10 +32,12 @@ function getconfig(key, morekeys...; default=nothing) ...@@ -32,10 +32,12 @@ function getconfig(key, morekeys...; default=nothing)
config isa Dict ? default : config config isa Dict ? default : config
end end
getbucket(session_id) = joinpath(__storage_location__, "exports", session_id) function getbucket(session_id, appname)
joinpath(__storage_location__, "exports", session_id, appname)
end
function getbucket(session_id, mode) function getbucket(session_id, appname, mode)
session_bucket = getbucket(session_id) session_bucket = getbucket(session_id, appname)
if mode === :read if mode === :read
session_bucket session_bucket
elseif mode === :write elseif mode === :write
...@@ -50,8 +52,10 @@ end ...@@ -50,8 +52,10 @@ end
function getbucket(; child_resource) function getbucket(; child_resource)
parts = splitpath(child_resource) parts = splitpath(child_resource)
session_id = parts[findfirst(==("exports"), parts) + 1] exports = findfirst(==("exports"), parts)
return getbucket(session_id) session_id = parts[exports + 1]
appname = parts[exports + 2]
return getbucket(session_id, appname)
end end
function clear_oldest(session_bucket) function clear_oldest(session_bucket)
...@@ -61,4 +65,11 @@ function clear_oldest(session_bucket) ...@@ -61,4 +65,11 @@ function clear_oldest(session_bucket)
rm(oldest; recursive=true) rm(oldest; recursive=true)
end end
function purge_appdata(session_id, appname)
bucket = getbucket(session_id, appname, :read)
isdir(bucket) && rm(bucket; recursive=true)
exportdir = joinpath("public", session_id, appname)
isdir(exportdir) && rm(exportdir; recursive=true)
end
end end
...@@ -34,10 +34,10 @@ function app_catalog_view() ...@@ -34,10 +34,10 @@ function app_catalog_view()
Html.div(class=caption, "Coming soon!"), Html.div(class=caption, "Coming soon!"),
), ),
]))), ]))),
item(item(class=app, Html.a(href="", class="disabled", [ item(item(class=app, Html.a(href=Router.link_to(:get_larvatagger), [
Html.h1("LarvaTagger", style=header2), Html.h1("LarvaTagger", style=header2),
imageview(src="/images/LarvaTagger.png", style=img, imageview(src="/images/LarvaTagger.png", style=img,
Html.div(class=caption, "Coming soon!"), Html.div(class=caption, "Partially available"),
), ),
]))), ]))),
]), ]),
......
module LarvaTagger
using NyxUI, NyxUI.Storage, NyxUI.GenieExtras
using NyxWidgets.Base: Cache
import LarvaTagger as LT
using GenieSession, Stipple
import Stipple: @app, @init, @private, @in, @onchange, @onbutton, @click
import StippleUI: tooltip
const sizefactors = Dict("1.0"=>1., "1.5"=>1.5, "2.0"=>2.)
const sizefactors_str = Dict(v=>k for (k,v) in pairs(sizefactors))
const appheight = Dict("1.0"=>"900px", "1.5"=>"1300px", "2.0"=>"1700px")
const appwidth = Dict("1.0"=>"1680px", "1.5"=>"2280px", "2.0"=>"2880px")
mutable struct Model
sizefactor
app # TODO: check whether explicitly closing apps helps
appdata
end
const bonito_models = Cache{String, Model}()
const bonito_app = NamedApp("larvatagger",
function (session)
bucket = getbucket(session, "larvatagger", :read)
mkpath(bucket)
model = lock(bonito_models) do
if haskey(bonito_models, session)
bonito_models.cache[session]
else
inputfile = Ref{Union{Nothing, String}}(nothing)
bonito_models.cache[session] = Model(1.0, nothing, inputfile)
end
end
exportdir = joinpath("public", session, "larvatagger")
function prepare_download(srcfile)
mkpath(exportdir)
filename = basename(srcfile)
tempfile = joinpath(exportdir, filename)
if !samefile(srcfile, tempfile)
open(tempfile, "w") do fout
open(srcfile, "r") do fin
write(fout, read(fin))
end
end
end
return "/$session/larvatagger/$filename"
end
isnothing(model.app) || close(model.app)
model.app = app = LT.larvaeditor(model.appdata;
root_directory=bucket,
enable_uploads=true,
enable_downloads=true,
prepare_download=prepare_download,
enable_new_directories=true,
enable_delete=true,
viewfactor=model.sizefactor)
return app
end
)
function purgesession(session)
model = lock(bonito_models) do
pop!(bonito_models.cache, session)
end
isnothing(model.app) || close(model.app)
purge_appdata(session, "larvatagger")
BonitoServer.addsession(bonito_app, session; restart=true)
end
@app begin
@private bonito_session_id = ""
@in sizefactor = "1.0"
@in reset_bonito_session = false
@onchange isready begin
genie_session = GenieSession.session()
bonito_session_id = genie_session.id[1:32]
if haskey(bonito_models, bonito_session_id)
sizefactor = sizefactors_str[bonito_models[bonito_session_id].sizefactor]
else
BonitoServer.addsession(bonito_app, bonito_session_id)
end
url = bonito_url(bonito_app, bonito_session_id)
width = appwidth[sizefactor]
height = appheight[sizefactor]
run(__model__, """
const iframe = document.getElementById('bonito');
iframe.src = '$url';
iframe.style.height = '$height';
document.getElementById('footer').style.width = '$width';
document.getElementById('iframe_alt_text').style.display = 'none';
""")
end
@onchange sizefactor begin
new_sizefactor = sizefactors[sizefactor]
if new_sizefactor != bonito_models[bonito_session_id].sizefactor
@info "New size factor; refreshing the iframe"
bonito_models[bonito_session_id].sizefactor = new_sizefactor
BonitoServer.addsession(bonito_app, bonito_session_id; restart=true) # restart
width = appwidth[sizefactor]
height = appheight[sizefactor]
run(__model__, """
const iframe = document.getElementById('bonito');
iframe.style.height = '$height';
iframe.src += '';
document.getElementById('footer').style.width = '$width';
""")
end
end
@onbutton reset_bonito_session begin
purgesession(bonito_session_id)
# reset UI to defaults
bonito_session_id[!] = ""
sizefactor[!] = "1.0"
# reload page
run(__model__, "location.reload(true);")
end
end
function view()
[
Html.div("Refresh the page if the present message does not disappear after a few seconds";
id="iframe_alt_text",
style="width:$(appwidth["1.0"]);margin:auto;padding:2rem;"),
Html.iframe(; id="bonito", style="width:100%;height:0;border:none;"),
Html.div([
Html.div([
Html.span("2D view size factor:&nbsp;"; style="margin-right:0.5rem;"),
Html.select(:sizefactor; options=collect(keys(sizefactors)),
multiple=false, clearable=false, counter=false, usechips=false,
dense=true, var"options-dense"=true)];
style="display:flex;flex-direction:horizontal;align-items:center;"),
Html.span("Reset session", @click(:reset_bonito_session),
style="cursor:pointer;",
[tooltip("Useful when the app crashes. All data will be lost!")]),
Html.span(appinfo())];
id="footer",
),
]
end
function appinfo()
version = string(pkgversion(LT))
if endswith(version, ".0")
version = version[1:end-2]
end
versionfile = joinpath(@__DIR__, "version.txt")
if isfile(versionfile)
version = join((version, readchomp(versionfile)), "-")
end
tagurl = "https://gitlab.pasteur.fr/nyx/LarvaTagger.jl/-/tags"
return "LarvaTagger.jl <a href=\"$tagurl\">v$version</a>"
end
function handler(_)
model = @init
page(model, view; title="LarvaTagger")
end
end
...@@ -9,7 +9,7 @@ include("MuscleWidgets.jl") ...@@ -9,7 +9,7 @@ include("MuscleWidgets.jl")
using .MuscleWidgets using .MuscleWidgets
export hasmodel, getmodel, setmodel, newsequence, getsequence, withsequences, export hasmodel, getmodel, setmodel, newsequence, getsequence, withsequences,
exportsequence, loadsequence, deletesequence exportsequence, loadsequence, deletesequence, purgesession
# mutable struct Backbone{W} # mutable struct Backbone{W}
# widget::Union{Nothing, W} # widget::Union{Nothing, W}
...@@ -132,10 +132,10 @@ end ...@@ -132,10 +132,10 @@ end
## persistent storage ## persistent storage
function exportsequence(session_id) function exportsequence(session_id; appname="muscles")
sequence = getsequence(session_id) sequence = getsequence(session_id)
isempty(sequence.program_name) && return "" isempty(sequence.program_name) && return ""
dir = getbucket(session_id, :write) dir = getbucket(session_id, appname, :write)
filepath = joinpath(dir, sequence.program_name * ".json") filepath = joinpath(dir, sequence.program_name * ".json")
MuscleActivities.to_json_file(filepath, sequence) MuscleActivities.to_json_file(filepath, sequence)
return filepath return filepath
...@@ -166,4 +166,20 @@ function deletesequence(session_id) ...@@ -166,4 +166,20 @@ function deletesequence(session_id)
end end
end end
function purgesession(session_id; appname="muscles")
model = lock(__backbone_cache__) do
if session_id in keys(__backbone_cache__.cache)
#pop!(__valid_sessions__, session_id) # not sure what to do with it
pop!(__backbone_cache__.cache, session_id)
end
end
lock(__sequences__) do
if session_id in keys(__sequences__.cache)
pop!(__sequences__.cache, session_id)
end
end
purge_appdata(session_id, appname)
return model
end
end end
...@@ -22,7 +22,6 @@ struct MuscleWidget ...@@ -22,7 +22,6 @@ struct MuscleWidget
sequence::AbstractObservable sequence::AbstractObservable
colormap::AbstractObservable colormap::AbstractObservable
colorscheme::AbstractObservable colorscheme::AbstractObservable
setvalue::AbstractObservable
overview::Muscles.MultiSegmentMuscleWidget overview::Muscles.MultiSegmentMuscleWidget
individualsegments::Vector{Muscles.MuscleWidget} individualsegments::Vector{Muscles.MuscleWidget}
heatmaps::Vector{Heatmap} heatmaps::Vector{Heatmap}
...@@ -39,17 +38,16 @@ function MuscleWidget(sequence) ...@@ -39,17 +38,16 @@ function MuscleWidget(sequence)
on(colormap) do c on(colormap) do c
colorscheme[] = scale2scheme(c) colorscheme[] = scale2scheme(c)
end end
setvalue = Observable{Float64}(1)
editmode = Observable(false) editmode = Observable(false)
# #
overview = Muscles.MultiSegmentMuscleWidget(; musclelabel=false) overview = Muscles.MultiSegmentMuscleWidget(; musclelabel=false, sidelabel=:right)
individualsegments = [Muscles.MuscleWidget(; segment=segment.segment, individualsegments = [Muscles.MuscleWidget(; segment=segment.segment,
side=segment.side, side=segment.side,
upsidedown=segment.side=="right", upsidedown=segment.side=="right",
segmentlabel=true, segmentlabel=true,
style="width: 40%; margin-top: 2rem;") style="width: 40%; margin-top: 2rem;")
for segment in segments(overview)] for segment in segments(overview)]
moveto = clipboard = zlim = nothing moveto = clipboard = zlim = setvalue = nothing
heatmaps = Heatmap[] heatmaps = Heatmap[]
for segment in individualsegments for segment in individualsegments
halfsegment = sequence[][segment.segment, segment.side] halfsegment = sequence[][segment.segment, segment.side]
...@@ -61,10 +59,12 @@ function MuscleWidget(sequence) ...@@ -61,10 +59,12 @@ function MuscleWidget(sequence)
pick=moveto, pick=moveto,
clipboard=clipboard, clipboard=clipboard,
setvalue=setvalue, setvalue=setvalue,
zlim=zlim) zlim=zlim,
prompt_on_assign=true)
moveto = heatmap.pick moveto = heatmap.pick
clipboard = heatmap.clipboard clipboard = heatmap.clipboard
zlim = heatmap.zlim zlim = heatmap.zlim
setvalue = heatmap.setvalue
push!(heatmaps, heatmap) push!(heatmaps, heatmap)
end end
detailedviews = [Div(widget, heatmap; style=:row) detailedviews = [Div(widget, heatmap; style=:row)
...@@ -137,7 +137,7 @@ function MuscleWidget(sequence) ...@@ -137,7 +137,7 @@ function MuscleWidget(sequence)
heatmaps[layer-1].editing[] = b heatmaps[layer-1].editing[] = b
end end
end end
return MuscleWidget(sequence, colormap, colorscheme, setvalue, overview, return MuscleWidget(sequence, colormap, colorscheme, overview,
individualsegments, heatmaps, animation, editmode) individualsegments, heatmaps, animation, editmode)
end end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment