Add active/inactive/game-time timers to fleet + server pages
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.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import StatusDot from './StatusDot.svelte';
|
||||
import type { RosterPlayer, Health } from '$ui/stores/servers.js';
|
||||
import { uiNow } from '$ui/stores/servers.js';
|
||||
import { gametypeLabel } from '$ui/gametype.js';
|
||||
import { elapsedSince } from '$ui/duration.js';
|
||||
|
||||
interface Held {
|
||||
target: string;
|
||||
@@ -19,6 +21,10 @@
|
||||
held?: Held[];
|
||||
lastRestartedAt?: string | null;
|
||||
restarting?: boolean;
|
||||
activeSince?: string | null;
|
||||
inactiveSince?: string | null;
|
||||
gameStartedAt?: string | null;
|
||||
gameEndedAt?: string | null;
|
||||
}
|
||||
let {
|
||||
name,
|
||||
@@ -31,7 +37,11 @@
|
||||
lastError = null,
|
||||
held = [],
|
||||
lastRestartedAt = null,
|
||||
restarting = false
|
||||
restarting = false,
|
||||
activeSince = null,
|
||||
inactiveSince = null,
|
||||
gameStartedAt = null,
|
||||
gameEndedAt = null
|
||||
}: Props = $props();
|
||||
|
||||
function ago(iso: string | null): string {
|
||||
@@ -52,6 +62,16 @@
|
||||
let showRoster = $derived(health !== 'offline');
|
||||
let mapLabel = $derived(map ?? '—');
|
||||
let gtLabel = $derived(gametypeLabel(gametype));
|
||||
|
||||
// Live-ticking occupancy + match timers. Anchored to server-issued
|
||||
// ISO timestamps and re-derived from a shared 1s ticker.
|
||||
let activeElapsed = $derived(activeSince ? elapsedSince(activeSince, $uiNow) : null);
|
||||
let inactiveElapsed = $derived(inactiveSince ? elapsedSince(inactiveSince, $uiNow) : null);
|
||||
let gameInProgress = $derived(!!gameStartedAt && !gameEndedAt);
|
||||
let gameElapsed = $derived(gameInProgress ? elapsedSince(gameStartedAt, $uiNow) : null);
|
||||
let showTiming = $derived(
|
||||
health !== 'offline' && (activeElapsed !== null || inactiveElapsed !== null || gameElapsed !== null)
|
||||
);
|
||||
</script>
|
||||
|
||||
<a class="card" href="/servers/{name}" class:restarting class:offline={health === 'offline'}>
|
||||
@@ -77,6 +97,28 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if showTiming}
|
||||
<div class="timing">
|
||||
{#if activeElapsed}
|
||||
<span class="timing-item active">
|
||||
<span class="label">Active</span>
|
||||
<span class="value">{activeElapsed}</span>
|
||||
</span>
|
||||
{:else if inactiveElapsed}
|
||||
<span class="timing-item inactive">
|
||||
<span class="label">Inactive</span>
|
||||
<span class="value">{inactiveElapsed}</span>
|
||||
</span>
|
||||
{/if}
|
||||
{#if gameElapsed}
|
||||
<span class="timing-item game">
|
||||
<span class="label">Game</span>
|
||||
<span class="value">{gameElapsed}</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showRoster}
|
||||
{#if topRoster.length > 0}
|
||||
<ul class="roster">
|
||||
@@ -244,6 +286,29 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timing {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-mono-sm);
|
||||
}
|
||||
.timing-item {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
.timing-item .label {
|
||||
color: var(--ink-500);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-label);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.timing-item .value { color: var(--cream); }
|
||||
.timing-item.active .value { color: var(--spark-500); }
|
||||
.timing-item.inactive .value { color: var(--ink-300); }
|
||||
.timing-item.game .value { color: var(--accent, var(--spark-500)); }
|
||||
|
||||
.lobby-empty {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-mono-sm);
|
||||
|
||||
Reference in New Issue
Block a user