diff --git a/Project.toml b/Project.toml index 993d8dc..99aefd0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Stipple" uuid = "4acbeb90-81a0-11ea-1966-bdaff8155998" authors = ["Adrian and the Stipple contributors"] -version = "0.28.18" +version = "0.28.19" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" @@ -38,7 +38,7 @@ StippleOffsetArraysExt = "OffsetArrays" DataFrames = "1" Dates = "1.6" FilePathsBase = "0.9" -Genie = "5.31" +Genie = "5.31.1" GenieSession = "1" GenieSessionFileSession = "1" JSON = "0.20, 0.21" diff --git a/src/Layout.jl b/src/Layout.jl index 48aa51f..7178c3a 100644 --- a/src/Layout.jl +++ b/src/Layout.jl @@ -319,15 +319,27 @@ function set_theme!(theme::Symbol) else THEMES[][Stipple.Theme.THEME_INDEX[]] = Stipple.Theme.to_asset(theme) end + + theme == :usertheme && ! Stipple.Theme.theme_exists(:usertheme) && register_usertheme() Stipple.Theme.set_theme(theme) + try + read_theme_dotfile() != theme && write_theme_dotfile(theme) + catch ex + @warn "Could not write theme to dotfile: $ex" + end + nothing end const set_theme = set_theme! +const THEME_DOTFILE = joinpath(".theme") const DEFAULT_USER_THEME_FILE = joinpath("css", Stipple.THEMES_FOLDER, "theme.css") const USER_THEME_WATCHER = Ref(false) +const DOTTHEME_WATCHER = Ref(false) + + function set_user_theme_watcher() :: Bool (USER_THEME_WATCHER[] || ! Genie.Configuration.isdev()) && return false @@ -344,6 +356,64 @@ function set_user_theme_watcher() :: Bool end +function set_dottheme_watcher() :: Bool + (DOTTHEME_WATCHER[] || ! Genie.Configuration.isdev()) && return false + + @async Genie.Revise.entr([THEME_DOTFILE]) do + theme_from_dotfile = read_theme_dotfile() + if theme_from_dotfile !== nothing && Stipple.Theme.get_theme() != theme_from_dotfile + if theme_from_dotfile == :usertheme && ! Stipple.Theme.theme_exists(:usertheme) + register_usertheme() + end + set_theme!(theme_from_dotfile) + end + end + DOTTHEME_WATCHER[] = true + + true +end + + +function read_theme_dotfile() :: Union{Symbol,Nothing} + if ! isfile(THEME_DOTFILE) + try + touch(THEME_DOTFILE) # create the file if it doesn't exist + catch ex + @warn "Could not create theme dotfile: $ex" + end + + return nothing + end + + try + Symbol(open(THEME_DOTFILE, "r") do file + read(file, String) + end) + catch + nothing + end +end + + +function write_theme_dotfile(theme::Symbol) + isfile(THEME_DOTFILE) || return + + open(THEME_DOTFILE, "w") do file + write(file, string(theme)) + end + + nothing +end + + +function register_usertheme() :: Bool + isfile(joinpath(Genie.config.server_document_root, DEFAULT_USER_THEME_FILE)) || return false + register_theme(:usertheme, "/" * join(splitpath(DEFAULT_USER_THEME_FILE), "/")) # make this a relative URL + + true +end + + """ function theme() :: String @@ -375,15 +445,31 @@ function theme(; core_theme::Bool = true) :: Vector{String} unique!(THEMES[]) + has_custom_theme = false + has_dottheme = false + user_theme_file = joinpath(Genie.config.server_document_root, DEFAULT_USER_THEME_FILE) if isfile(user_theme_file) - register_theme(:usertheme, "/" * join(splitpath(DEFAULT_USER_THEME_FILE), "/")) # make this a relative URL + register_usertheme() + has_custom_theme = true + end + + theme_from_dotfile = read_theme_dotfile() + if theme_from_dotfile !== nothing && Stipple.Theme.theme_exists(theme_from_dotfile) + has_dottheme = true + end + + if has_dottheme + set_theme!(theme_from_dotfile) + elseif has_custom_theme set_theme!(:usertheme) - set_user_theme_watcher() else - set_theme!(Stipple.Theme.get_theme()) # set the default theme + set_theme!(:default) end + set_dottheme_watcher() + set_user_theme_watcher() + for f in THEMES[] _o = f() if _o isa Vector || _o isa Tuple diff --git a/src/Stipple.jl b/src/Stipple.jl index 5a55392..45c9c5d 100644 --- a/src/Stipple.jl +++ b/src/Stipple.jl @@ -15,8 +15,6 @@ existing Vue.js libraries. """ module Stipple -using PrecompileTools -const PRECOMPILE = Ref(false) const ALWAYS_REGISTER_CHANNELS = Ref(true) const USE_MODEL_STORAGE = Ref(true) @@ -526,7 +524,7 @@ function init(t::Type{M}; # add a timer that checks if the model is outdated and if so prepare the model to be garbage collected LAST_ACTIVITY[Symbol(getchannel(model))] = now() - PRECOMPILE[] || Timer(setup_purge_checker(model), PURGE_CHECK_DELAY[], interval = PURGE_CHECK_DELAY[]) + # PRECOMPILE[] || Timer(setup_purge_checker(model), PURGE_CHECK_DELAY[], interval = PURGE_CHECK_DELAY[]) # register channels and routes only if within a request if haskey(Genie.Router.params(), :CHANNEL) || haskey(Genie.Router.params(), :ROUTE) || always_register_channels @@ -1316,46 +1314,48 @@ include("Layout.jl") @reexport using .Theme @reexport using .Layout -# precompilation ... - using Stipple.ReactiveTools -@setup_workload begin - # Putting some things in `setup` can reduce the size of the - # precompile file and potentially make loading faster. - using Genie.HTTPUtils.HTTP - PRECOMPILE[] = true - @compile_workload begin - # all calls in this block will be precompiled, regardless of whether - # they belong to your package or not (on Julia 1.8 and higher) - ui() = [cell("hello"), row("world"), htmldiv("Hello World")] - - @app PrecompileApp begin - @in demo_i = 1 - @out demo_s = "Hi" - - @onchange demo_i begin - println(demo_i) - end - end +# precompilation ... - route("/") do - model = Stipple.ReactiveTools.@init PrecompileApp - page(model, ui) |> html - end - port = tryparse(Int, get(ENV, "STIPPLE_PRECOMPILE_PORT", "")) - port === nothing && (port = rand(8081:8999)) - up(port) - - precompile_get = tryparse(Bool, get(ENV, "STIPPLE_PRECOMPILE_GET", "1")) - precompile_get === true && HTTP.get("http://localhost:$port") - # The following lines (still) produce an error although - # they pass at the repl. Not very important though. - # HTTP.get("http://localhost:$port$(Genie.Assets.asset_path(Genie.assets_config, :js, file = "channels"))") - # HTTP.get("http://localhost:$port$(Genie.Assets.asset_path(assets_config, :js, file = "stipplecore"))") - down() - end - PRECOMPILE[] = false -end +# using PrecompileTools +# const PRECOMPILE = Ref(false) +# @setup_workload begin +# # Putting some things in `setup` can reduce the size of the +# # precompile file and potentially make loading faster. +# using Genie.HTTPUtils.HTTP +# PRECOMPILE[] = true +# @compile_workload begin +# # all calls in this block will be precompiled, regardless of whether +# # they belong to your package or not (on Julia 1.8 and higher) +# ui() = [cell("hello"), row("world"), htmldiv("Hello World")] + +# @app PrecompileApp begin +# @in demo_i = 1 +# @out demo_s = "Hi" + +# @onchange demo_i begin +# println(demo_i) +# end +# end + +# route("/") do +# model = Stipple.ReactiveTools.@init PrecompileApp +# page(model, ui) |> html +# end +# port = tryparse(Int, get(ENV, "STIPPLE_PRECOMPILE_PORT", "")) +# port === nothing && (port = rand(8081:8999)) +# up(port) + +# precompile_get = tryparse(Bool, get(ENV, "STIPPLE_PRECOMPILE_GET", "1")) +# precompile_get === true && HTTP.get("http://localhost:$port") +# # The following lines (still) produce an error although +# # they pass at the repl. Not very important though. +# # HTTP.get("http://localhost:$port$(Genie.Assets.asset_path(Genie.assets_config, :js, file = "channels"))") +# # HTTP.get("http://localhost:$port$(Genie.Assets.asset_path(assets_config, :js, file = "stipplecore"))") +# down() +# end +# PRECOMPILE[] = false +# end end diff --git a/src/Theme.jl b/src/Theme.jl index 921f722..2ae463c 100644 --- a/src/Theme.jl +++ b/src/Theme.jl @@ -39,12 +39,13 @@ end """ Get the current theme. """ -function set_theme(theme::Symbol) +function set_theme(theme::Symbol) :: Bool if haskey(get_themes(), theme) CURRENT_THEME[] = theme - else - error("Theme not found: $theme") - end + return true + end + + return false end @@ -68,7 +69,7 @@ end Unregister a theme. """ function unregister_theme(name::Symbol) - theme_exists!(name) && delete!(get_themes(), name) + theme_exists(name) && delete!(get_themes(), name) end @@ -80,23 +81,11 @@ function theme_exists(theme::Symbol) end -""" -Check if a theme exists, and throw an error if it doesn't. -""" -function theme_exists!(theme::Symbol) - if ! theme_exists(theme) - error("Theme not found: $theme") - end - - return true -end - - """ Get the URL or path to the theme's stylesheet. """ function to_path(theme::Symbol = CURRENT_THEME[]) :: String - theme_path = theme_exists!(theme) && get_themes()[theme] + theme_path = theme_exists(theme) ? get_themes()[theme] : return "" return if startswith(theme_path, "http://") || startswith(theme_path, "https://") # external URL theme_path @@ -112,7 +101,7 @@ end Get the current theme as a stylesheet to be loaded into Stipple.Layout.THEMES[] """ function to_asset(theme::Symbol = CURRENT_THEME[]) :: Function - theme_path = theme_exists!(theme) && get_themes()[theme] + theme_path = theme_exists(theme) ? get_themes()[theme] : return () -> "" return if startswith(theme_path, "http://") || startswith(theme_path, "https://") || startswith(theme_path, "/") () -> stylesheet(theme_path)