Skip to content

Instantly share code, notes, and snippets.

@dralletje
Last active June 29, 2021 10:53
Show Gist options
  • Save dralletje/131eff9e90703346ca7db09ebcec223f to your computer and use it in GitHub Desktop.
Save dralletje/131eff9e90703346ca7db09ebcec223f to your computer and use it in GitHub Desktop.
### A Pluto.jl notebook ###
# v0.14.0
using Markdown
using InteractiveUtils
# ╔═╡ 46b1e7b2-8460-11eb-3454-f7fa9542f903
begin
import Pkg
Pkg.add([
"Dash",
"DashCoreComponents",
"DashHtmlComponents",
"DashTable",
"HTTP",
])
end
# ╔═╡ b6963f48-81ce-4332-94b7-acc5740376be
using Dash
# ╔═╡ 7209c5f8-47f8-4d64-89e2-b08e121eb227
using DashHtmlComponents
# ╔═╡ 007a8782-2487-41bc-82cc-c26ef7e6a6f9
using DashCoreComponents
# ╔═╡ c8f8d722-96a9-49b3-a566-58f73dc1f4c4
md"""
# Example of Dash app in Pluto
It just creates a server and then proxies requests through Pluto.
In the end I want to render individual components directly in your cell.
It is also still a bit jittery when you refresh.
"""
# ╔═╡ 17f86bb0-4960-4980-9ba6-2f3ada6ceee3
md"## Appendix"
# ╔═╡ b53711ab-7d12-40f1-8457-146b665ea9ca
md"### Packages"
# ╔═╡ db3b4012-52b8-4711-b81d-7dc54f914add
import HTTP
# ╔═╡ fd894b9d-0844-4627-a833-e79c9355972c
md"### Dash setup"
# ╔═╡ 2685e3f7-2c70-4e1f-a6ae-bdce5e07fedb
"I still have to make it so that the port isn't awkward"
PORT = 8086
# ╔═╡ c250c492-ad8d-4e59-8520-0c3a86135e69
base_url = "/integrations/Dash/$(PlutoRunner.workspace_info.notebook_id)/$(PORT)/"
# base_url = "./" * PlutoRunner.IntegrationsWithOtherPackages.get_base_url()[3:end] * "/$(PORT)/"
# ╔═╡ 74a80b9f-663d-4fa4-b1d6-56db206cb167
HTML("""
<a href="$(base_url)" target="_blank">Open app in seperate tab</a>
""")
# ╔═╡ 4e12b92f-c372-4628-9c9d-dc3568635611
PREVIOUS_RESULTS = Dict{Any,Any}()
# ╔═╡ 265c7672-980e-4bcf-a07d-375a4a35ca49
function make_dash_app(make_layout)
app = dash(
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"],
url_base_pathname=base_url,
)
app.layout = make_layout()
app
end
# ╔═╡ 0a8ed9fd-eb72-420f-93ff-8368cf0fc0e8
begin
app = make_dash_app() do
html_div() do
dcc_input(id="graphTitle", value="Let's Dance!!", type = "text"),
html_div(id="outputID"),
dcc_graph(id="graph",
figure = (
data = [(x = [1,2,3], y = [3,2,8], type="bar")],
layout = Dict(:title => "Graph")
)
)
end
end
callback!(app, Output("outputID", "children"), Input("graphTitle","value"), State("graphTitle","type")) do value, type
@info "SAY WHAAT" value
"You've entered: '$(value)' into a '$(type)' input control"
end
callback!(app, Output("graph", "figure"), Input("graphTitle", "value")) do value
(
data = [
(x = [1,2,3], y = abs.(randn(3)), type="bar"),
(x = [1,2,3], y = abs.(randn(3)), type="scatter", mode = "lines+markers", line = (width = 4,))
],
layout = (title = value,)
)
end
end;
# ╔═╡ 1eb65acd-3385-4f7c-9e05-d4af5456c3cf
server_task = PREVIOUS_RESULTS["dash_server_task"] = begin
# Ideally I want @cell_id() here or something
# This closes the server, but only if we are starting a new one.
# This is not a very reliable technique
if haskey(PREVIOUS_RESULTS, "dash_server_task")
try
local previous_task = PREVIOUS_RESULTS["dash_server_task"]
schedule(previous_task, InterruptException(), error=true)
catch e
end
end
@async begin
println("Startng dash server @ http://127.0.0.1:$(PORT)")
local server = run_server(app, "127.0.0.1", PORT)
end
end
# ╔═╡ ad6afe08-b802-4b38-80af-0cd343d12a41
server_task; HTML("""
<div style="position: relative">
<div
id="loading"
style="
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255,255,255);
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
"
><span>Loading</span></div>
<iframe src="$(base_url)" style="border: none; width: 100%;"></iframe>
</div>
<script>
let \$iframe = currentScript.closest('pluto-output').querySelector('iframe')
let \$loading = currentScript.closest('pluto-output').querySelector('#loading')
// Code below here is borrowed from CellOutput.js in Pluto
let iframeref = {
current: \$iframe,
}
await new Promise((resolve) => iframeref.current.addEventListener("load", () => resolve()))
let iframeDocument = iframeref.current.contentWindow.document
// Insert iframe resizer inside the iframe
let x = iframeDocument.createElement("script")
x.src = "https://cdn.jsdelivr.net/npm/iframe-resizer@4.2.11/js/iframeResizer.contentWindow.min.js"
x.integrity = "sha256-EH+7IdRixWtW5tdBwMkTXL+HvW5tAqV4of/HbAZ7nEc="
x.crossOrigin = "anonymous"
iframeDocument.head.appendChild(x)
// Apply iframe resizer from the host side
new Promise((resolve) => x.addEventListener("load", () => resolve()))
// @ts-ignore
window.iFrameResize({ checkOrigin: false }, iframeref.current)
// End of borrowed code
\$loading.style.display = "none"
</script>
""")
# ╔═╡ 084ebaf4-ba36-413c-b409-35e25b7b9ca5
"""
For Dash I am just running the Dash server as normal, and proxying all the requests 🙃
This works remarkably well.
"""
function PlutoRunner.IntegrationsWithOtherPackages.on_request(::Val{:Dash}, request)
_1, _2, _3, _4, port = split(request[:target], "/")
url = "http://localhost:$(port)$(request[:target])"
@info "request[:method]" request[:method]
response = HTTP.request(
request[:method],
url,
[],
request[:body],
)
return Dict(
:status => Int64(response.status),
:headers => response.headers,
:body => response.body,
)
end
# ╔═╡ Cell order:
# ╟─c8f8d722-96a9-49b3-a566-58f73dc1f4c4
# ╟─74a80b9f-663d-4fa4-b1d6-56db206cb167
# ╟─ad6afe08-b802-4b38-80af-0cd343d12a41
# ╠═0a8ed9fd-eb72-420f-93ff-8368cf0fc0e8
# ╟─17f86bb0-4960-4980-9ba6-2f3ada6ceee3
# ╟─b53711ab-7d12-40f1-8457-146b665ea9ca
# ╠═46b1e7b2-8460-11eb-3454-f7fa9542f903
# ╠═db3b4012-52b8-4711-b81d-7dc54f914add
# ╠═b6963f48-81ce-4332-94b7-acc5740376be
# ╠═7209c5f8-47f8-4d64-89e2-b08e121eb227
# ╠═007a8782-2487-41bc-82cc-c26ef7e6a6f9
# ╟─fd894b9d-0844-4627-a833-e79c9355972c
# ╠═2685e3f7-2c70-4e1f-a6ae-bdce5e07fedb
# ╠═c250c492-ad8d-4e59-8520-0c3a86135e69
# ╟─4e12b92f-c372-4628-9c9d-dc3568635611
# ╠═1eb65acd-3385-4f7c-9e05-d4af5456c3cf
# ╠═265c7672-980e-4bcf-a07d-375a4a35ca49
# ╠═084ebaf4-ba36-413c-b409-35e25b7b9ca5
@fonsp
Copy link

