From 76b210ae73abc4975ab2724ad67925544dda2f79 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Mon, 10 Jul 2023 00:08:58 -0600 Subject: [PATCH 1/7] Slider server status component --- frontend/components/Editor.js | 4 ++++ frontend/components/SliderServerStatus.js | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 frontend/components/SliderServerStatus.js diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index e6f4a95c81..6aca6af65c 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -35,6 +35,7 @@ import { HijackExternalLinksToOpenInNewTab } from "./HackySideStuff/HijackExtern import { FrontMatterInput } from "./FrontmatterInput.js" import { EditorLaunchBackendButton } from "./Editor/LaunchBackendButton.js" import { get_environment } from "../common/Environment.js" +import { SliderServerStatus } from "./SliderServerStatus.js" // This is imported asynchronously - uncomment for development // import environment from "../common/Environment.js" @@ -1516,6 +1517,9 @@ patch: ${JSON.stringify( last_hot_reload_time=${notebook.last_hot_reload_time} connected=${this.state.connected} /> + <${SliderServerStatus} + bond_connections=${null} + /> <${Notebook} notebook=${notebook} cell_inputs_local=${this.state.cell_inputs_local} diff --git a/frontend/components/SliderServerStatus.js b/frontend/components/SliderServerStatus.js new file mode 100644 index 0000000000..b61a076927 --- /dev/null +++ b/frontend/components/SliderServerStatus.js @@ -0,0 +1,12 @@ +import { html, useEffect, useState, useContext, useRef, useMemo } from "../imports/Preact.js" + +export const SliderServerStatus = ({ bond_connections }) => { + return html` + +
+ +
+
` +} From 938c92771307d093d7d1dd95bd9fc0f5d1541e55 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Tue, 11 Jul 2023 11:39:46 -0600 Subject: [PATCH 2/7] Show cell loading while fetching bond updates --- frontend/common/SliderServerClient.js | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index 49e64ffb19..d6ed1697d1 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -8,6 +8,23 @@ const assert_response_ok = (/** @type {Response} */ r) => (r.ok ? r : Promise.re const actions_to_keep = ["get_published_object"] +const get_start = (graph, v) => Object.values(graph).find((node) => Object.keys(node.downstream_cells_map).includes(v))?.cell_id +const get_starts = (graph, vars) => new Set([...vars].map((v) => get_start(graph, v))) +const recursive_dependencies = (graph, starts) => { + const deps = new Set(starts) + const ends = [...starts] + while (ends.length > 0) { + const node = ends.splice(0, 1)[0] + _.flatten(Object.values(graph[node].downstream_cells_map)).forEach((child) => { + if (!deps.has(child)) { + ends.push(child) + deps.add(child) + } + }) + } + return deps +} + export const nothing_actions = ({ actions }) => Object.fromEntries( Object.entries(actions).map(([k, v]) => [ @@ -47,6 +64,16 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const hash = await notebookfile_hash const graph = await bond_connections + // compute dependencies and update cell running statuses + const dep_graph = get_current_state().cell_dependencies + const starts = get_starts(dep_graph, bonds_to_set.current) + const running_cells = [...recursive_dependencies(dep_graph, starts)] + await setStatePromise( + immer((state) => { + running_cells.forEach((cell_id) => (state.notebook.cell_results[cell_id][starts.has(cell_id) ? "running" : "queued"] = true)) + }) + ) + if (bonds_to_set.current.size > 0) { const to_send = new Set(bonds_to_set.current) bonds_to_set.current.forEach((varname) => (graph[varname] ?? []).forEach((x) => to_send.add(x))) @@ -88,6 +115,10 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, ids_of_cells_that_ran.forEach((id) => { state.cell_results[id] = original.cell_results[id] }) + running_cells.forEach((id) => { + state.cell_results[id].queued = false + state.cell_results[id].running = false + }) })(get_current_state()) ) } catch (e) { From 27c876070a2dfe63f812ea4688b762922d399919 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Tue, 11 Jul 2023 12:46:19 -0600 Subject: [PATCH 3/7] Subtle connected to sliders indicator --- frontend/common/SliderServerClient.js | 16 +++++++++++++++- frontend/components/Editor.js | 7 ++++++- frontend/components/SliderServerStatus.js | 12 +++--------- frontend/editor.css | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index d6ed1697d1..e2a8933619 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -40,6 +40,12 @@ export const nothing_actions = ({ actions }) => ) export const slider_server_actions = ({ setStatePromise, launch_params, actions, get_original_state, get_current_state, apply_notebook_patches }) => { + setStatePromise( + immer((state) => { + state.slider_server.connecting = true + }) + ) + const notebookfile_hash = fetch(new Request(launch_params.notebookfile, { integrity: launch_params.notebookfile_integrity })) .then(assert_response_ok) .then((r) => r.arrayBuffer()) @@ -53,7 +59,15 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, .then((r) => r.arrayBuffer()) .then((b) => unpack(new Uint8Array(b))) - bond_connections.then((x) => console.log("Bond connections:", x)) + bond_connections.then((x) => { + console.log("Bond connections:", x) + setStatePromise( + immer((state) => { + state.slider_server.connecting = false + state.slider_server.interactive = Object.keys(x).length > 0 + }) + ) + }) const mybonds = {} const bonds_to_set = { diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 6aca6af65c..9fc15c3340 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -337,6 +337,11 @@ export class Editor extends Component { is_recording: false, recording_waiting_to_start: false, + + slider_server: { + connecting: false, + interactive: false, + }, } this.setStatePromise = (fn) => new Promise((r) => this.setState(fn, r)) @@ -1518,7 +1523,7 @@ patch: ${JSON.stringify( connected=${this.state.connected} /> <${SliderServerStatus} - bond_connections=${null} + ...${this.state.slider_server} /> <${Notebook} notebook=${notebook} diff --git a/frontend/components/SliderServerStatus.js b/frontend/components/SliderServerStatus.js index b61a076927..7ce2f522bc 100644 --- a/frontend/components/SliderServerStatus.js +++ b/frontend/components/SliderServerStatus.js @@ -1,12 +1,6 @@ import { html, useEffect, useState, useContext, useRef, useMemo } from "../imports/Preact.js" -export const SliderServerStatus = ({ bond_connections }) => { - return html` - -
- -
-
` +export const SliderServerStatus = ({ interactive }) => { + const details_message = interactive ? "Connected to sliders!" : "" + return html`
${interactive && html``}
` } diff --git a/frontend/editor.css b/frontend/editor.css index 3d15fa28c4..f8c2066457 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -997,6 +997,22 @@ main > preamble #saveall-container.saved > span { opacity: 0.5; } +#sliderstatus-container { + position: fixed; + bottom: 15px; + right: 20px; +} +#sliderstatus-container > span::after { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/checkmark-outline.svg"); + background-size: contain; + filter: var(--image-filters); + opacity: 0.15; +} + .overlay-button span.pluto-icon::after { content: ""; display: inline-block; From 475b8bd91b21492c52ea76e90899b3a8f633a8f1 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Tue, 11 Jul 2023 12:56:59 -0600 Subject: [PATCH 4/7] Remove sliders status indicator --- frontend/components/SliderServerStatus.js | 6 ------ frontend/editor.css | 16 ---------------- 2 files changed, 22 deletions(-) delete mode 100644 frontend/components/SliderServerStatus.js diff --git a/frontend/components/SliderServerStatus.js b/frontend/components/SliderServerStatus.js deleted file mode 100644 index 7ce2f522bc..0000000000 --- a/frontend/components/SliderServerStatus.js +++ /dev/null @@ -1,6 +0,0 @@ -import { html, useEffect, useState, useContext, useRef, useMemo } from "../imports/Preact.js" - -export const SliderServerStatus = ({ interactive }) => { - const details_message = interactive ? "Connected to sliders!" : "" - return html`
${interactive && html``}
` -} diff --git a/frontend/editor.css b/frontend/editor.css index f8c2066457..3d15fa28c4 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -997,22 +997,6 @@ main > preamble #saveall-container.saved > span { opacity: 0.5; } -#sliderstatus-container { - position: fixed; - bottom: 15px; - right: 20px; -} -#sliderstatus-container > span::after { - content: ""; - display: inline-block; - width: 20px; - height: 20px; - background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/checkmark-outline.svg"); - background-size: contain; - filter: var(--image-filters); - opacity: 0.15; -} - .overlay-button span.pluto-icon::after { content: ""; display: inline-block; From 923617e36032f75a21acc86fe3819271c77c7bd8 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Tue, 11 Jul 2023 12:57:46 -0600 Subject: [PATCH 5/7] Remove imports from editor --- frontend/components/Editor.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 9fc15c3340..85288ab561 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -35,7 +35,6 @@ import { HijackExternalLinksToOpenInNewTab } from "./HackySideStuff/HijackExtern import { FrontMatterInput } from "./FrontmatterInput.js" import { EditorLaunchBackendButton } from "./Editor/LaunchBackendButton.js" import { get_environment } from "../common/Environment.js" -import { SliderServerStatus } from "./SliderServerStatus.js" // This is imported asynchronously - uncomment for development // import environment from "../common/Environment.js" @@ -1522,9 +1521,6 @@ patch: ${JSON.stringify( last_hot_reload_time=${notebook.last_hot_reload_time} connected=${this.state.connected} /> - <${SliderServerStatus} - ...${this.state.slider_server} - /> <${Notebook} notebook=${notebook} cell_inputs_local=${this.state.cell_inputs_local} From d7a6515760faf5b13ca821e0ff085b4537f71e4c Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Mon, 17 Jul 2023 11:15:40 -0600 Subject: [PATCH 6/7] Warning messages for slider server runtime states --- frontend/common/SliderServerClient.js | 37 +++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index e2a8933619..24349a77e9 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -3,6 +3,7 @@ import { plutohash_arraybuffer, debounced_promises, base64url_arraybuffer } from import { pack, unpack } from "./MsgPack.js" import immer from "../imports/immer.js" import _ from "../imports/lodash.js" +import { open_pluto_popup } from "../components/Popup.js" const assert_response_ok = (/** @type {Response} */ r) => (r.ok ? r : Promise.reject(r)) @@ -39,6 +40,23 @@ export const nothing_actions = ({ actions }) => ]) ) +const sliders_error = (e, previously_set) => { + const el = previously_set ? document.querySelector(`bond[def=${previously_set.values().next().value}]`) : null + if (e.status === 503) { + open_pluto_popup({ + type: "warn", + body: "Sliders are still starting, check back later for interactivity!", + source_element: el, + }) + } else { + open_pluto_popup({ + type: "warn", + body: "Error connecting to slider server!", + source_element: el, + }) + } +} + export const slider_server_actions = ({ setStatePromise, launch_params, actions, get_original_state, get_current_state, apply_notebook_patches }) => { setStatePromise( immer((state) => { @@ -55,7 +73,6 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const bond_connections = notebookfile_hash .then((hash) => fetch(trailingslash(launch_params.slider_server_url) + "bondconnections/" + hash)) - .then(assert_response_ok) .then((r) => r.arrayBuffer()) .then((b) => unpack(new Uint8Array(b))) @@ -92,6 +109,7 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const to_send = new Set(bonds_to_set.current) bonds_to_set.current.forEach((varname) => (graph[varname] ?? []).forEach((x) => to_send.add(x))) console.debug("Requesting bonds", bonds_to_set.current, to_send) + const previously_set = new Set(bonds_to_set.current) bonds_to_set.current = new Set() const mybonds_filtered = Object.fromEntries( @@ -129,16 +147,25 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, ids_of_cells_that_ran.forEach((id) => { state.cell_results[id] = original.cell_results[id] }) - running_cells.forEach((id) => { - state.cell_results[id].queued = false - state.cell_results[id].running = false - }) })(get_current_state()) ) } catch (e) { console.error(unpacked, e) + + if (previously_set.size === 1) { + sliders_error(e, previously_set) + } } } + + await setStatePromise( + immer((state) => { + running_cells.forEach((id) => { + state.notebook.cell_results[id].queued = false + state.notebook.cell_results[id].running = false + }) + }) + ) }) return { From 0a2af908afb632967956fc908bf07846b444e265 Mon Sep 17 00:00:00 2001 From: Connor Burns Date: Mon, 17 Jul 2023 11:48:55 -0600 Subject: [PATCH 7/7] Fix outdated bond connections after reconnect bug --- frontend/common/SliderServerClient.js | 34 +++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index 24349a77e9..0e12b22403 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -71,29 +71,33 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, notebookfile_hash.then((x) => console.log("Notebook file hash:", x)) - const bond_connections = notebookfile_hash - .then((hash) => fetch(trailingslash(launch_params.slider_server_url) + "bondconnections/" + hash)) - .then((r) => r.arrayBuffer()) - .then((b) => unpack(new Uint8Array(b))) - - bond_connections.then((x) => { - console.log("Bond connections:", x) - setStatePromise( - immer((state) => { - state.slider_server.connecting = false - state.slider_server.interactive = Object.keys(x).length > 0 - }) - ) - }) + let bond_connections = null const mybonds = {} const bonds_to_set = { current: new Set(), } const request_bond_response = debounced_promises(async () => { + if (!bond_connections) { + const bond_connections_res = await notebookfile_hash.then((hash) => + fetch(trailingslash(launch_params.slider_server_url) + "bondconnections/" + hash) + ) + if (bond_connections_res.ok) { + bond_connections = await bond_connections_res.arrayBuffer().then((x) => unpack(new Uint8Array(x))) + + console.log("Bond connections:", bond_connections) + setStatePromise( + immer((state) => { + state.slider_server.connecting = false + state.slider_server.interactive = Object.keys(bond_connections).length > 0 + }) + ) + } + } + const base = trailingslash(launch_params.slider_server_url) const hash = await notebookfile_hash - const graph = await bond_connections + const graph = bond_connections || {} // compute dependencies and update cell running statuses const dep_graph = get_current_state().cell_dependencies