spectator updates

This commit is contained in:
legop3
2025-11-24 00:40:11 -05:00
parent c1c78585ba
commit 9ba7f8fd7a
6 changed files with 21 additions and 51 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-Cx2yYVpk.js"></script> <script type="module" crossorigin src="/assets/index-Bu0ya-aM.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-sMfSpjY4.css"> <link rel="stylesheet" crossorigin href="/assets/index-B8zLJlAS.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+5 -4
View File
@@ -39,11 +39,12 @@ function buildWhepUrlForSource(source) {
function passesMode(socket) { function passesMode(socket) {
const mode = getMode(); const mode = getMode();
if (mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) { if (mode === MODES.LOCKDOWN) {
return false; return isLockdownAdmin(socket);
} }
if (mode === MODES.ADMIN && !isAdmin(socket)) { if (mode === MODES.ADMIN) {
return false; const role = getRole(socket);
return role === 'spectator' || isAdmin(socket);
} }
return true; return true;
} }
+4 -35
View File
@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import { SettingsProvider } from '../settings/index.js'; import { SettingsProvider } from '../settings/index.js';
import { useSession } from '../context/SessionContext.jsx'; import { useSession } from '../context/SessionContext.jsx';
import { useSpectatorMode } from '../hooks/useSpectatorMode.js'; import { useSpectatorMode } from '../hooks/useSpectatorMode.js';
@@ -10,22 +9,6 @@ import UserListPanel from '../components/UserListPanel.jsx';
import ChatPanel from '../components/ChatPanel.jsx'; import ChatPanel from '../components/ChatPanel.jsx';
import LogPanel from '../components/LogPanel.jsx'; import LogPanel from '../components/LogPanel.jsx';
function SpectatorStatus({ connected, ready, rosterCount, mode, blocked }) {
return (
<div className="flex flex-wrap items-center gap-0.5 text-sm">
<span className={`px-0.5 py-0.5 font-semibold ${connected ? 'bg-emerald-500/20 text-emerald-200' : 'bg-red-500/20 text-red-200'}`}>
{connected ? 'Connected' : 'Disconnected'}
</span>
<span className={`px-0.5 py-0.5 ${ready ? 'bg-blue-600/40 text-blue-100' : 'bg-slate-700 text-slate-200'}`}>
Spectator mode {ready ? 'active' : 'pending…'}
</span>
<span className="bg-slate-800 px-0.5 py-0.5 text-slate-200">Rovers: {rosterCount}</span>
<span className="bg-slate-800 px-0.5 py-0.5 text-slate-200">Mode: {mode || 'unknown'}</span>
{blocked && <span className="bg-red-700/60 px-0.5 py-0.5 text-red-100">Spectate blocked</span>}
</div>
);
}
function TelemetrySummary({ frame }) { function TelemetrySummary({ frame }) {
const sensors = frame?.sensors || {}; const sensors = frame?.sensors || {};
const updated = frame?.receivedAt ? new Date(frame.receivedAt).toLocaleTimeString() : null; const updated = frame?.receivedAt ? new Date(frame.receivedAt).toLocaleTimeString() : null;
@@ -87,7 +70,7 @@ function RoverRow({ roster, frames, videoSources, session }) {
return <p className="col-span-full text-slate-400">No rovers registered.</p>; return <p className="col-span-full text-slate-400">No rovers registered.</p>;
} }
return ( return (
<section className="grid grid-cols-1 gap-0.5 md:grid-cols-2 xl:grid-cols-3"> <section className="grid grid-cols-1 gap-0.5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{roster.map((rover) => ( {roster.map((rover) => (
<RoverSpectatorCard <RoverSpectatorCard
key={rover.id} key={rover.id}
@@ -120,30 +103,16 @@ function LogsRow() {
} }
export default function SpectatorApp() { export default function SpectatorApp() {
const { connected, session } = useSession(); const { session } = useSession();
const spectatorReady = useSpectatorMode(); useSpectatorMode();
const frames = useTelemetryFrames(); const frames = useTelemetryFrames();
const roster = session?.roster ?? []; const roster = session?.roster ?? [];
const videoSources = useVideoRequests(roster.map((rover) => rover.id)); const videoSources = useVideoRequests(roster.map((rover) => rover.id));
const blocked = session?.mode === 'lockdown';
const status = useMemo(
() => ({ connected, ready: spectatorReady, rosterCount: roster.length, mode: session?.mode, blocked }),
[blocked, connected, roster.length, session?.mode, spectatorReady],
);
return ( return (
<SettingsProvider> <SettingsProvider>
<div className="min-h-screen bg-black text-slate-100"> <div className="min-h-screen bg-black text-slate-100">
<main className="mx-auto flex max-w-7xl flex-col gap-0.5 px-0.5 py-0.5"> <main className="flex flex-col gap-0.5 p-0.5">
<header className="space-y-0.5">
<h1 className="text-4xl font-semibold text-white">Spectator Console</h1>
<p className="text-slate-400">Live rover telemetry & logs. This view is read-only.</p>
<SpectatorStatus {...status} />
{blocked && (
<p className="text-sm text-amber-300">Lockdown active. Spectator view will resume automatically when access returns.</p>
)}
</header>
<RoverRow roster={roster} frames={frames} videoSources={videoSources} session={session} /> <RoverRow roster={roster} frames={frames} videoSources={videoSources} session={session} />
<SecondaryRow /> <SecondaryRow />
<LogsRow /> <LogsRow />