feat(frontend): add HistoryCard + HistoryList
This commit is contained in:
parent
65d88be032
commit
01c281ca80
3 changed files with 88 additions and 0 deletions
12
frontend/src/components/HistoryCard.astro
Normal file
12
frontend/src/components/HistoryCard.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
import { HistoryList } from './HistoryList';
|
||||
interface Props { station: string; }
|
||||
const { station } = Astro.props;
|
||||
---
|
||||
<div class="card history">
|
||||
<div class="full-secthead">
|
||||
<div class="full-secthead-jp">再生履歴</div>
|
||||
<div class="full-secthead-en">// recently played</div>
|
||||
</div>
|
||||
<HistoryList station={station} client:visible />
|
||||
</div>
|
||||
48
frontend/src/components/HistoryList.tsx
Normal file
48
frontend/src/components/HistoryList.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import type { HistoryEntry } from '@lib/types';
|
||||
import { fmtRelative } from '@lib/format';
|
||||
|
||||
interface Props {
|
||||
station: string;
|
||||
}
|
||||
|
||||
export function HistoryList({ station }: Props) {
|
||||
const [entries, setEntries] = useState<HistoryEntry[] | null>(null);
|
||||
const [now, setNow] = useState(() => new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const ctrl = new AbortController();
|
||||
const load = async () => {
|
||||
try {
|
||||
const r = await fetch(`/now-playing/${station}.history.json`, { signal: ctrl.signal });
|
||||
if (!r.ok) { setEntries([]); return; }
|
||||
const arr = await r.json() as HistoryEntry[];
|
||||
setEntries(Array.isArray(arr) ? arr : []);
|
||||
} catch (err) {
|
||||
if ((err as Error).name !== 'AbortError') setEntries([]);
|
||||
}
|
||||
};
|
||||
void load();
|
||||
const id = setInterval(load, 15_000);
|
||||
const tick = setInterval(() => setNow(new Date()), 30_000);
|
||||
return () => { ctrl.abort(); clearInterval(id); clearInterval(tick); };
|
||||
}, [station]);
|
||||
|
||||
if (entries === null) return <div className="history-empty">loading…</div>;
|
||||
if (!entries.length) return <div className="history-empty">no recent tracks</div>;
|
||||
|
||||
return (
|
||||
<div className="history-list">
|
||||
{entries.slice(0, 10).map((e, i) => {
|
||||
const start = new Date(parseInt(e.started_at, 10) * 1000);
|
||||
return (
|
||||
<div key={`${e.started_at}-${i}`} className="history-row">
|
||||
<span className="t">{fmtRelative(start, now)}</span>
|
||||
<span className="title">{e.title || '—'}</span>
|
||||
<span className="artist">{e.artist || '—'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
frontend/src/styles/components/history-card.css
Normal file
28
frontend/src/styles/components/history-card.css
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
.full-secthead { margin-bottom: 14px; }
|
||||
.full-secthead-jp { font-family: var(--f-pixel); font-size: 18px; color: var(--lemon); letter-spacing: 4px; }
|
||||
.full-secthead-en { font-family: var(--f-pixel); font-size: 22px; color: var(--cream); letter-spacing: 1px; margin-top: 2px; }
|
||||
|
||||
.card {
|
||||
background: var(--plum);
|
||||
border: 2px solid var(--ink);
|
||||
padding: 22px 24px;
|
||||
position: relative;
|
||||
}
|
||||
.card::before {
|
||||
content: ""; position: absolute; inset: 4px; border: 1px dashed #ff3ea522; pointer-events: none;
|
||||
}
|
||||
|
||||
.history-list { display: flex; flex-direction: column; }
|
||||
.history-row {
|
||||
display: grid; grid-template-columns: 50px 1fr auto;
|
||||
gap: 12px; padding: 8px 4px;
|
||||
border-bottom: 1px dashed #ff3ea522;
|
||||
font-size: 13px;
|
||||
}
|
||||
.history-row:last-child { border-bottom: none; }
|
||||
.history-row .t { font-family: var(--f-mono); color: var(--cyan); }
|
||||
.history-row .title { font-family: var(--f-pixel); font-size: 14px; color: var(--cream); }
|
||||
.history-row .artist { font-family: var(--f-mono); color: #fff4e8aa; font-size: 12px; }
|
||||
.history-empty {
|
||||
font-family: var(--f-mono); font-size: 13px; color: #fff4e8aa; padding: 8px 4px;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue