import { useEffect, useMemo, useState } from 'react'; import { useSession } from '../context/SessionContext.jsx'; import { useSettingsNamespace } from '../settings/index.js'; function normalizeSources(list = []) { return list .map((entry) => { if (!entry?.type || !entry?.id) return null; return { type: entry.type, id: String(entry.id), label: entry.label || String(entry.id), key: `${entry.type}:${entry.id}`, }; }) .filter(Boolean); } export default function ReplaySourcesPanel({ panelId = 'replay-sources' }) { const { session, triggerReplay } = useSession(); const sources = normalizeSources(session?.replaySources || []); const { value: settings, save: saveSettings } = useSettingsNamespace('replaySources', {}); const [selected, setSelected] = useState([]); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const replayState = session?.replay || null; const [remainingMs, setRemainingMs] = useState(0); const defaults = useMemo(() => { const roverId = session?.assignment?.roverId; if (roverId) { return [`rover:${roverId}`]; } return []; }, [session?.assignment?.roverId]); useEffect(() => { const saved = settings?.[panelId]; if (Array.isArray(saved) && saved.length) { setSelected(saved); return; } setSelected(defaults); }, [settings?.[panelId], defaults, panelId]); useEffect(() => { const allowed = new Set(sources.map((source) => source.key)); setSelected((prev) => prev.filter((key) => allowed.has(key))); }, [sources]); const grouped = useMemo(() => { return { rovers: sources.filter((source) => source.type === 'rover'), rooms: sources.filter((source) => source.type === 'room'), }; }, [sources]); useEffect(() => { if (!replayState?.lastTriggeredAt || !replayState?.cooldownMs) { setRemainingMs(0); return undefined; } const update = () => { const next = replayState.lastTriggeredAt + replayState.cooldownMs - Date.now(); setRemainingMs(Math.max(0, next)); }; update(); const interval = setInterval(update, 250); return () => clearInterval(interval); }, [replayState?.lastTriggeredAt, replayState?.cooldownMs, replayState?.remainingMs]); const replayDisabled = busy || session?.mode === 'lockdown' || remainingMs > 0 || !selected.length; const toggleKey = (key) => { setSelected((prev) => { const next = prev.includes(key) ? prev.filter((value) => value !== key) : [...prev, key]; saveSettings((current) => ({ ...(current || {}), [panelId]: next })); return next; }); setSuccess(null); }; const handleReplay = async () => { if (replayDisabled) return; setBusy(true); setError(null); setSuccess(null); try { const payload = selected.map((key) => { const [type, id] = key.split(':'); return { type, id }; }); await triggerReplay(payload); setSuccess('Replay sent. Check the Discord replay channel.'); } catch (err) { setError(err.message); } finally { setBusy(false); } }; return (
Replay Sources {sources.length}
{error ?
{error}
: null} {success ?
{success}
: null}
); } function GroupList({ title, items, selected, onToggle }) { if (!items.length) return null; return (
{title}
{items.map((item) => ( ))}
); }