fonsp commented Mar 16, 2021

I added Pkg.activate and matched a variable rename that i did to your PR:

### A Pluto.jl notebook ###
# v0.14.0

using Markdown
using InteractiveUtils

# ╔═╡ 46b1e7b2-8460-11eb-3454-f7fa9542f903
begin
	import Pkg
	Pkg.activate(mktempdir())
	Pkg.add([
		"Dash",
		"DashCoreComponents",
		"DashHtmlComponents",
		"DashTable",
		"HTTP",
	])
end

# ╔═╡ b6963f48-81ce-4332-94b7-acc5740376be
using Dash

# ╔═╡ 7209c5f8-47f8-4d64-89e2-b08e121eb227
using DashHtmlComponents

# ╔═╡ 007a8782-2487-41bc-82cc-c26ef7e6a6f9
using DashCoreComponents

# ╔═╡ c8f8d722-96a9-49b3-a566-58f73dc1f4c4
md"""
# Example of Dash app in Pluto

It just creates a server and then proxies requests through Pluto.
In the end I want to render individual components directly in your cell.
It is also still a bit jittery when you refresh.
"""

# ╔═╡ 17f86bb0-4960-4980-9ba6-2f3ada6ceee3
md"## Appendix"

# ╔═╡ b53711ab-7d12-40f1-8457-146b665ea9ca
md"### Packages"

