Repository Public
1/** Formatting helpers. Null/undefined always renders as an em dash: the UI never invents values. */2export const DASH = '—';34export function fmtBytes(n: number | null | undefined): string {5 if (n == null || !Number.isFinite(n)) return DASH;6 if (n < 1024) return `${n} B`;7 const units = ['KiB', 'MiB', 'GiB', 'TiB'];8 let v = n / 1024;9 let i = 0;10 while (v >= 1024 && i < units.length - 1) {11 v /= 1024;12 i++;13 }14 return `${v >= 100 ? v.toFixed(0) : v.toFixed(1)} ${units[i]}`;15}16export function fmtNum(n: number | null | undefined, digits = 0, unit = ''): string {17 if (n == null || !Number.isFinite(n)) return DASH;18 return `${n.toLocaleString('en-CA', { minimumFractionDigits: digits, maximumFractionDigits: digits })}${unit}`;19}20export function fmtMbAsGb(mb: number | null | undefined, digits = 0): string {21 return mb == null ? DASH : `${(mb / 1024).toFixed(digits)} GB`;22}23export function fmtPct(n: number | null | undefined, digits = 0): string {24 return n == null || !Number.isFinite(n) ? DASH : `${n.toFixed(digits)} %`;25}26export function fmtTime(iso: string | number | null | undefined): string {27 if (iso == null) return DASH;28 const d = new Date(iso);29 return Number.isNaN(d.getTime()) ? DASH : d.toLocaleTimeString('en-CA', { hour12: false });30}31export function fmtDateTime(iso: string | number | null | undefined): string {32 if (iso == null) return DASH;33 const d = new Date(iso);34 if (Number.isNaN(d.getTime())) return DASH;35 return `${d.toLocaleDateString('en-CA')} ${d.toLocaleTimeString('en-CA', { hour12: false })}`;36}37export function fmtRelative(iso: string | number | null | undefined, nowMs = Date.now()): string {38 if (iso == null) return DASH;39 const t = new Date(iso).getTime();40 if (Number.isNaN(t)) return DASH;41 const s = Math.round((nowMs - t) / 1000);42 if (s < 5) return 'just now';43 if (s < 60) return `${s} s ago`;44 if (s < 3600) return `${Math.floor(s / 60)} min ago`;45 if (s < 86400) return `${Math.floor(s / 3600)} h ago`;46 if (s < 86400 * 30) return `${Math.floor(s / 86400)} d ago`;47 return new Date(t).toLocaleDateString('en-CA');48}49export function fmtDuration(sec: number | null | undefined): string {50 if (sec == null || !Number.isFinite(sec) || sec < 0) return DASH;51 const s = Math.floor(sec);52 if (s < 60) return `${s} s`;53 if (s < 3600) return `${Math.floor(s / 60)} min ${String(s % 60).padStart(2, '0')} s`;54 if (s < 86400) return `${Math.floor(s / 3600)} h ${String(Math.floor((s % 3600) / 60)).padStart(2, '0')} min`;55 return `${Math.floor(s / 86400)} d ${Math.floor((s % 86400) / 3600)} h`;56}57export function basename(p: string): string {58 const i = p.lastIndexOf('/');59 return i < 0 ? p : p.slice(i + 1);60}61export function dirname(p: string): string {62 const i = p.lastIndexOf('/');63 return i < 0 ? '' : p.slice(0, i);64}65export function joinPath(dir: string, name: string): string {66 return dir ? `${dir}/${name}` : name;67}68export const isMac = (): boolean => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);69