diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 89 |
1 files changed, 72 insertions, 17 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 5eb4a5c..ec36abf 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -56,6 +56,14 @@ section h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 8px; flex: none; } section h2 .what { text-transform: none; letter-spacing: 0; color: var(--fg); } + /* Collapsed: the title only, and nothing drawn in it (`busy`). */ + button.collapse { font: inherit; padding: 0 4px 0 0; color: var(--muted); background: none; + border: none; cursor: pointer; } + button.collapse:hover { color: var(--fg); } + section.collapsed > :not(h2) { display: none; } + section.collapsed { align-self: start; } + section.collapsed h2 { margin: 0; } + section.collapsed h2 .toggle { display: none; } section h2 .what .id { color: var(--muted); font-weight: normal; word-break: break-all; } /* The space between boxes, where they are resized. */ main.resize-columns, main.resize-columns * { cursor: col-resize; } @@ -184,7 +192,7 @@ <div class="scroll" id="peers-scroll"><table id="peers"></table></div> </section> <section id="figures-box"> - <h2 id="figures-title">Figures</h2> + <h2><span id="figures-title">Figures</span></h2> <div class="scroll"><div id="stats" class="tree"></div></div> </section> <section id="document-box"> @@ -668,6 +676,7 @@ function layoutAgain() { lastShape = ""; } function renderGraph() { + if (document.getElementById("map").classList.contains("collapsed")) return; const root = document.getElementById("graph"); const width = root.clientWidth || 800, height = root.clientHeight || 560; root.setAttribute("viewBox", "0 0 " + width + " " + height); @@ -1185,6 +1194,8 @@ document.getElementById("document").replaceChildren( // A box is busy while text in it is selected: drawing it anew would lose the selection, // so it waits (`held`) until it's gone. function busy(box) { + // Collapsed: drawn when opened (`release`). + if (box.classList.contains("collapsed")) return true; const selection = window.getSelection(); if (!selection || selection.isCollapsed || !selection.rangeCount) return false; return box.contains(selection.getRangeAt(0).commonAncestorContainer); @@ -1471,7 +1482,8 @@ function messages(byKind) { function trafficArrived() { if (!state) return; const self = state.node.id, now = performance.now(); - const dots = flowsShown && !calm.matches && !document.hidden; + const dots = flowsShown && !calm.matches && !document.hidden && + !document.getElementById("map").classList.contains("collapsed"); const current = new Map(); for (const [id, peer] of Object.entries(state.peers.peers)) { if (peer.traffic) { @@ -1508,17 +1520,30 @@ function trafficArrived() { // every update (the same counts as the map's messages), the latest at the bottom, // `CONSOLE_LINES` at most. Scrolled along while at the bottom. --- const CONSOLE_LINES = 200; +// The lines, kept while the box is collapsed and drawn when it's opened (`showConsole`). +const consoleLines = []; +function consoleLine([when, text]) { + return el("div", {}, el("span", { class: "when" }, when + " "), text); +} function logTraffic(way, peer, total, kinds) { + const parts = Object.entries(kinds).map(([kind, count]) => kind + " " + count).join(", "); + const line = [new Date().toLocaleTimeString(), + (way === "sent" ? "→ " : "← ") + name(peer) + ": " + total + " (" + parts + ")"]; + consoleLines.push(line); + if (consoleLines.length > CONSOLE_LINES) consoleLines.shift(); + if (document.getElementById("console-box").classList.contains("collapsed")) return; const log = document.getElementById("console"); const scroll = document.getElementById("console-scroll"); const following = scroll.scrollTop + scroll.clientHeight >= scroll.scrollHeight - 4; - const when = new Date().toLocaleTimeString(); - const parts = Object.entries(kinds).map(([kind, count]) => kind + " " + count).join(", "); - log.append(el("div", {}, el("span", { class: "when" }, when + " "), - (way === "sent" ? "→ " : "← ") + name(peer) + ": " + total + " (" + parts + ")")); + log.append(consoleLine(line)); while (log.childElementCount > CONSOLE_LINES) log.firstElementChild.remove(); if (following) scroll.scrollTop = scroll.scrollHeight; } +function showConsole() { + document.getElementById("console").replaceChildren(...consoleLines.map(consoleLine)); + const scroll = document.getElementById("console-scroll"); + scroll.scrollTop = scroll.scrollHeight; +} function drawFlows() { flowFrame = null; const layer = document.getElementById("flows"); @@ -1668,20 +1693,18 @@ function applySizes() { if (sizes.columns === null) style.removeProperty("--columns"); else style.setProperty("--columns", "minmax(0, " + sizes.columns + "fr) minmax(0, " + (1 - sizes.columns) + "fr)"); - if (sizes.peers === null && sizes.split === null && sizes.console === null) { - style.removeProperty("--rows"); - } else { - const split = sizes.split === null ? .5 : sizes.split; - const consoleShare = sizes.console === null ? .2 : sizes.console; - const rest = 1 - consoleShare; - style.setProperty("--rows", (sizes.peers === null ? "auto" : sizes.peers + "px") + - " minmax(0, " + split * rest + "fr) minmax(0, " + (1 - split) * rest + "fr)" + - " minmax(0, " + consoleShare + "fr)"); - } + // A collapsed box's row as tall as its title. + const shut = id => document.getElementById(id).classList.contains("collapsed"); + const split = sizes.split === null ? .5 : sizes.split; + const consoleShare = sizes.console === null ? .2 : sizes.console; + const rest = 1 - consoleShare; + const share = (id, fr) => shut(id) ? "auto" : "minmax(0, " + fr + "fr)"; + style.setProperty("--rows", (sizes.peers === null || shut("peers-box") ? "auto" + : sizes.peers + "px") + " " + share("figures-box", split * rest) + " " + + share("document-box", (1 - split) * rest) + " " + share("console-box", consoleShare)); main.classList.toggle("peers-sized", sizes.peers !== null); try { localStorage.setItem("continuum-sizes", JSON.stringify(sizes)); } catch (_) {} } -applySizes(); const box = id => document.getElementById(id).getBoundingClientRect(); // Which space between boxes `event` is over, if any: "columns", "peers" (below the // peers), "split" (between figures and document) or "console" (above the console). @@ -1751,6 +1774,38 @@ const resized = () => { }; main.addEventListener("pointerup", resized); main.addEventListener("pointercancel", resized); +// --- Boxes collapsed to their titles, and opened again; kept in this browser. A +// collapsed box isn't drawn (`busy`), so it costs nothing while the node updates. --- +const collapsed = new Set(); +try { + for (const id of JSON.parse(localStorage.getItem("continuum-collapsed") || "[]")) { + collapsed.add(id); + } +} catch (_) {} +for (const section of document.querySelectorAll("main > section")) { + const button = el("button", { class: "collapse" }); + section.querySelector("h2").prepend(button); + const show = () => { + const shut = collapsed.has(section.id); + section.classList.toggle("collapsed", shut); + button.textContent = shut ? "▸" : "▾"; + button.title = shut ? "Open" : "Collapse"; + }; + show(); + button.addEventListener("click", () => { + if (collapsed.has(section.id)) collapsed.delete(section.id); else collapsed.add(section.id); + try { localStorage.setItem("continuum-collapsed", JSON.stringify([...collapsed])); } + catch (_) {} + show(); + applySizes(); + if (collapsed.has(section.id)) return; + if (section.id === "console-box") showConsole(); + if (section.id === "map") layoutAgain(); + release(); + render(); + }); +} +applySizes(); main.addEventListener("dblclick", event => { const gap = gapAt(event); if (!gap) return; |
