swarm-ui: self-updating RelativeTime component
Closes #3445. New ui/relative-time/RelativeTime — takes a UTC epoch-ms instant, not a precomputed age, and re-renders itself on a 1s interval so a tab left open doesn't silently show a frozen 'fresh (5s ago)' hours later. Pauses while the document is hidden, resyncs immediately on becoming visible again. StatusChip's label widened from string to ComponentChildren so a chip can embed the live ticker (a plain string couldn't carry it). HivesPage's freshness column now derives the age from last_seen_unix client-side via RelativeTime instead of rendering the once-computed age_seconds from the API response. Verified: tsc clean, build succeeds, screenshotted the components page (ticking observed advancing within one virtual-time-budget window) and the hives page (fresh/stale/never-reported all render correctly).
This commit is contained in:
parent
8ffc22eaea
commit
a5ef31ed8b
4 changed files with 91 additions and 5 deletions
|
|
@ -0,0 +1,47 @@
|
|||
// <RelativeTime> — a self-updating relative-age label ("5m ago"), so a
|
||||
// tab left open doesn't silently drift out of sync with reality the way
|
||||
// a value computed once at fetch time would. Takes the actual UTC
|
||||
// instant (`epochMs`), not a precomputed age: the caller doing its own
|
||||
// age math ahead of time is exactly the shape that goes stale.
|
||||
//
|
||||
// Pauses its own ticking while the document is hidden rather than
|
||||
// running for no visible benefit, same instinct as the dashboard
|
||||
// package's matrix-rain effect — and resyncs immediately on becoming
|
||||
// visible again instead of waiting out the rest of a stale interval.
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { fmtAgo } from '../../util.js';
|
||||
|
||||
// Cheap at the handful of rows/chips this renders for today, and coarse
|
||||
// enough to be correct without visibly ticking every frame — the same
|
||||
// choice a wall clock makes.
|
||||
const TICK_MS = 1000;
|
||||
|
||||
export function RelativeTime({ epochMs }: { epochMs: number }) {
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
let id: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
const start = () => {
|
||||
setNow(Date.now());
|
||||
id = setInterval(() => setNow(Date.now()), TICK_MS);
|
||||
};
|
||||
const stop = () => {
|
||||
if (id !== undefined) clearInterval(id);
|
||||
id = undefined;
|
||||
};
|
||||
|
||||
if (document.visibilityState === 'visible') start();
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === 'visible') start();
|
||||
else stop();
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
return () => {
|
||||
stop();
|
||||
document.removeEventListener('visibilitychange', onVisibility);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <>{fmtAgo((now - epochMs) / 1000)}</>;
|
||||
}
|
||||
Loading…
Reference in a new issue