hyperhive/frontend/packages/dashboard/src/util.js
iris 3fda4ab127 refactor(frontend): extract shared render/format helpers into util.js (#1451)
The tabs.js split's render-heavy domains (schedules, system, containers,
questions/approvals) all share a handful of pure helpers that lived in
the tabs.js IIFE: paintAtomic (atomic-swap render) and the fmtAgo /
fmtElapsed / fmtDuration / truncate formatters (used 10/15/4/6/6 times
across the file). Pull them into a new dashboard-internal util.js so the
upcoming per-tab modules can import them instead of depending on the
entry's closure — the helper analogue of the state.js roster keystone.

They stay out of the cross-page common.js (their phrasing is
dashboard-specific) but are now a shared dashboard module. All five are
pure, so this is behaviour-preserving code motion; esbuild inlines util.js
into the tabs.js bundle. Build green.
2026-06-09 14:52:01 +02:00

49 lines
2.2 KiB
JavaScript

// Shared dashboard render + format helpers.
//
// Small pure utilities used across most dashboard tabs (the container
// tree, the rebuild queue, questions / approvals, schedules + reminders):
// an atomic-swap render helper and a handful of relative-time / duration
// formatters. They live here — a dashboard-internal module — rather than
// in the cross-page `common.js`, because they're specific to the
// dashboard's render style and not needed by the stand-alone pages.
//
// All pure: no DOM/module state captured, so any tab module can import
// them freely without ordering concerns.
// Atomic-swap render: build into an off-DOM DocumentFragment, then commit
// with a single `replaceChildren`. Avoids an intermediate empty-state
// flash on poll cycles even when the builder allocates a lot of nodes.
export function paintAtomic(liveRoot, build) {
const buf = document.createDocumentFragment();
build(buf);
liveRoot.replaceChildren(buf);
}
// Relative age of a unix timestamp, coarsened to one unit ("5m ago").
export function fmtAgo(unixSecs) {
const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - unixSecs));
if (ageSec < 60) return ageSec + 's ago';
if (ageSec < 3600) return Math.floor(ageSec / 60) + 'm ago';
if (ageSec < 86400) return Math.floor(ageSec / 3600) + 'h ago';
return Math.floor(ageSec / 86400) + 'd ago';
}
// Truncate a string to `n` chars, appending an ellipsis when clipped.
export function truncate(s, n) {
return s.length <= n ? s : s.slice(0, n - 1) + '…';
}
// Running-duration label for in-flight items ("3m 12s running").
export function fmtElapsed(secs) {
if (secs < 60) return secs + 's running';
if (secs < 3600) return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's running';
return Math.floor(secs / 3600) + 'h ' + Math.floor((secs % 3600) / 60) + 'm running';
}
// Compact duration label, two units deep ("1h 5m", "2d 3h").
export function fmtDuration(secs) {
if (secs < 60) return secs + 's';
if (secs < 3600) return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's';
if (secs < 86400) return Math.floor(secs / 3600) + 'h ' + Math.floor((secs % 3600) / 60) + 'm';
return Math.floor(secs / 86400) + 'd ' + Math.floor((secs % 86400) / 3600) + 'h';
}