aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-31 23:26:16 +0100
committertv <tv@krebsco.de>2026-10-31 23:26:16 +0100
commit15c9a8c26196b147dd8f7aa06010d5c2c4f38beb (patch)
tree2185fed9c9b3acfe7aa8780c224a712c50447b28 /src/api
parent796166d439d0b44bb2c6109e2129a20b7c699393 (diff)
dashboard: document changes breathe
Diffstat (limited to 'src/api')
-rw-r--r--src/api/dashboard/dashboard.html20
1 files changed, 20 insertions, 0 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 08436d0..53f8ccf 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -1107,6 +1107,20 @@ function editableLeaf(key, shown, value, path) {
return leaf;
}
+// An entry that changed in the document breathes once, as the map's labels do: its
+// row glows and fades.
+function glow(entry) {
+ if (calm.matches || !entry.animate) return;
+ const row = entry.tagName === "DETAILS" ? entry.querySelector(":scope > summary")
+ : entry.classList.contains("leaf") ? entry : entry.firstElementChild || entry;
+ const colour = getComputedStyle(document.documentElement).getPropertyValue("--self").trim();
+ row.animate([
+ { backgroundColor: "transparent" },
+ { backgroundColor: "color-mix(in srgb, " + colour + " 30%, transparent)", offset: .3 },
+ { backgroundColor: "transparent" },
+ ], { duration: 900, easing: "ease-in-out" });
+}
+
// One entry: a short scalar as it is; anything else a branch read when opened. A
// branch's entries are kept across reads, so what's open inside stays open.
function documentEntry(key, summary, path) {
@@ -1169,6 +1183,8 @@ function documentEntry(key, summary, path) {
}));
}
let read = false;
+ // Read once at least: what's new or changed from then on breathes (`glow`).
+ let filled = false;
const fill = async () => {
// Being edited as a whole: read again once that's done.
if (details.classList.contains("editing")) { documentDeferred.add(id); return; }
@@ -1195,11 +1211,13 @@ function documentEntry(key, summary, path) {
if (known && known.element.sameKind && known.element.sameKind(newer)) {
known.element.updated(newer);
known.summary = newer;
+ glow(known.element);
continue;
}
const element = documentEntry(k, newer, [...path, k]);
if (known) known.element.replaceWith(element);
children.set(k, { element, summary: newer });
+ if (filled) glow(element);
}
// In order; moving keeps their state.
details.querySelectorAll(":scope > .note").forEach(note => note.remove());
@@ -1210,7 +1228,9 @@ function documentEntry(key, summary, path) {
const shown = el("pre", {}, typeof value === "string" ? value : JSON.stringify(value, null, 2));
const before = details.querySelector(":scope > pre");
if (before) before.replaceWith(shown); else details.append(shown);
+ if (before && before.textContent !== shown.textContent) glow(shown);
}
+ filled = true;
} catch (error) {
details.querySelectorAll(":scope > .note").forEach(note => note.remove());
details.append(el("div", { class: "note error" }, "Not read: " + error.message));