driver removal information finaly!

This commit is contained in:
legop3
2026-07-05 22:45:38 -04:00
parent df22ac6d81
commit b526beb712
18 changed files with 502 additions and 168 deletions
+41 -12
View File
@@ -11,6 +11,46 @@ import LowBatteryOverlay from '../HudOverlays/LowBatteryOverlay/index.jsx';
import DriverBottomStrip from '../HudOverlays/DriverBottomStrip/index.jsx';
import HudChatInput from '../HudOverlays/HudChatInput/index.jsx';
import CardFrame from '../CardFrame/index.jsx';
import { useSharedClock } from '../../hooks/useSharedClock.js';
const REMOVAL_NOTICE_VISIBLE_MS = 2 * 60 * 1000;
function EmptyDriverVideoNotice() {
const removalNotice = useSessionSelector((state) => state.roverRemovalNotice || null);
const now = useSharedClock(1000, Boolean(removalNotice?.receivedAt));
const noticeAgeMs = removalNotice?.receivedAt ? now - removalNotice.receivedAt : Infinity;
/*
Removal explanations should feel immediate and contextual. After a short
window, falling back to the neutral no-rover state avoids showing an old
moderation/safety message during unrelated later waiting periods.
*/
const showRemovalNotice = Boolean(removalNotice?.message && noticeAgeMs <= REMOVAL_NOTICE_VISIBLE_MS);
const title = showRemovalNotice ? removalNotice.title || 'Removed from rover' : 'No rover assigned';
const message = showRemovalNotice
? removalNotice.message
: 'You are not currently assigned to a rover.';
return (
<CardFrame hideHeader className="shrink-0">
<div className="panel-muted flex aspect-[4/3] items-center justify-center p-4 text-center">
<div
className={`mx-auto flex max-w-md flex-col gap-1 rounded border px-4 py-3 ${
showRemovalNotice
? 'border-amber-300/60 bg-amber-950/35 text-amber-50'
: 'border-slate-700/70 bg-slate-950/35 text-slate-300'
}`}
>
<div className={showRemovalNotice ? 'text-sm font-semibold text-amber-100' : 'text-sm font-semibold text-slate-200'}>
{title}
</div>
<div className={showRemovalNotice ? 'text-sm text-amber-50/90' : 'text-sm text-slate-400'}>
{message}
</div>
</div>
</div>
</CardFrame>
);
}
export default function DriverVideo({ layoutFormat = 'desktop' }) {
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
@@ -18,18 +58,7 @@ export default function DriverVideo({ layoutFormat = 'desktop' }) {
const lastControlIntentAt = useControlSelector((control) => control.state.lastControlIntentAt);
if (!roverId) {
return (
<CardFrame hideHeader className="shrink-0">
<div className="panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]">
<p>You are not assigned to a rover.</p>
<p className="mt-0">
<a href="/spectate" className="text-blue-400 underline hover:text-blue-500">
Click here to visit the spectator page.
</a>
</p>
</div>
</CardFrame>
);
return <EmptyDriverVideoNotice />;
}
const mobileHud = layoutFormat !== 'desktop';
+28
View File
@@ -19,6 +19,7 @@ const INITIAL_STATE = {
latestReplay: null,
latestRequestedReplay: null,
duplicateIdentityBlock: null,
roverRemovalNotice: null,
};
const SessionContext = createContext(null);
@@ -304,6 +305,31 @@ export function SessionProvider({ children }) {
},
}));
}
function handleRoverRemovalNotice(payload = {}) {
/*
Removal reasons arrive as socket events because the next normal session
sync only says "not assigned". Keeping the explanation outside the
session tree lets the no-rover video panel tell the user why control was
removed after admin, safety, or idle-removal actions.
*/
const message =
typeof payload?.message === 'string' && payload.message.trim()
? payload.message.trim()
: 'You were removed from the rover.';
const title =
typeof payload?.title === 'string' && payload.title.trim()
? payload.title.trim()
: 'Removed from rover';
setState((prev) => ({
...prev,
roverRemovalNotice: {
...payload,
title,
message,
receivedAt: Date.now(),
},
}));
}
socket.on('session:sync', handleSession);
socket.on('log:init', handleLogInit);
socket.on('log:entry', handleLogEntry);
@@ -317,6 +343,7 @@ export function SessionProvider({ children }) {
socket.on('replay:ready', handleReplayReady);
socket.on('replay:failed', handleReplayFailed);
socket.on('session:duplicateIdentity', handleDuplicateIdentity);
socket.on('session:roverRemovalNotice', handleRoverRemovalNotice);
return () => {
socket.off('session:sync', handleSession);
socket.off('log:init', handleLogInit);
@@ -331,6 +358,7 @@ export function SessionProvider({ children }) {
socket.off('replay:ready', handleReplayReady);
socket.off('replay:failed', handleReplayFailed);
socket.off('session:duplicateIdentity', handleDuplicateIdentity);
socket.off('session:roverRemovalNotice', handleRoverRemovalNotice);
};
}, [setState, socket]);