Add real time, match duration, and round context to chat
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { parseGameChatTimeline, type ChatEntry } from '$lib/game-chat.js';
|
||||
import type { ChatContext, ContextualChatEntry } from '$lib/game-chat.js';
|
||||
import { formatDuration } from '$ui/duration.js';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import Button from './primitives/Button.svelte';
|
||||
|
||||
let { server, game }: { server: string; game: string | null } = $props();
|
||||
let entries = $state<ChatEntry[]>([]);
|
||||
let { server }: { server: string } = $props();
|
||||
let entries = $state<ContextualChatEntry[]>([]);
|
||||
let hasMessages = $derived(entries.some(entry => entry.type === 'message'));
|
||||
let loaded = $state(false);
|
||||
let loading = $state(false);
|
||||
@@ -21,12 +23,11 @@
|
||||
const request = new AbortController();
|
||||
controller = request;
|
||||
try {
|
||||
const key = game === 't6' ? 'games_zm' : 'games_sp';
|
||||
const response = await fetch(`/api/servers/${encodeURIComponent(server)}/logs/${key}?lines=2000`, { signal: request.signal });
|
||||
const response = await fetch(`/api/servers/${encodeURIComponent(server)}/chat`, { signal: request.signal });
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.ok) throw new Error(data.reason ?? 'Could not refresh chat.');
|
||||
if (request.signal.aborted) return;
|
||||
entries = parseGameChatTimeline(data.lines ?? []);
|
||||
entries = data.entries ?? [];
|
||||
missing = !!data.missing;
|
||||
loaded = true;
|
||||
error = '';
|
||||
@@ -51,6 +52,17 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet chatContext(context: ChatContext)}
|
||||
<dl class="context" aria-label="Message context">
|
||||
<div>
|
||||
<dt>Real time</dt>
|
||||
<dd>{#if context.realTime}<time datetime={context.realTime} title={`Estimated real time: ${$timeFormat.dateTime(context.realTime)}`}>≈ {$timeFormat.time(context.realTime)}</time>{:else}—{/if}</dd>
|
||||
</div>
|
||||
<div><dt>Match</dt><dd>{context.matchElapsedMs === null ? '—' : formatDuration(context.matchElapsedMs)}</dd></div>
|
||||
<div><dt>Round</dt><dd>{context.round ?? '—'}</dd></div>
|
||||
</dl>
|
||||
{/snippet}
|
||||
|
||||
<section aria-label="Player chat">
|
||||
<div class="chat-head">
|
||||
<div><h2>Chat</h2><p>Recent messages from the game log · refreshes every 5 seconds</p></div>
|
||||
@@ -71,22 +83,22 @@
|
||||
{#each entries as entry}
|
||||
{#if entry.type === 'boundary'}
|
||||
<li class="match-divider" class:match-end={entry.event === 'end'}>
|
||||
<span class="match-label">
|
||||
{entry.event === 'start' ? 'Match started' : entry.event === 'end' ? 'Match ended' : 'New match · timer reset'}
|
||||
{#if entry.time}<span class="time" title="Elapsed game-log time">{entry.time}</span>{/if}
|
||||
</span>
|
||||
<div class="match-label">
|
||||
<span>{entry.event === 'start' ? 'Match started' : entry.event === 'end' ? 'Match ended' : 'New match · timer reset'}</span>
|
||||
{@render chatContext(entry.context)}
|
||||
</div>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="message">
|
||||
<span class="time" title="Elapsed game-log time">{entry.message.time}</span>
|
||||
<div><strong>{entry.message.player}</strong><p>{entry.message.message}</p></div>
|
||||
<div class="message-head"><strong>{entry.message.player}</strong>{@render chatContext(entry.context)}</div>
|
||||
<p>{entry.message.message}</p>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="note">Times are elapsed game-log times. Older messages may fall outside the recent log window.</p>
|
||||
<p class="note">Real times are estimated from log writes. — means context is unavailable. Older messages may fall outside the recent log window.</p>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
@@ -95,12 +107,17 @@
|
||||
.chat-head p, .note, .empty { color: var(--text-secondary); font-size: var(--text-sm); }
|
||||
.messages { max-height: 60vh; overflow: auto; margin-top: 16px; }
|
||||
ol { list-style: none; padding: 0; margin: 0; }
|
||||
.message { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border-subtle); }
|
||||
.message { padding: 12px 0; border-bottom: 1px solid var(--border-subtle); }
|
||||
.message-head { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 6px 20px; }
|
||||
.context { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 16px; margin: 0; font-size: var(--text-sm); font-weight: 400; }
|
||||
.context > div { display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
|
||||
.context dt { color: var(--text-tertiary); }
|
||||
.context dd { margin: 0; color: var(--text-secondary); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
||||
.match-divider { display: flex; align-items: center; gap: 12px; padding: 20px 0; color: var(--text-primary); font-size: var(--text-sm); }
|
||||
.match-divider::before, .match-divider::after { content: ''; height: 1px; flex: 1; background: var(--border-subtle); }
|
||||
.match-label { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px 12px; padding: 6px 12px; border: 1px solid var(--border-subtle); border-radius: 999px; font-weight: 600; }
|
||||
.match-label { display: flex; align-items: center; flex-direction: column; gap: 6px; max-width: 100%; padding: 8px 14px; border: 1px solid var(--border-subtle); border-radius: 12px; font-weight: 600; text-align: center; }
|
||||
.match-label .context { justify-content: center; }
|
||||
.match-end { color: var(--text-secondary); }
|
||||
.time { font-family: var(--font-mono); color: var(--text-tertiary); font-size: var(--text-sm); }
|
||||
strong { color: var(--text-primary); overflow-wrap: anywhere; }
|
||||
li p { margin: 4px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.empty { padding: 24px 0; }
|
||||
|
||||
Reference in New Issue
Block a user