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

🩺 Process status #2399

Merged
merged 29 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions frontend/common/clock sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useContext, useState, useEffect } from "../imports/Preact.js"
import { PlutoActionsContext } from "./PlutoContext.js"

/** Request the current time from the server, compare with the local time, and return the current best estimate of our time difference. Updates regularly.
* @param {{connected: boolean}} props
*/
export const useMyClockIsAheadBy = ({ connected }) => {
let pluto_actions = useContext(PlutoActionsContext)

const [my_clock_is_ahead_by, set_my_clock_is_ahead_by] = useState(0)

useEffect(() => {
console.error({ connected })
if (connected) {
let f = async () => {
let getserver = () => pluto_actions.send("current_time").then((m) => m.message.time)
let getlocal = () => Date.now() / 1000

// once to precompile and to make sure that the server is not busy with other tasks
// console.log("getting server time warmup")
for (let i = 0; i < 16; i++) await getserver()
// console.log("getting server time warmup done")

let t1 = await getlocal()
let s1 = await getserver()
let s2 = await getserver()
let t2 = await getlocal()
// console.log("getting server time done")

let mytime = (t1 + t2) / 2
let servertime = (s1 + s2) / 2

let diff = mytime - servertime
// console.info("My clock is ahead by ", diff, " s")
if (!isNaN(diff)) set_my_clock_is_ahead_by(diff)
}

f()

let handle = setInterval(f, 60 * 1000)

return () => clearInterval(handle)
}
}, [connected])

return my_clock_is_ahead_by
}
95 changes: 75 additions & 20 deletions frontend/components/BottomRightPanel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { html, useState, useRef, useEffect } from "../imports/Preact.js"
import { html, useState, useRef, useEffect, useMemo } from "../imports/Preact.js"
import { cl } from "../common/ClassTable.js"

import { LiveDocsTab } from "./LiveDocsTab.js"
import { is_finished, ProcessTab, total_done, total_tasks } from "./ProcessTab.js"
import { useMyClockIsAheadBy } from "../common/clock sync.js"

export const ENABLE_PROCESS_TAB = window.localStorage.getItem("ENABLE_PROCESS_TAB") === "true"

Expand All @@ -20,27 +22,63 @@ window.PLUTO_TOGGLE_PROCESS_TAB = () => {
window.location.reload()
}

