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 |