aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard/dashboard.html
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/dashboard/dashboard.html')
-rw-r--r--src/api/dashboard/dashboard.html44
1 files changed, 43 insertions, 1 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 5180038..ede191d 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -1236,8 +1236,13 @@ function renderGraph() {
// In a remake (`inRemakes`): amber; its coordinator also ringed.
const voting = amber.has(id);
if (coordinating.has(id)) {
+ // Counting down once committed (`countdowns`): from the top, as a clock's hand.
+ 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" }));
+ fill: "none", stroke: AMBER, "stroke-width": 1, "pointer-events": "none",
+ ...(dash ? { "stroke-dasharray": dash,
+ transform: "rotate(-90 " + p.x + " " + p.y + ")" } : {}) }));
+ if (dash && countdownFrame === null) countdownFrame = requestAnimationFrame(tickCountdowns);
}
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)",
@@ -2096,8 +2101,15 @@ function remakesArrived() {
breathe(id, { fill, rim: fill, to: 26, ms: 600, opacity: .6 });
}
};
+ countdowns.clear();
for (const remake of remakes) {
seen.add(remake.id);
+ // Committed: this node's wait to switch, as of now (re-anchored with every update).
+ if (remake.phase === "committed" && typeof remake.switchInMs === "number" &&
+ remake.switchDelayMs > 0) {
+ countdowns.set(remake.coordinator, { until: performance.now() + remake.switchInMs,
+ total: remake.switchDelayMs });
+ }
const was = knownRemakes.get(remake.id);
if (remakesKnown) {
const breath = id => breathe(id, { fill: AMBER, rim: AMBER, to: 20, ms: 900, opacity: .45 });
@@ -2113,6 +2125,9 @@ function remakesArrived() {
}
knownRemakes.set(remake.id, remake);
}
+ if (countdowns.size > 0 && countdownFrame === null) {
+ countdownFrame = requestAnimationFrame(tickCountdowns);
+ }
// Gone from the snapshot: this node switched to the next epoch (the records stay in
// the old one's document). Committed then, if it was still open.
for (const [id, remake] of knownRemakes) {
@@ -2125,6 +2140,33 @@ function remakesArrived() {
}
remakesKnown = true;
}
+// Committed remakes' coordinators, by node ID: when this node switches (by
+// `performance.now()`) and the whole wait (ms). Their ring counts down: a full circle
+// from the top, going counterclockwise as the wait runs out, gone as the epoch switches.
+const countdowns = new Map();
+let countdownFrame = null;
+// The ring's dashes for `id` (radius `r`), if it counts down: the part left.
+function countdownDash(id, r) {
+ const countdown = countdowns.get(id);
+ if (!countdown) return null;
+ const left = Math.min(1, Math.max(0, (countdown.until - performance.now()) / countdown.total));
+ const around = 2 * Math.PI * r;
+ return (around * left).toFixed(1) + " " + around.toFixed(1);
+}
+// Every frame while a ring counts down: its dashes, and nothing else drawn anew.
+function tickCountdowns() {
+ countdownFrame = null;
+ const now = performance.now();
+ if (document.hidden || ![...countdowns.values()].some(c => c.until > now - 100)) return;
+ for (const id of countdowns.keys()) {
+ const ring = drawn.get("coordinator " + id);
+ if (ring) {
+ const dash = countdownDash(id, Number(ring.getAttribute("r")));
+ if (dash) ring.setAttribute("stroke-dasharray", dash);
+ }
+ }
+ countdownFrame = requestAnimationFrame(tickCountdowns);
+}
// Who is amber now (`remakesArrived`), and who of them coordinates: of the open
// remakes, those not past the epoch yet (`knownEpochs`).
function inRemakes() {