Fleet cards and the per-server detail hero now show three live-ticking timers: Active (since players > 0), Inactive (since server emptied), and Game (since InitGame in games_sp.log, back-computed from the log's own MM:SS prefix so it stays accurate across restarts). Anchors are persisted on `servers` and fanned out via the SSE bus; a single 1s ticker feeds every card. Idle map cycles are filtered out so the game timer doesn't flicker.
28 lines
1008 B
TypeScript
28 lines
1008 B
TypeScript
/**
|
|
* Format an elapsed millisecond count as a compact human string:
|
|
* < 60s -> "12s"
|
|
* < 1h -> "5m 23s"
|
|
* >= 1h -> "2h 05m"
|
|
* Negatives clamp to 0s (clock skew tolerance).
|
|
*/
|
|
export function formatDuration(ms: number): string {
|
|
if (!Number.isFinite(ms) || ms < 0) ms = 0;
|
|
const totalSeconds = Math.floor(ms / 1000);
|
|
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = totalSeconds % 60;
|
|
if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;
|
|
const hours = Math.floor(minutes / 60);
|
|
const mins = minutes % 60;
|
|
return `${hours}h ${String(mins).padStart(2, '0')}m`;
|
|
}
|
|
|
|
/**
|
|
* Convenience: elapsed since an ISO timestamp, formatted. Returns null when
|
|
* the anchor is null so callers can `{#if}` on it.
|
|
*/
|
|
export function elapsedSince(iso: string | null, now: number): string | null {
|
|
if (!iso) return null;
|
|
return formatDuration(now - new Date(iso).getTime());
|
|
}
|