Show live match links and elapsed time (Refs #115)
CI / checks (push) Successful in 5m0s

This commit is contained in:
2026-10-09 23:47:46 -04:00
parent 3e0b4db736
commit a0663e43b8
2 changed files with 69 additions and 13 deletions
+31 -13
View File
@@ -2,9 +2,12 @@
import RelativeTime from '$ui/components/RelativeTime.svelte';
import { untrack } from 'svelte';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { contextHref } from '$lib/navigation-context.js';
import { timestampISO } from '$lib/time-zone.js';
import { goto } from '$app/navigation';
import { timeFormat, timeZone } from '$ui/stores/time-zone.js';
import { uiNow } from '$ui/stores/servers.js';
import type { MatchHistoryResponse } from '$lib/match-history.js';
import {
matchApiParams,
@@ -16,7 +19,7 @@
} from '$lib/match-history-query.js';
import { matchInstanceLabel } from '$ui/match-history.js';
import { pollVisible } from '$ui/poll.js';
import { formatDuration } from '$ui/duration.js';
import { elapsedSince, formatDuration } from '$ui/duration.js';
import Button from './primitives/Button.svelte';
import EmptyState from './primitives/EmptyState.svelte';
import Dialog from './primitives/Dialog.svelte';
@@ -33,6 +36,7 @@
let tableRegion = $state<HTMLDivElement>();
let pendingUrl: URL | undefined;
let navigationId = 0;
const now = $derived(browser ? $uiNow : Date.now());
const filters = $derived(readMatchQuery(matchApiParams($page.url)));
const apiUrl = $derived.by(() => {
const params = matchApiParams($page.url);
@@ -308,7 +312,7 @@
? 'Unique players observed throughout the match'
: item.key === 'round'
? 'Final round, or highest observed round for unfinished matches'
: 'Recorded duration; unknown durations sort last'}
: 'Recorded duration; in-progress and unknown durations sort last'}
aria-describedby={item.key === 'players'
? 'match-player-help'
: undefined}
@@ -324,6 +328,12 @@
>
<tbody
>{#each data.matches as match (match.id)}
{@const duration =
match.status === 'active'
? elapsedSince(timestampISO(match.startedAt) ?? null, now)
: match.durationMs === null
? null
: formatDuration(match.durationMs)}
<tr>
<td><RelativeTime value={match.startedAt} /></td>
<td class="match">
@@ -333,6 +343,15 @@
aria-label={`View ${match.map} match ${match.id} from ${$timeFormat.dateTime(match.startedAt)}`}
>{match.map} · Match #{match.id}</a
>
{:else if match.status === 'active' && !match.server_deleted_at}<a
class="match-link"
href={contextHref(
`/servers/${encodeURIComponent(match.server)}`,
$page.url
)}
aria-label={`View live ${match.map} match on ${matchInstanceLabel(match)}`}
>{match.map}</a
>
{:else}<span class="match-name">{match.map}</span>{/if}
<small class="mobile-match-context"
><RelativeTime value={match.startedAt} /> · {match.status ===
@@ -340,8 +359,8 @@
? 'In progress'
: match.status === 'completed'
? 'Completed'
: 'Interrupted'}{match.durationMs !== null
? ` · ${formatDuration(match.durationMs)}`
: 'Interrupted'}{duration !== null
? ` · ${duration}`
: ''}</small
>
{#if server === undefined}<div class="instance">
@@ -366,12 +385,9 @@
<td class="numeric">{match.uniquePlayers}</td>
<td class="numeric"
><span
title={match.durationMs === null
? 'Duration was not recorded or the match has not completed.'
: undefined}
>{match.durationMs === null
? '—'
: formatDuration(match.durationMs)}</span
title={duration === null
? 'Duration was not recorded.'
: undefined}>{duration ?? '—'}</span
></td
>
<td class="numeric"
@@ -423,9 +439,11 @@
<summary>About match history</summary>
<p>
Players counts unique players across all reports. A dash means the value is unavailable,
not zero. Duration requires a recorded start and completed ending. Round shows the final
round for completed matches and the highest observed round for unfinished matches. Open
a completed match for exact timestamps and starting and ending player counts.
not zero. In-progress matches show elapsed time since their recorded start and link to
the live server. Completed durations require a recorded start and completed ending.
Round shows the final round for completed matches and the highest observed round for
unfinished matches. Open a completed match for exact timestamps and starting and ending
player counts.
</p>
</details>
</section>