# ╔═╡ db3b4012-52b8-4711-b81d-7dc54f914add
import HTTP

# ╔═╡ fd894b9d-0844-4627-a833-e79c9355972c
md"### Dash setup"

# ╔═╡ 2685e3f7-2c70-4e1f-a6ae-bdce5e07fedb
"I still have to make it so that the port isn't awkward"
PORT = 8086

# ╔═╡ c250c492-ad8d-4e59-8520-0c3a86135e69
base_url = "/integrations/Dash/$(PlutoRunner.workspace_info.notebook_id)/$(PORT)/"
# base_url = "./" * PlutoRunner.IntegrationsWithOtherPackages.get_base_url()[3:end] * "/$(PORT)/"

# ╔═╡ 74a80b9f-663d-4fa4-b1d6-56db206cb167
HTML("""
<a href="$(base_url)" target="_blank">Open app in seperate tab</a>
""")

# ╔═╡ 4e12b92f-c372-4628-9c9d-dc3568635611
PREVIOUS_RESULTS = Dict{Any,Any}()

# ╔═╡ 265c7672-980e-4bcf-a07d-375a4a35ca49
function make_dash_app(make_layout)
	app = dash(
		external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"],
		url_base_pathname=base_url,
	)
	app.layout = make_layout()
	app
end

# ╔═╡ 0a8ed9fd-eb72-420f-93ff-8368cf0fc0e8
begin
	app = make_dash_app() do
		html_div() do
			dcc_input(id="graphTitle", value="Let's Dance!!", type = "text"),
			html_div(id="outputID"),
			dcc_graph(id="graph",
				figure = (
					data = [(x = [1,2,3], y = [3,2,8], type="bar")],
					layout = Dict(:title => "Graph")
				)
			)
		end
	end
	callback!(app, Output("outputID", "children"), Input("graphTitle","value"), State("graphTitle","type")) do value, type
		@info "SAY WHAAT" value
		"You've entered: '$(value)' into a '$(type)' input control"
	end
	callback!(app, Output("graph", "figure"), Input("graphTitle", "value")) do value
		(
			data = [
				(x = [1,2,3], y = abs.(randn(3)), type="bar"),
				(x = [1,2,3], y = abs.(randn(3)), type="scatter", mode = "lines+markers", line = (width = 4,))
			],
			layout = (title = value,)
		)
	end
end;

# ╔═╡ 1eb65acd-3385-4f7c-9e05-d4af5456c3cf
server_task = PREVIOUS_RESULTS["dash_server_task"] = begin	
	# Ideally I want @cell_id() here or something
	# This closes the server, but only if we are starting a new one.
	# This is not a very reliable technique
	if haskey(PREVIOUS_RESULTS, "dash_server_task")
		try
			local previous_task = PREVIOUS_RESULTS["dash_server_task"]
			schedule(previous_task, InterruptException(), error=true)
		catch e
		end
	end
	
	@async begin
		println("Startng dash server @ http://127.0.0.1:$(PORT)")
		local server = run_server(app, "127.0.0.1", PORT)
	end
