diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 87 |
1 files changed, 55 insertions, 32 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 06bf267..9390af4 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -1368,19 +1368,30 @@ function renderGraph() { const dormant = (state.peers.peers[id] || {}).status === "dormant"; const radius = member.self ? 8 : 6; mark(id, "circle", { cx: p.x, cy: p.y, r: radius + 6, fill: "rgb(127 127 127)" }); - // In a remake (`inRemakes`): amber; its coordinator also ringed. + // In a remake (`inRemakes`): amber; its coordinator also with a halo, or deciding. const voting = amber.has(id); - if (coordinating.has(id)) { - // Filling while it settles, counting down once committed (`countdowns`): both - // from the top of the screen, however the map is turned. - const dash = countdownDash(id, radius + 4); - children.push(drawing("coordinator " + id, "circle", { cx: p.x, cy: p.y, r: radius + 4, - fill: "none", stroke: AMBER, "stroke-width": 1, "pointer-events": "none", - ...(dash ? { "stroke-dasharray": dash, transform: countdownTurn(id, p) } : {}) })); + // Its remake's wait (`countdowns`), and whether it is over: settled but not decided + // yet (a question mark on it, below), or committed with this node's wait to + // switch over (nothing: it switches any moment). + const countdown = coordinating.has(id) ? countdowns.get(id) : undefined; + const over = countdown !== undefined && countdown.until <= performance.now(); + const deciding = over && countdown.kind === "settle"; + if (coordinating.has(id) && !over) { + // While it settles, and once committed until this node switches: a golden halo, + // smaller than the one in focus and over it, waning counterclockwise from the top + // of the screen, however the map is turned; whole while no wait is known yet + // (just proposed, or committed before this node saw it). A circle half the halo's + // radius, its stroke as wide as the radius: a disc, its dashes a slice of it. + const dash = countdownDash(id, COUNTDOWN_R / 2); + children.push(drawing("countdown " + id, "circle", { cx: p.x, cy: p.y, + r: COUNTDOWN_R / 2, fill: "none", stroke: AMBER, "stroke-width": COUNTDOWN_R, + "stroke-opacity": .55, "pointer-events": "none", + ...(dash ? { "stroke-dasharray": dash, transform: countdownTurn(p) } : {}) })); if (dash && countdownFrame === null) countdownFrame = requestAnimationFrame(tickCountdowns); } + // Deciding: hollow, so its question mark shows. const circle = drawing("node " + id, "circle", { cx: p.x, cy: p.y, r: radius, - fill: voting ? AMBER : member.self ? "var(--self)" : reported ? "var(--fg)" : "var(--panel)", + fill: deciding ? "var(--panel)" : voting ? AMBER : member.self ? "var(--self)" : reported ? "var(--fg)" : "var(--panel)", stroke: dormant ? "var(--bad)" : voting ? AMBER : member.self ? "var(--self)" : "var(--fg)", "stroke-width": dormant ? 2.5 : 1.5 }, pressed); // Its clock against the swarm's, as the members' reports chain it (the node's). @@ -1397,7 +1408,14 @@ function renderGraph() { const nameLabel = drawing("name " + id, "text", upright({ x: p.x + 10, y: p.y + 4, "font-size": 12, fill: "var(--fg)", cursor: "pointer" }), pressed); label(nameLabel, name(id)); - children.push(circle, ...pin, nameLabel); + // Deciding: a golden question mark in it. Without the map's labels' edge in the + // background colour (`#graph text`, 4 px wide: a style, which only a style + // overrides), which would cover the node's rim. + const asking = deciding ? [drawing("deciding " + id, "text", upright({ x: p.x, y: p.y, + "font-size": radius * 1.7, "font-weight": "bold", fill: AMBER, style: "stroke: none", + "text-anchor": "middle", "dominant-baseline": "central", "pointer-events": "none" }))] : []; + for (const question of asking) text(question, "?"); + children.push(circle, ...pin, ...asking, nameLabel); } marks.pointed.replaceChildren(...marked.pointed); marks.shown.replaceChildren(...marked.shown); @@ -2234,7 +2252,7 @@ function breathe(id, { fill, rim, to, ms, opacity }) { // Remakes on the map (`remakes` in the snapshot: those of this node's epoch, with their // coordinator, phase and voters). While one is open (proposed, or committed until they -// switch), its coordinator and voters are amber, the coordinator also ringed; a small +// switch), its coordinator and voters are amber, the coordinator with a halo; a small // amber breath as one is proposed (its coordinator) and as each vote comes. Decided, a // pop over all of them: green if committed, red if aborted or expired, and they turn // back. Only changes seen while the page is open: those of before show as they are. @@ -2302,11 +2320,12 @@ function remakesArrived() { } // Open remakes' coordinators, by node ID: when the wait ends (by `performance.now()`), // the whole wait (ms), and which: `settle` (votes collected) or `switch` (committed, -// this node's wait to switch). The ring of one settling fills: empty at first, growing -// counterclockwise from the top; once committed it counts down: a full circle from the -// top, its end going back counterclockwise as the wait runs out, gone as the epoch -// switches. Both meet at the top, so the one hands over to the other there. +// this node's wait to switch). Each is a golden halo, whole at first, waning +// counterclockwise to the top as the wait runs out: the votes', then, once committed, +// whole again, the switch's, gone as the epoch switches. const countdowns = new Map(); +// The halo's radius: within the one of a node in focus (13), drawn over it. +const COUNTDOWN_R = 11; // When each wait was first seen (`remakesArrived`), by remake ID and kind. const anchors = new Map(); let countdownFrame = null; @@ -2316,31 +2335,35 @@ function countdownDash(id, r) { if (!countdown) return null; const left = Math.min(1, Math.max(0, (countdown.until - performance.now()) / countdown.total)); const around = 2 * Math.PI * r; - const shown = countdown.kind === "settle" ? 1 - left : left; - return (around * shown).toFixed(1) + " " + around.toFixed(1); + return (around * left).toFixed(1) + " " + around.toFixed(1); } -// Where the ring of `id` (at `p`) starts, and which way it goes: a circle's stroke -// starts at its right and goes clockwise; turned a quarter back (and back from the -// map's own turn, `view`), it starts at the top of the screen. Settling, mirrored -// about the upright through its centre: counterclockwise from there. -function countdownTurn(id, p) { - const turn = "rotate(" + (-90 - degrees(view.turn)) + " " + p.x + " " + p.y + ")"; - if ((countdowns.get(id) || {}).kind !== "settle") return turn; - const a = degrees(view.turn); - // The mirror about the screen's upright through p, in the map's turned frame. - return "rotate(" + -a + " " + p.x + " " + p.y + ") translate(" + 2 * p.x + " 0) scale(-1 1) rotate(" + - a + " " + p.x + " " + p.y + ") " + turn; +// Where a halo at `p` starts: a circle's stroke starts at its right and goes clockwise; +// turned a quarter back (and back from the map's own turn, `view`), it starts at the +// top of the screen, so what is left ends further back counterclockwise as it wanes. +function countdownTurn(p) { + return "rotate(" + (-90 - degrees(view.turn)) + " " + p.x + " " + p.y + ")"; } -// Every frame while a ring counts down: its dashes, and nothing else drawn anew. +// Every frame while a halo counts: its dashes and where they start, nothing else drawn +// anew. Both: a key's element may have been drawn before it counted, and then it +// starts at its right, going clockwise, until the next render turns it. function tickCountdowns() { countdownFrame = null; const now = performance.now(); if (document.hidden || ![...countdowns.values()].some(c => c.until > now - 100)) return; + // A wait just over: drawn anew, for what shows then (`render`: a question mark, or + // nothing). + const ended = [...countdowns.values()].filter(c => c.until <= now && !c.ended); + for (const countdown of ended) countdown.ended = true; + if (ended.length) renderGraph(); for (const id of countdowns.keys()) { - const ring = drawn.get("coordinator " + id); - if (ring) { + const ring = drawn.get("countdown " + id); + if (ring && ring.isConnected) { const dash = countdownDash(id, Number(ring.getAttribute("r"))); - if (dash) ring.setAttribute("stroke-dasharray", dash); + if (dash) { + ring.setAttribute("stroke-dasharray", dash); + ring.setAttribute("transform", countdownTurn( + { x: Number(ring.getAttribute("cx")), y: Number(ring.getAttribute("cy")) })); + } } } countdownFrame = requestAnimationFrame(tickCountdowns); |