export let BottomRightPanel = ({ desired_doc_query, on_update_doc_query, notebook }) => {
/**
* @typedef PanelTabName
* @type {"docs" | "process" | null}
*/

export const open_bottom_right_panel = (/** @type {PanelTabName} */ tab) => window.dispatchEvent(new CustomEvent("open_bottom_right_panel", { detail: tab }))

/**
* @param {{
* notebook: import("./Editor.js").NotebookData,
* desired_doc_query: string?,
* on_update_doc_query: (query: string?) => void,
* connected: boolean,
* }} props
*/
export let BottomRightPanel = ({ desired_doc_query, on_update_doc_query, notebook, connected }) => {
let container_ref = useRef()

const focus_docs_on_open_ref = useRef(false)
const [open_tab, set_open_tab] = useState(/** @type { "docs" | "process" | null} */ (null))
const [open_tab, set_open_tab] = useState(/** @type { PanelTabName} */ (null))
const hidden = open_tab == null

// Open docs when "open_live_docs" event is triggered
// Open panel when "open_bottom_right_panel" event is triggered
useEffect(() => {
let handler = () => {
let handler = (/** @type {CustomEvent} */ e) => {
console.log(e.detail)
// https://github.com/fonsp/Pluto.jl/issues/321
focus_docs_on_open_ref.current = false
set_open_tab("docs")
set_open_tab(e.detail)
if (window.getComputedStyle(container_ref.current).display === "none") {
alert("This browser window is too small to show docs.\n\nMake the window bigger, or try zooming out.")
}
}
window.addEventListener("open_live_docs", handler)
return () => window.removeEventListener("open_live_docs", handler)
window.addEventListener("open_bottom_right_panel", handler)
return () => window.removeEventListener("open_bottom_right_panel", handler)
}, [])

const [status_total, status_done] = useMemo(
() =>
!ENABLE_PROCESS_TAB || notebook.status_tree == null
? [0, 0]
: [
// total_tasks minus 1, to exclude the notebook task itself
total_tasks(notebook.status_tree) - 1,
// the notebook task should never be done, but lets be sure and subtract 1 if it is:
total_done(notebook.status_tree) - (is_finished(notebook.status_tree) ? 1 : 0),
],
[notebook.status_tree]
)

const busy = status_done < status_total

const show_business_outline = useDelayedTruth(busy, 700)
const show_business_counter = useDelayedTruth(busy, 3000)

const my_clock_is_ahead_by = useMyClockIsAheadBy({ connected })

return html`
<aside id="helpbox-wrapper" ref=${container_ref}>
<pluto-helpbox class=${cl({ hidden, [`helpbox-${open_tab ?? hidden}`]: true })}>
Expand All @@ -58,7 +96,8 @@ export let BottomRightPanel = ({ desired_doc_query, on_update_doc_query, noteboo
// TODO: focus the docs input
}}
>
<span>Live Docs</span>
<span class="tabicon"></span>
<span class="tabname">Live Docs</span>
</button>
<button
disabled=${!ENABLE_PROCESS_TAB}
Expand All @@ -67,12 +106,20 @@ export let BottomRightPanel = ({ desired_doc_query, on_update_doc_query, noteboo
"helpbox-tab-key": true,
"helpbox-process": true,
"active": open_tab === "process",
"busy": ENABLE_PROCESS_TAB && show_business_outline,
})}
onClick=${() => {
set_open_tab(open_tab === "process" ? null : "process")
}}
>
<span>${ENABLE_PROCESS_TAB ? "Status" : "Coming soon"}</span>
<span class="tabicon"></span>
<span class="tabname"
>${ENABLE_PROCESS_TAB
? open_tab === "process" || !show_business_counter
? "Status"
: html`Status${" "}<span class="subprogress-counter">(${status_done}/${status_total})</span>`
: "Coming soon"}</span
>
</button>
${hidden
? null
Expand All @@ -93,18 +140,26 @@ export let BottomRightPanel = ({ desired_doc_query, on_update_doc_query, noteboo
notebook=${notebook}
/>`
: open_tab === "process"
? html`<section>
<p>Congratulations, you found the secret!</p>
<p>
Have you considered becoming a Pluto.jl open source contributor? We are always looking for creative people with JavaScript
experience! Take a look at our${" "}
<a href="https://github.com/fonsp/Pluto.jl/issues?q=is%3Aopen+label%3A%22good+first+issue%22+sort%3Aupdated-desc"
>good first issues</a
>, and our ${" "}<a href="https://juliapluto.github.io/weekly-call-notes/">weekly community call</a>.
</p>
</section>`
? html`<${ProcessTab} notebook=${notebook} my_clock_is_ahead_by=${my_clock_is_ahead_by} />`
: null}
</pluto-helpbox>
</aside>
`
}

const useDelayedTruth = (/** @type {boolean} */ x, /** @type {number} */ timeout) => {
const [output, set_output] = useState(false)

useEffect(() => {
if (x) {
let handle = setTimeout(() => {
set_output(true)
}, timeout)
return () => clearTimeout(handle)
} else {
set_output(false)
}
}, [x])

return output
}
3 changes: 2 additions & 1 deletion frontend/components/CellInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { HighlightLineFacet, highlightLinePlugin } from "./CellInput/highlight_l
import { commentKeymap } from "./CellInput/comment_mixed_parsers.js"
import { ScopeStateField } from "./CellInput/scopestate_statefield.js"
import { mod_d_command } from "./CellInput/mod_d_command.js"
import { open_bottom_right_panel } from "./BottomRightPanel.js"

export const ENABLE_CM_MIXED_PARSER = window.localStorage.getItem("ENABLE_CM_MIXED_PARSER") === "true"

Expand Down Expand Up @@ -647,7 +648,7 @@ export const CellInput = ({
EditorView.updateListener.of((update) => {
if (!update.docChanged) return
if (update.state.doc.length > 0 && update.state.sliceDoc(0, 1) === "?") {
window.dispatchEvent(new CustomEvent("open_live_docs"))
open_bottom_right_panel("docs")
}
}),
EditorState.tabSize.of(4),
Expand Down
3 changes: 2 additions & 1 deletion frontend/components/CellInput/pluto_autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { get_selected_doc_from_state } from "./LiveDocsFromCursor.js"
import { cl } from "../../common/ClassTable.js"
import { ScopeStateField } from "./scopestate_statefield.js"
import { open_bottom_right_panel } from "../BottomRightPanel.js"

let { autocompletion, completionKeymap, completionStatus, acceptCompletion } = autocomplete

Expand Down Expand Up @@ -90,7 +91,7 @@ const tab_completion_command = (cm) => {
let open_docs_if_autocomplete_is_open_command = (cm) => {
let autocompletion_open = cm.state.field(completionState, false)?.open ?? false
if (autocompletion_open) {
window.dispatchEvent(new CustomEvent("open_live_docs"))
open_bottom_right_panel("docs")
return true
}
return false
Expand Down
55 changes: 55 additions & 0 deletions frontend/components/DiscreteProgressBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { cl } from "../common/ClassTable.js"
import { html, useEffect, useRef, useState } from "../imports/Preact.js"

export const DiscreteProgressBar = ({ total, done, busy }) => {
total = Math.max(1, total)

return html`
<div
class=${cl({
"discrete-progress-bar": true,
"small": total < 8,
"mid": total >= 8 && total < 48,
"big": total >= 48,
})}
data-total=${total}
>
${[...Array(total)].map((_, i) => {
return html`<div
class=${cl({
done: i < done,
busy: i >= done && i < done + busy,
})}
></div>`
})}
</div>
`
}

export const DiscreteProgressBarTest = () => {
const [done_total, set_done_total] = useState([0, 0, 0])

const done_total_ref = useRef(done_total)
done_total_ref.current = done_total

useEffect(() => {
let handle = setInterval(() => {
const [done, busy, total] = done_total_ref.current

if (Math.random() < 0.3) {
if (done < total) {
if (Math.random() < 0.1) {
set_done_total([done, 1, total + 5])
} else {
set_done_total([done + 1, 1, total])
}
} else {
set_done_total([0, 1, Math.ceil(Math.random() * Math.random() * 100)])
}
}
}, 100)
return () => clearInterval(handle)
}, [])

return html`<${DiscreteProgressBar} total=${done_total[2]} busy=${done_total[1]} done=${done_total[0]} />`
}
12 changes: 12 additions & 0 deletions frontend/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ const first_true_key = (obj) => {
* }}
*/

/**
* @typedef StatusEntryData
* @type {{
* name: string,
* started_at: number?,
* finished_at: number?,
* subtasks: Record<string,StatusEntryData>,
* }}
*/

/**
* @typedef CellResultData
* @type {{
Expand Down Expand Up @@ -233,6 +243,7 @@ const first_true_key = (obj) => {
* bonds: BondValuesDict,
* nbpkg: NotebookPkgData?,
* metadata: object,
* status_tree: StatusEntryData?,
* }}
*/

Expand Down Expand Up @@ -1533,6 +1544,7 @@ patch: ${JSON.stringify(
<${BottomRightPanel}
desired_doc_query=${this.state.desired_doc_query}
on_update_doc_query=${this.actions.set_doc_query}
connected=${this.state.connected}
notebook=${this.state.notebook}
/>
<${Popup}
Expand Down
Loading