Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement browser display #76

Merged
merged 6 commits into from Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
@@ -0,0 +1,48 @@
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
tags: '*'
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
- 'nightly'
os:
- ubuntu-latest
arch:
- x64
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- name: Run headless test
uses: GabrielBB/xvfb-action@v1
with:
run: julia --project=@. -e "using Pkg; Pkg.test(coverage=true)"
- uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
file: lcov.info
19 changes: 0 additions & 19 deletions .travis.yml

This file was deleted.

7 changes: 4 additions & 3 deletions README.md
@@ -1,14 +1,15 @@
# JSServe

[![Build Status](https://travis-ci.com/SimonDanisch/JSServe.jl.svg?branch=master)](https://travis-ci.com/SimonDanisch/JSServe.jl)
[![Build Status](https://ci.appveyor.com/api/projects/status/github/SimonDanisch/JSServe.jl?svg=true)](https://ci.appveyor.com/project/SimonDanisch/JSServe-jl)

![CI](https://github.com/SimonDanisch/JSServe.jl/workflows/CI/badge.svg)

[![Codecov](https://codecov.io/gh/SimonDanisch/JSServe.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/SimonDanisch/JSServe.jl)
[![Build Status](https://travis-ci.com/SimonDanisch/JSServe.jl.svg?branch=master)](https://travis-ci.com/SimonDanisch/JSServe.jl)

Easy way of building interactive applications from Julia.
Uses Hyperscript to create HTML descriptions, and allows to execute Javascript & building of widgets. It also supports an offline mode, that exports your interactive app to a folder, and optionally records a statemap for all UI elements, so that a running Julia process isn't necessary anymore.

Have a look at the [examples](https://github.com/SimonDanisch/JSServe.jl/tree/master/examples), or check out the most outstanding ones:
Have a look at the [examples](https://github.com/SimonDanisch/JSServe.jl/tree/master/examples), or check out the most outstanding ones:
## Markdown support
https://github.com/SimonDanisch/JSServe.jl/blob/master/examples/markdown.jl
![markdown_vol](https://user-images.githubusercontent.com/1010467/88916397-48513480-d266-11ea-8741-c5246f7f2395.gif)
Expand Down
17 changes: 17 additions & 0 deletions src/JSServe.jl
Expand Up @@ -18,6 +18,8 @@ import AbstractTrees
using Colors
using LinearAlgebra

struct BrowserDisplay <: Base.Multimedia.AbstractDisplay end

include("types.jl")
include("js_source.jl")
include("session.jl")
Expand Down Expand Up @@ -52,6 +54,16 @@ const JSSERVE_CONFIGURATION = (
verbose = Ref(false)
)

function has_html_display()
for display in Base.Multimedia.displays
# Ugh, why would textdisplay say it supports HTML??
display isa TextDisplay && continue
displayable(display, MIME"text/html"()) && return true
end
return false
end


function __init__()
url = if haskey(ENV, "JULIA_WEBIO_BASEURL")
ENV["JULIA_WEBIO_BASEURL"]
Expand All @@ -72,7 +84,12 @@ function __init__()
# gets closed
rm(dependency_path("session_temp_data"), recursive=true, force=true)
end

start_gc_task()

if !has_html_display()
push!(Base.Multimedia.displays, BrowserDisplay())
end
end


Expand Down
46 changes: 43 additions & 3 deletions src/display.jl
Expand Up @@ -18,7 +18,6 @@ end
const WebMimes = (
MIME"text/html",
MIME"application/prs.juno.plotpane+html",
# MIME"application/vnd.webio.application+html"
)

function get_global_app()
Expand Down Expand Up @@ -113,7 +112,7 @@ for M in WebMimes
end
end

function Base.show(io::IO, m::MIME"application/vnd.webio.application+html", dom::DisplayInline)
function Base.show(io::IO, ::MIME"application/vnd.webio.application+html", dom::DisplayInline)
application = get_global_app()
session = Session()
application.sessions[session.id] = session
Expand Down Expand Up @@ -222,6 +221,47 @@ function Base.show(io::IOContext, m::MIME"application/vnd.jsserve.application+ht
end
end

function Base.show(io::IO, m::MIME"application/vnd.jsserve.application+html", dom::DisplayInline)
function Base.show(io::IO, ::MIME"application/vnd.jsserve.application+html", dom::DisplayInline)
show(io.io, MIME"text/html"(), dom)
end

function Base.show(io::IO, ::MIME"juliavscode/html", dom::DisplayInline)
show(io.io, MIME"text/html"(), dom)
end

function openurl(url::String)
if Sys.isapple()
success(`open $url`) && return
elseif Sys.iswindows()
success(`powershell.exe start $url`) && return
elseif Sys.isunix()
success(`xdg-open $url`) && return
success(`gnome-open $url`) && return
end
success(`python -mwebbrowser $(url)`) && return
# our last hope
success(`python3 -mwebbrowser $(url)`) && return
@warn("Can't find a way to open a browser, open $(url) manually!")
end

function Base.display(::BrowserDisplay, dom::DisplayInline)
application = get_global_app()
session = Session()
session_url = "/browser-display"
route_was_present = route!(application, session_url) do context
# Serve the actual content
application = context.application
application.sessions[session.id] = session
html_dom = Base.invokelatest(dom.dom_function, session, context.request)
return html(dom2html(session, html_dom))
end
# Only open url first time!
if isempty(application.sessions)
openurl(local_url(application, session_url))
else
for (id, session) in application.sessions
evaljs(session, js"location.reload(true)")
end
end
return session
end
5 changes: 3 additions & 2 deletions src/http.jl
Expand Up @@ -102,7 +102,7 @@ function wait_timeout(condition::Function, error_msg::String, timeout = 5.0)
return
end

function handle_ws_connection(session::Session, websocket::WebSocket)
function handle_ws_connection(application::Application, session::Session, websocket::WebSocket)
# TODO, do we actually need to wait here?!
wait_timeout(()-> isopen(websocket), "Websocket not open after waiting 5s")
while isopen(websocket)
Expand All @@ -118,6 +118,7 @@ function handle_ws_connection(session::Session, websocket::WebSocket)
end
end
close(session)
delete!(application.sessions, session.id)
end

"""
Expand All @@ -132,7 +133,7 @@ function websocket_handler(context, websocket::WebSocket)
session = application.sessions[sessionid]
# We can have multiple sessions for a client
push!(session, websocket)
handle_ws_connection(session, websocket)
handle_ws_connection(application, session, websocket)
else
# This happens when an old session trys to reconnect to a new app
# We somehow need to figure out better, how to recognize this
Expand Down
4 changes: 2 additions & 2 deletions src/types.jl
Expand Up @@ -210,7 +210,8 @@ function Base.setindex!(routes::Routes, f, pattern)
end
# Sort for priority so that exact string matches come first
sort!(routes.table, by = pattern_priority)
return
# return if it was inside already!
return idx === nothing
end

function apply_handler(f, args...)
Expand Down Expand Up @@ -315,7 +316,6 @@ function warmup(application::Application)
try
@async stream_handler(application, stream)
write(stream, "blaaa")

catch e
# TODO make it not error so we can test this properly
# This will error, since its not a propper websocket request
Expand Down