mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
driver removal information finaly!
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user