| name = "JLD2View" | |||||
| uuid = "a494b24a-6d3b-4871-b66b-22839c4c06d4" | |||||
| authors = ["francois "] | |||||
| version = "0.1.0" | |||||
| [deps] | |||||
| Electron = "a1bb12fb-d4d1-54b4-b10a-ee7951ef7ad3" | |||||
| JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" | |||||
| Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" | |||||
| NativeFileDialog = "e1fe445b-aa65-4df4-81c1-2041507f0fd4" | |||||
| Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" |
| [deps] | |||||
| PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" | |||||
| Scratch = "6c6a2e73-6563-6170-7368-637461726353" |
| using Pkg; Pkg.activate(@__DIR__); Pkg.instantiate() | |||||
| using PackageCompiler | |||||
| using Scratch | |||||
| Pkg.activate(joinpath(@__DIR__, "..")) | |||||
| using JLD2View | |||||
| scratchdir = get_scratch!(JLD2View, "sysimages") | |||||
| # List of packages to include in the sysimage | |||||
| packages = :JLD2View #Symbol.(keys(Pkg.project().dependencies)) | |||||
| # Sysimage base name | |||||
| sysimage_name = "sysimage" | |||||
| # Sysimage extension | |||||
| sysimage_ext = if Sys.iswindows() | |||||
| ".dll" | |||||
| elseif Sys.isapple() | |||||
| ".dylib" | |||||
| else | |||||
| ".so" | |||||
| end | |||||
| sysimage_path = joinpath(scratchdir, sysimage_name * sysimage_ext) | |||||
| @info("Creating system image", | |||||
| name=sysimage_path, | |||||
| packages) | |||||
| create_sysimage( | |||||
| packages, | |||||
| sysimage_path = sysimage_path, | |||||
| precompile_execution_file = joinpath(@__DIR__, "precomp.jl"), | |||||
| # Optional: generate a "portable" sysimage on x86-64 architectures | |||||
| cpu_target = "generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1)", | |||||
| ) |
| using JLD2 | |||||
| using JLD2View | |||||
| using JLD2View: send_signal | |||||
| mktempdir() do path | |||||
| d = Dict("int" => 1, | |||||
| "float" => 2.0, | |||||
| "vector" => rand(1000), | |||||
| "matrix" => rand(1000, 1000)) | |||||
| d["dict"] = deepcopy(d) | |||||
| d["array"] = [deepcopy(d), deepcopy(d)] | |||||
| fname = joinpath(path, "tmp.jld2") | |||||
| JLD2.save(fname, d) | |||||
| (viewer, task) = jld2_view(fname, sync=false) | |||||
| sleep(1) | |||||
| function simulate(action, arg) | |||||
| println("Simulating action: $action $arg") | |||||
| send_signal(viewer, action, arg) | |||||
| sleep(2) | |||||
| end | |||||
| simulate(:select_key, "int") | |||||
| simulate(:select_key, "float") | |||||
| simulate(:select_key, "vector"); simulate(:back, 0) | |||||
| simulate(:select_key, "matrix"); simulate(:back, 0) | |||||
| simulate(:select_key, "dict"); begin | |||||
| simulate(:select_key, "vector"); simulate(:back, 1) | |||||
| simulate(:select_key, "matrix"); simulate(:back, 1) | |||||
| end; simulate(:back, 0) | |||||
| simulate(:select_key, "array"); begin | |||||
| simulate(:select_key, 1); simulate(:back, 1) | |||||
| end; simulate(:back, 0) | |||||
| println("Terminating event loop") | |||||
| schedule(task, :stop, error=true) | |||||
| wait(task) | |||||
| end |
| import Pkg; Pkg.activate(@__DIR__, io=IOBuffer()) | |||||
| using JLD2View | |||||
| using NativeFileDialog | |||||
| path = get(ARGS, 1) do | |||||
| pick_file(filterlist="jld2") | |||||
| end | |||||
| const HELP = """ | |||||
| Usage: | |||||
| jld2view [JLD2_PATH] | |||||
| Display the contents of a JLD2 file. If no file is provided in the command line, | |||||
| a file-picker dialog box will appear to select one interactively. | |||||
| """ | |||||
| if path == "--help" | |||||
| println(HELP) | |||||
| exit(0) | |||||
| elseif path == "" # pick_file cancelled | |||||
| exit(0) | |||||
| elseif isfile(path) | |||||
| jld2_view(path) | |||||
| else | |||||
| @error "File not found" path | |||||
| println("\n", HELP) | |||||
| exit(1) | |||||
| end |
| <html> | |||||
| <head> | |||||
| <title>JLD2view</title> | |||||
| <style>{{:STYLE}}</style> | |||||
| </head> | |||||
| <body> | |||||
| Loading file `{{:FNAME}}`... | |||||
| <script> | |||||
| function showLoading() { | |||||
| true; | |||||
| } | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| // Author: Jared Goodwin | |||||
| // showLoading() - Display loading wheel. | |||||
| // Requires ECMAScript 6 (any modern browser). | |||||
| function showLoading() { | |||||
| if (document.getElementById("divLoadingFrame") != null) { | |||||
| return; | |||||
| } | |||||
| var style = document.createElement("style"); | |||||
| style.id = "styleLoadingWindow"; | |||||
| style.innerHTML = ` | |||||
| .loading-frame { | |||||
| position: fixed; | |||||
| background-color: rgba(0, 0, 0, 0.5); | |||||
| left: 0; | |||||
| top: 0; | |||||
| right: 0; | |||||
| bottom: 0; | |||||
| z-index: 4; | |||||
| } | |||||
| .loading-track { | |||||
| height: 50px; | |||||
| display: inline-block; | |||||
| position: absolute; | |||||
| top: calc(50% - 50px); | |||||
| left: 50%; | |||||
| } | |||||
| .loading-dot { | |||||
| height: 5px; | |||||
| width: 5px; | |||||
| background-color: white; | |||||
| border-radius: 100%; | |||||
| opacity: 0; | |||||
| } | |||||
| .loading-dot-animated { | |||||
| animation-name: loading-dot-animated; | |||||
| animation-direction: alternate; | |||||
| animation-duration: .75s; | |||||
| animation-iteration-count: infinite; | |||||
| animation-timing-function: ease-in-out; | |||||
| } | |||||
| @keyframes loading-dot-animated { | |||||
| from { | |||||
| opacity: 0; | |||||
| } | |||||
| to { | |||||
| opacity: 1; | |||||
| } | |||||
| } | |||||
| ` | |||||
| document.body.appendChild(style); | |||||
| var frame = document.createElement("div"); | |||||
| frame.id = "divLoadingFrame"; | |||||
| frame.classList.add("loading-frame"); | |||||
| for (var i = 0; i < 10; i++) { | |||||
| var track = document.createElement("div"); | |||||
| track.classList.add("loading-track"); | |||||
| var dot = document.createElement("div"); | |||||
| dot.classList.add("loading-dot"); | |||||
| track.style.transform = "rotate(" + String(i * 36) + "deg)"; | |||||
| track.appendChild(dot); | |||||
| frame.appendChild(track); | |||||
| } | |||||
| document.body.appendChild(frame); | |||||
| var wait = 0; | |||||
| var dots = document.getElementsByClassName("loading-dot"); | |||||
| for (var i = 0; i < dots.length; i++) { | |||||
| window.setTimeout(function(dot) { | |||||
| dot.classList.add("loading-dot-animated"); | |||||
| }, wait, dots[i]); | |||||
| wait += 150; | |||||
| } | |||||
| } |
| #jld2 { | |||||
| padding-left: 1em; | |||||
| padding-top: 0.5em; | |||||
| } | |||||
| #jld2 td { | |||||
| padding-top: 0.25em; | |||||
| padding-bottom: 0.25em; | |||||
| padding-left: 0.5em; | |||||
| padding-right: 0.5em; | |||||
| } | |||||
| #jld2 tr { | |||||
| cursor: pointer; | |||||
| } | |||||
| #jld2 tr:hover td { | |||||
| background: lightblue; | |||||
| } | |||||
| #warning { | |||||
| padding: 0.75em 1em; | |||||
| border-radius: 4px; | |||||
| border-style: solid; | |||||
| border-width: 1px; | |||||
| margin-top: 0.5em; | |||||
| background-color: rgba(252, 248, 227, 1); | |||||
| border-color: rgba(177, 161, 129, 1); | |||||
| color: rgba(138, 109, 59, 1); | |||||
| } | |||||
| a { | |||||
| text-decoration: none; | |||||
| color: black; | |||||
| } | |||||
| .key { | |||||
| font-weight: bold; | |||||
| font-family: monospace; | |||||
| } | |||||
| .value { | |||||
| font-family: monospace; | |||||
| } |
| <html> | |||||
| <head> | |||||
| <title>JLD2view</title> | |||||
| <style>{{{:STYLE}}}</style> | |||||
| <script>{{{:SPIN_WHL}}}</script> | |||||
| </head> | |||||
| <body> | |||||
| <a class="key" href="#" onclick='sendMessageToJulia("back|0")'>[{{:FNAME}}]</a> | |||||
| {{#:PATH}} | |||||
| ⊳ <a class="key" href="#" onclick='sendMessageToJulia("back|{{:idx}}")'>{{:name}}</a> | |||||
| {{/:PATH}} | |||||
| {{#:WARNING}} | |||||
| <div id="warning"><b>WARNING:</b> {{:text}}</div> | |||||
| {{/:WARNING}} | |||||
| <table id="jld2" cellspacing="0"> | |||||
| {{#:DATA}} | |||||
| <tr onclick='sendMessageToJulia("select|{{:idx}}")'> | |||||
| <td><span class="key">⊳ {{:key}}</span></td> | |||||
| <td><span class="value">{{:val}}</span></td> | |||||
| </tr> | |||||
| {{/:DATA}} | |||||
| </table> | |||||
| </body> | |||||
| </html> |
| [deps] | |||||
| JLD2View = "a494b24a-6d3b-4871-b66b-22839c4c06d4" | |||||
| Scratch = "6c6a2e73-6563-6170-7368-637461726353" |
| #!/bin/bash | |||||
| #= | |||||
| exec julia --color=no --startup-file=no --compile=min --optimize=0 "${BASH_SOURCE[0]}" "$@" | |||||
| =# | |||||
| using Pkg | |||||
| Pkg.activate(@__DIR__, io=IOBuffer()) | |||||
| using Scratch | |||||
| julia = Base.julia_cmd()[1] | |||||
| script = joinpath(@__DIR__, "..", "main.jl") | |||||
| args = String["--compile=min", "--optimize=0"] | |||||
| scratchdir = get_scratch!(Pkg.project().dependencies["JLD2View"], "sysimages") | |||||
| sysimage = joinpath(scratchdir, "sysimage.so") | |||||
| if (ispath(sysimage)) | |||||
| push!(args, "--sysimage=$sysimage") | |||||
| end | |||||
| cmd = `$julia $args $script $ARGS` | |||||
| try | |||||
| run(cmd) | |||||
| catch e | |||||
| process = first(e.procs) | |||||
| exit(process.exitcode) | |||||
| end | |||||
| # Local Variables: | |||||
| # mode: julia | |||||
| # End: |
| module JLD2View | |||||
| using Electron | |||||
| using Mustache | |||||
| using JLD2 | |||||
| using Printf | |||||
| export jld2_view | |||||
| function pretty_repr(x) | |||||
| io = IOBuffer() | |||||
| show(IOContext(io, :compact=>true, :limit=>true), "text/plain", x) | |||||
| String(take!(io)) | |||||
| end | |||||
| function pretty_repr(x::Vector) | |||||
| n = length(x) | |||||
| if n < 10 && all(e isa Number for e in x) | |||||
| "[" * join((pretty_repr(e) for e in x), ", ") * "]" | |||||
| else | |||||
| "<$n-element Vector>" | |||||
| end | |||||
| end | |||||
| function pretty_repr(x::Matrix) | |||||
| m, n = size(x) | |||||
| "<$m×$n Matrix>" | |||||
| end | |||||
| function pretty_repr(x::Dict) | |||||
| n = length(keys(x)) | |||||
| "<$n-entry Dict>" | |||||
| end | |||||
| const STYLE = String(read( joinpath(@__DIR__, "..", "resources", "style.css"))) | |||||
| const SPIN_WHL = String(read( joinpath(@__DIR__, "..", "resources", "spinning_wheel.js"))) | |||||
| const PAGE_LOAD = Mustache.load(joinpath(@__DIR__, "..", "resources", "load.html")) | |||||
| const PAGE_VIEW = Mustache.load(joinpath(@__DIR__, "..", "resources", "view.html")) | |||||
| mutable struct Viewer | |||||
| app | |||||
| win | |||||
| fname | |||||
| data | |||||
| path | |||||
| keys | |||||
| function Viewer(path) | |||||
| viewer = new() | |||||
| viewer.fname = last(splitpath(path)) | |||||
| create_win!(viewer) | |||||
| @info "Loading JLD2 file" path | |||||
| @time viewer.data = JLD2.load(path) | |||||
| update!(viewer, ()) | |||||
| end | |||||
| end | |||||
| function get_data(viewer, path=viewer.path) | |||||
| f(dict, ::Tuple{}) = dict | |||||
| f(dict, path) = f(dict[first(path)], Base.tail(path)) | |||||
| return f(viewer.data, path) | |||||
| end | |||||
| function create_win!(viewer) | |||||
| viewer.app = Application() | |||||
| viewer.win = Window(viewer.app, render(PAGE_LOAD, STYLE=STYLE, FNAME=viewer.fname)) | |||||
| end | |||||
| function update!(viewer, path) | |||||
| Electron.run(viewer.win, "setTimeout(showLoading, 100)") | |||||
| viewer.path = path | |||||
| @info "Refreshing view" path = join(("⌂", path...), " ⊳ ") | |||||
| @time begin | |||||
| oldpath = path | |||||
| warning = nothing | |||||
| keys = Base.keys(get_data(viewer)) | |||||
| N = 1000 | |||||
| if length(keys) > N | |||||
| warning = (; text="showing only the first $N entries (out of $(length(keys)))") | |||||
| keys = Iterators.take(keys, N) | |||||
| end | |||||
| viewer.keys = keys = collect(keys) | |||||
| values = map(keys) do key | |||||
| pretty_repr(get_data(viewer, (path..., key))) | |||||
| end | |||||
| data = [(idx=i, key=keys[i], val=values[i]) for i in eachindex(keys)] | |||||
| html = render(PAGE_VIEW; STYLE, SPIN_WHL, | |||||
| DATA = data, | |||||
| FNAME = viewer.fname, | |||||
| PATH = [(idx=i, name=x) for (i,x) in enumerate(path)], | |||||
| WARNING = warning) | |||||
| open("index.html", "w") do f | |||||
| write(f, html) | |||||
| end | |||||
| Electron.load(viewer.win, html) | |||||
| end | |||||
| return viewer | |||||
| end | |||||
| function run!(viewer) | |||||
| if !viewer.app.exists | |||||
| @warn "Window does not exist any more. Creating a new one." | |||||
| create_win!(viewer) | |||||
| update!(viewer, ()) | |||||
| end | |||||
| ch = msgchannel(viewer.win) | |||||
| try | |||||
| while true | |||||
| msg = try | |||||
| take!(ch) | |||||
| catch e | |||||
| if isa(e, InvalidStateException) && e.state === :closed | |||||
| @info "Window closed. Terminating" | |||||
| break | |||||
| else | |||||
| rethrow() | |||||
| end | |||||
| end | |||||
| action, arg = split(msg, "|") | |||||
| arg = parse(Int, arg) | |||||
| path = if action == "back" | |||||
| viewer.path[1:arg] | |||||
| elseif action == "select" | |||||
| (viewer.path..., viewer.keys[arg]) | |||||
| end | |||||
| if any(typeof(get_data(viewer, path)) .<: (Dict, AbstractArray)) | |||||
| update!(viewer, path) | |||||
| end | |||||
| end | |||||
| finally | |||||
| close(viewer.app) | |||||
| sleep(0.1) | |||||
| end | |||||
| end | |||||
| send_signal(viewer, action::Symbol, args...) = send_signal(viewer, Val(action), args...) | |||||
| function send_signal(viewer, ::Val{:back}, idx::Integer) | |||||
| Electron.run(viewer.win, "sendMessageToJulia('back|$idx')") | |||||
| end | |||||
| function send_signal(viewer, ::Val{:select}, idx::Integer) | |||||
| Electron.run(viewer.win, "sendMessageToJulia('select|$idx')") | |||||
| end | |||||
| function send_signal(viewer, ::Val{:select_key}, key) | |||||
| idx = findfirst(==(key), viewer.keys) | |||||
| @assert !isnothing(idx) | |||||
| send_signal(viewer, :select, idx) | |||||
| end | |||||
| function jld2_view(fname; sync=true) | |||||
| viewer = Viewer(fname) | |||||
| task = @async try | |||||
| run!(viewer) | |||||
| catch e | |||||
| e == :stop || rethrow() | |||||
| end | |||||
| sync && wait(task) | |||||
| return (viewer, task) | |||||
| end | |||||
| function install() | |||||
| julia = first(Base.julia_cmd()) | |||||
| make = joinpath(@__DIR__, "..", "build", "make.jl") | |||||
| run(`$julia $make`) | |||||
| end | |||||
| end # module |