end

# ╔═╡ ad6afe08-b802-4b38-80af-0cd343d12a41
server_task; HTML("""
<div style="position: relative">
	<div
		id="loading"
		style="
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background-color: rgba(255,255,255);
	
			display: flex;
			align-items: center;
			justify-content: center;
			font-size: 32px;
		"
	><span>Loading</span></div>
	<iframe src="$(base_url)" style="border: none; width: 100%;"></iframe>
</div>
	
<script>
let \$iframe = currentScript.closest('pluto-output').querySelector('iframe')
let \$loading = currentScript.closest('pluto-output').querySelector('#loading')
	
// Code below here is borrowed from CellOutput.js in Pluto
let iframeref = {
	current: \$iframe,
}
await new Promise((resolve) => iframeref.current.addEventListener("load", () => resolve()))
let iframeDocument = iframeref.current.contentWindow.document

// Insert iframe resizer inside the iframe
let x = iframeDocument.createElement("script")
x.src = "https://cdn.jsdelivr.net/npm/iframe-resizer@4.2.11/js/iframeResizer.contentWindow.min.js"
x.integrity = "sha256-EH+7IdRixWtW5tdBwMkTXL+HvW5tAqV4of/HbAZ7nEc="
x.crossOrigin = "anonymous"
iframeDocument.head.appendChild(x)

// Apply iframe resizer from the host side
new Promise((resolve) => x.addEventListener("load", () => resolve()))
// @ts-ignore
window.iFrameResize({ checkOrigin: false }, iframeref.current)
// End of borrowed code
	
\$loading.style.display = "none"
</script>
""")

# ╔═╡ 084ebaf4-ba36-413c-b409-35e25b7b9ca5
"""
For Dash I am just running the Dash server as normal, and proxying all the requests 🙃
This works remarkably well.
"""
function PlutoRunner.IntegrationsWithOtherPackages.on_http_request(::Val{:Dash}, request)
	_1, _2, _3, _4, port = split(request[:target], "/")
	url = "http://localhost:$(port)$(request[:target])"
		
	@info "request[:method]" request[:method]
 	
	response = HTTP.request(
		request[:method],
		url,
		[],
		request[:body],
	)
		
	return Dict(
		:status => Int64(response.status),
		:headers => response.headers,
		:body => response.body,
	)
end

# ╔═╡ Cell order:
# ╟─c8f8d722-96a9-49b3-a566-58f73dc1f4c4
# ╟─74a80b9f-663d-4fa4-b1d6-56db206cb167
# ╟─ad6afe08-b802-4b38-80af-0cd343d12a41
# ╠═0a8ed9fd-eb72-420f-93ff-8368cf0fc0e8
# ╟─17f86bb0-4960-4980-9ba6-2f3ada6ceee3
# ╟─b53711ab-7d12-40f1-8457-146b665ea9ca
# ╠═46b1e7b2-8460-11eb-3454-f7fa9542f903
# ╠═db3b4012-52b8-4711-b81d-7dc54f914add
# ╠═b6963f48-81ce-4332-94b7-acc5740376be
# ╠═7209c5f8-47f8-4d64-89e2-b08e121eb227
# ╠═007a8782-2487-41bc-82cc-c26ef7e6a6f9
# ╟─fd894b9d-0844-4627-a833-e79c9355972c
# ╠═2685e3f7-2c70-4e1f-a6ae-bdce5e07fedb
# ╠═c250c492-ad8d-4e59-8520-0c3a86135e69
# ╟─4e12b92f-c372-4628-9c9d-dc3568635611
# ╠═1eb65acd-3385-4f7c-9e05-d4af5456c3cf
# ╠═265c7672-980e-4bcf-a07d-375a4a35ca49
# ╠═084ebaf4-ba36-413c-b409-35e25b7b9ca5

@coprem
Copy link

coprem commented Jun 28, 2021

@dralletje @fonsp Can we use DashCoreComponents and DashHtmlComponents Directly in Pluto. Just Like PlutoUI Functions

@fonsp
Copy link

fonsp commented Jun 29, 2021

@coprem Try it out yourself and let us know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment