jevstrudel.git / website / src / jev / JevLog.jsx
JevLog.jsx38 lines · 1.8 KB · raw
1// Jev's request log (jevLog.mjs), in the pane under the editor: every
2// request this page made of Jev, newest on top, pushing the older ones down
3// out of view. A section's line says it is asking until the answer lands,
4// then what Jev decided and how long it took.
5import { useSyncExternalStore } from 'react';
6import { jevLog } from './jevLog.mjs';
7import { themeColor as ACCENT } from './site.mjs';
8
9// a pick's line starts with what asked for it (⏭ or 📻), so it has no icon of its own
10const ICON = { asking: '…', answered: '🎧', fallback: '⚠', ended: '·' };
11const time = (at) => new Date(at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
12
13export function JevLog() {
14  const entries = useSyncExternalStore(jevLog.subscribe, jevLog.get, jevLog.get);
15  return (
16    <div className="flex flex-col min-h-0 h-full font-mono text-xs" aria-label="Jev's requests" data-testid="jev-log">
17      <div className="px-3 py-1 font-bold shrink-0" style={{ color: ACCENT }}>
18        requests
19      </div>
20      <ol className="overflow-y-auto grow min-h-0 px-3 pb-2 space-y-1">
21        {!entries.length && <li className="opacity-60">none yet: Jev's requests show here as the song asks them</li>}
22        {entries.map((e) => (
23          <li
24            key={e.id}
25            className="leading-snug"
26            style={{ color: e.status === 'fallback' || e.status === 'error' ? '#f87171' : undefined }}
27          >
28            <span className="opacity-50">{time(e.at)} </span>
29            {ICON[e.status] && <span className={e.status === 'asking' ? 'animate-pulse' : ''}>{ICON[e.status]} </span>}
30            {e.song && <span className="opacity-60">{e.song} · </span>}
31            {e.text}
32            {typeof e.ms === 'number' && <span className="opacity-50"> · {e.ms} ms</span>}
33          </li>
34        ))}
35      </ol>
36    </div>
37  );
38}