mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
loading screen, better HUD, etc.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 337 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 MiB |
@@ -18,42 +18,69 @@ import ExpansionPanel from '../CornerPods/ExpansionPanel.jsx';
|
||||
import usePodVisibility from '../CornerPods/usePodVisibility.js';
|
||||
|
||||
function DockedAction({ driveKeyLabel, pending, controlsDisabled, error, onUndock }) {
|
||||
const [hidden, setHidden] = useState(false);
|
||||
const waitingForTurn = controlsDisabled && !pending;
|
||||
|
||||
/* The dismissal belongs to this mounted docked episode. DockingHud unmounts
|
||||
this component when the rover leaves the base, and its roverId key remounts
|
||||
it for a different assignment, so no persistence or reset effect is needed. */
|
||||
if (hidden && !pending) return null;
|
||||
|
||||
const mainToneClass = waitingForTurn
|
||||
? 'cursor-not-allowed bg-slate-950/95 ring-slate-400/70'
|
||||
: 'bg-emerald-950/90 ring-emerald-300/80 hover:bg-emerald-900/95 focus-visible:ring-emerald-200 disabled:cursor-wait disabled:opacity-75';
|
||||
const hideToneClass = waitingForTurn
|
||||
? 'bg-slate-950/95 ring-slate-400/70 hover:bg-slate-900'
|
||||
: 'bg-emerald-950/90 ring-emerald-300/80 hover:bg-emerald-900/95 focus-visible:ring-emerald-200';
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center p-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={pending || controlsDisabled}
|
||||
onClick={onUndock}
|
||||
className={`pointer-events-auto flex w-[min(32rem,80%)] flex-col items-center gap-2 px-8 py-7 text-center text-white shadow-2xl ring-2 transition focus-visible:outline-none focus-visible:ring-4 ${
|
||||
waitingForTurn
|
||||
? 'cursor-not-allowed bg-slate-950/95 ring-slate-400/70'
|
||||
: 'bg-emerald-950/90 ring-emerald-300/80 hover:bg-emerald-900/95 focus-visible:ring-emerald-200 disabled:cursor-wait disabled:opacity-75'
|
||||
}`}
|
||||
>
|
||||
<strong className="text-3xl leading-tight">{pending ? 'Undocking…' : 'Your rover is docked'}</strong>
|
||||
{pending ? (
|
||||
null
|
||||
) : waitingForTurn ? (
|
||||
/* A disabled action must explain the ownership constraint instead of
|
||||
continuing to advertise a click and keybind that cannot succeed. */
|
||||
<span className="text-lg font-semibold leading-snug text-slate-300">
|
||||
Wait for your turn to undock.
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-lg font-semibold leading-snug text-emerald-50">
|
||||
Click here
|
||||
{driveKeyLabel ? (
|
||||
<>
|
||||
{' '}or press <KeyPill label={driveKeyLabel} />
|
||||
</>
|
||||
) : null}
|
||||
{' '}to undock and drive the rover
|
||||
</span>
|
||||
)}
|
||||
{error ? <span className="text-sm font-semibold text-red-200">{error}</span> : null}
|
||||
</button>
|
||||
</div>
|
||||
<>
|
||||
{/* The docked shield is owned by the dismissible action so hiding the
|
||||
prompt also reveals the video and ordinary HUD instead of leaving an
|
||||
unexplained dark, input-blocking layer behind. */}
|
||||
<div className="pointer-events-auto absolute inset-0 z-[25] bg-black/75" aria-hidden="true" />
|
||||
<div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center p-6">
|
||||
<div className="relative w-[min(32rem,80%)]">
|
||||
<button
|
||||
type="button"
|
||||
disabled={pending || controlsDisabled}
|
||||
onClick={onUndock}
|
||||
className={`pointer-events-auto flex w-full flex-col items-center gap-2 px-8 py-7 text-center text-white shadow-2xl ring-2 transition focus-visible:outline-none focus-visible:ring-4 ${mainToneClass}`}
|
||||
>
|
||||
<strong className="text-3xl leading-tight">{pending ? 'Undocking…' : 'Your rover is docked'}</strong>
|
||||
{pending ? (
|
||||
null
|
||||
) : waitingForTurn ? (
|
||||
/* A disabled action must explain the ownership constraint instead of
|
||||
continuing to advertise a click and keybind that cannot succeed. */
|
||||
<span className="text-lg font-semibold leading-snug text-slate-300">
|
||||
Wait for your turn to undock.
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-lg font-semibold leading-snug text-emerald-50">
|
||||
Click here
|
||||
{driveKeyLabel ? (
|
||||
<>
|
||||
{' '}or press <KeyPill label={driveKeyLabel} />
|
||||
</>
|
||||
) : null}
|
||||
{' '}to undock and drive the rover
|
||||
</span>
|
||||
)}
|
||||
{error ? <span className="text-sm font-semibold text-red-200">{error}</span> : null}
|
||||
</button>
|
||||
{!pending ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHidden(true)}
|
||||
className={`pointer-events-auto absolute left-1/2 top-full -translate-x-1/2 rounded-b-lg px-6 py-1.5 text-sm font-bold text-white shadow-xl ring-2 transition focus-visible:outline-none focus-visible:ring-4 ${hideToneClass}`}
|
||||
>
|
||||
Hide
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,15 +345,12 @@ export default function DockingHud({ roverId }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* This single layer both dims and blocks the ordinary rover HUD. A passive
|
||||
undocked rover is still moving autonomously, so it gets a lighter but equally
|
||||
blocking shield. Explicit pending state keeps the correct shield mounted until
|
||||
the complete drive sequence finishes even as telemetry changes underneath it. */}
|
||||
{/* Automatic docking keeps its own lighter blocking shield. The ordinary
|
||||
docked shield lives inside DockedAction because the new Hide control
|
||||
must dismiss the prompt and its dimming as one coherent surface. */}
|
||||
<div
|
||||
className={`absolute inset-0 z-[25] transition-all duration-300 ${
|
||||
docked || pendingAction === 'undocking'
|
||||
? 'pointer-events-auto bg-black/75 opacity-100'
|
||||
: autoDocking || pendingAction === 'resuming'
|
||||
autoDocking || pendingAction === 'resuming'
|
||||
? 'pointer-events-auto bg-black/55 opacity-100'
|
||||
: 'pointer-events-none opacity-0'
|
||||
}`}
|
||||
@@ -335,6 +359,7 @@ export default function DockingHud({ roverId }) {
|
||||
|
||||
{docked || pendingAction === 'undocking' ? (
|
||||
<DockedAction
|
||||
key={roverId}
|
||||
driveKeyLabel={driveKeyLabel}
|
||||
pending={pendingAction === 'undocking'}
|
||||
controlsDisabled={!canControl}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Initial Session Overlay
|
||||
// Purpose: Hides incomplete driver-page placeholders until the first authoritative session snapshot arrives.
|
||||
// Scope: Provides startup presentation only; it deliberately does not delay or alter page initialization underneath.
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
import spinnerImage from '../../assets/spinner.png';
|
||||
import './styles.css';
|
||||
|
||||
export default function InitialSessionOverlay() {
|
||||
const connected = useSessionSelector((state) => Boolean(state.connected));
|
||||
const sessionReady = useSessionSelector((state) => state.session !== null);
|
||||
|
||||
if (sessionReady) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[1000] flex items-center justify-center bg-black text-slate-200"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={connected ? 'Loading session' : 'Connecting'}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
{/* The source image is intentionally constrained to a small fixed box;
|
||||
its intrinsic pixel dimensions must never determine overlay layout.
|
||||
Reduced-motion users still see the identifying image without either
|
||||
the rotation or continuously changing color. */}
|
||||
<div className="initial-session-spinner-rotation h-20 w-20" aria-hidden="true">
|
||||
<img
|
||||
src={spinnerImage}
|
||||
alt=""
|
||||
draggable="false"
|
||||
className="initial-session-spinner-image h-full w-full select-none object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-slate-300">
|
||||
{connected ? 'Loading session…' : 'Connecting…'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
These animations belong only to InitialSessionOverlay. Keeping them beside
|
||||
the component avoids adding feature-specific behavior to the global styles.
|
||||
Rotation and hue use separate elements so their independent infinite cycles
|
||||
cannot overwrite one another's transform or filter properties.
|
||||
*/
|
||||
@keyframes initial-session-spinner-rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes initial-session-spinner-hue {
|
||||
from {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
filter: hue-rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.initial-session-spinner-rotation {
|
||||
animation: initial-session-spinner-rotate 1.8s linear infinite;
|
||||
}
|
||||
|
||||
.initial-session-spinner-image {
|
||||
animation: initial-session-spinner-hue 2.8s linear infinite;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@ function ControlRow({ label, keyLabel }) {
|
||||
function DesktopQuickstart({ keymap }) {
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200">1. Press "Start Driving" put your rover into driving mode.</p>
|
||||
<p className="text-sm text-slate-200">1. Click "Your rover is docked" to undock.</p>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200">2. Use the drive controls to move your rover:</p>
|
||||
<p className="text-sm text-slate-200">2. Drive with these keybindings:</p>
|
||||
<div className="space-y-0.5">
|
||||
<ControlRow label="Forward" keyLabel={formatKeyLabel(keymap?.driveForward?.[0])} />
|
||||
<ControlRow label="Backward" keyLabel={formatKeyLabel(keymap?.driveBackward?.[0])} />
|
||||
@@ -31,7 +31,8 @@ function DesktopQuickstart({ keymap }) {
|
||||
<ControlRow label="Move slower" keyLabel={formatKeyLabel(keymap?.slowModifier?.[0])} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-slate-200">3. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.</p>
|
||||
<p className="text-sm text-slate-200">3. Use the video HUD for rover controls and information.</p>
|
||||
<p className="text-sm text-slate-200">4. Click "Dock rover" when finished.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -39,10 +40,11 @@ function DesktopQuickstart({ keymap }) {
|
||||
function MobileQuickstart() {
|
||||
return (
|
||||
<div className="space-y-0.5 text-sm text-slate-200">
|
||||
<p>1. Press "Start Driving" put your rover into driving mode.</p>
|
||||
<p>2. Touch and hold in Joystick area to move.</p>
|
||||
<p>3. Use the other column for motor, horn, and camera controls.</p>
|
||||
<p>4. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.</p>
|
||||
<p>1. Tap "Your rover is docked" to undock.</p>
|
||||
<p>2. Hold and drag on the drive pad.</p>
|
||||
<p>3. Choose Precision, Normal, or Turbo above the drive pad.</p>
|
||||
<p>4. Use the other column for rover controls.</p>
|
||||
<p>5. Tap "Dock and charge" when finished.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -71,7 +73,6 @@ export default function QuickstartOverlay({
|
||||
layout,
|
||||
showOnLoad,
|
||||
onToggleShowOnLoad,
|
||||
onOpenHelp,
|
||||
onClose,
|
||||
}) {
|
||||
const rawKeymap = useControlSelector((control) => control.state.keymap);
|
||||
|
||||
@@ -87,10 +87,6 @@ function DriverPageContent({ layout, oldDesktop }) {
|
||||
},
|
||||
[saveQuickstartSettings],
|
||||
);
|
||||
const openHelpFromQuickstart = useCallback(() => {
|
||||
setQuickstartVisible(false);
|
||||
setHelpVisible(true);
|
||||
}, []);
|
||||
return (
|
||||
<ControlSystemProvider>
|
||||
{/*
|
||||
@@ -127,7 +123,6 @@ function DriverPageContent({ layout, oldDesktop }) {
|
||||
layout={layout}
|
||||
showOnLoad={quickstartSettings?.showOnLoad !== false}
|
||||
onToggleShowOnLoad={setQuickstartShowOnLoad}
|
||||
onOpenHelp={openHelpFromQuickstart}
|
||||
onClose={closeQuickstart}
|
||||
/>
|
||||
</ControlSystemProvider>
|
||||
|
||||
+60
-27
@@ -11,26 +11,44 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Chat and nicknames',
|
||||
items: [
|
||||
'Set a nickname in the user list panel, on the bottom left of the page below the rover video.',
|
||||
{ segments: ['Toggle chat focus with ', { action: 'chatFocus' }, '. Press ', { action: 'chatFocus'}, ' again to send.'] },
|
||||
'Chat and nickname controls are in the Chat/Rovers tab.',
|
||||
{ segments: ['Open the HUD chat composer with ', { action: 'chatFocus' }, '. Press ', { action: 'chatFocus'}, ' again to send.'] },
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Driving the rover',
|
||||
items: [
|
||||
{ segments: ['Press the "Start Driving" button onscreen, or press ' , { action: 'driveMacro' }, ' on your keyboard to put the rover into driving mode.'] },
|
||||
'Refer to the controls for the controls for the rover.'
|
||||
{ segments: ['Click "Your rover is docked", or press ', { action: 'driveMacro' }, ', to undock.'] },
|
||||
'Drive with the movement keybindings.',
|
||||
'Rover controls dim while another person has the turn.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Video HUD',
|
||||
items: [
|
||||
'Top left shows the rover name and turns.',
|
||||
'Top right shows battery and rover status.',
|
||||
'Bottom left contains horn, headlight, and laser controls.',
|
||||
'Bottom right contains camera tilt and chat.',
|
||||
'Use the arrows to open and close pods and their expansions.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Page layout',
|
||||
items: [
|
||||
'Room, Activities, and VIP are in the left sidebar.',
|
||||
'Chat/Rovers, Help, and Settings are in the right sidebar.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'If the rover shows "Docking in Progress", it is already auto-seeking the dock.',
|
||||
'To dock manually, enter Docking Assist from the drive panel.',
|
||||
{ segments: ['Press "Enter Docking Assist", or press ', { action: 'dockMacro' }, '.'] },
|
||||
'In assist mode, camera tilts down and driving speed is limited for precise alignment.',
|
||||
{ segments: ['Click "Dock rover", or press ', { action: 'dockMacro' }, '.'] },
|
||||
'Click "Rover is docking itself" to resume driving.',
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -56,8 +74,8 @@ export const HELP_CONTENT = {
|
||||
id: 'macros',
|
||||
title: 'Rover modes & chat',
|
||||
items: [
|
||||
{ action: 'driveMacro', label: 'Drive macro' },
|
||||
{ action: 'dockMacro', label: 'Docking assist toggle' },
|
||||
{ action: 'driveMacro', label: 'Undock / resume driving' },
|
||||
{ action: 'dockMacro', label: 'Dock rover' },
|
||||
{ action: 'chatFocus', label: 'Chat focus' },
|
||||
],
|
||||
},
|
||||
@@ -119,27 +137,35 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Chat and nicknames',
|
||||
items: [
|
||||
'Set a nickname in the user list panel below.',
|
||||
'Tap in the chat box to send messages in the chat.'
|
||||
'Chat and nickname controls are in the Chat tab.',
|
||||
'Tap the chat box to send a message.',
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Driving the rover',
|
||||
items: [
|
||||
{ segments: ['Press the "Start Driving" button onscreen to put the rover into driving mode.'] },
|
||||
'Look below the rover video. Use the joystick column to move the rover, and hold the aux buttons in the other control column.'
|
||||
'Tap "Your rover is docked" to undock.',
|
||||
'Hold and drag on the drive pad.',
|
||||
'Choose Precision, Normal, or Turbo above the drive pad.',
|
||||
'Use the other column for rover controls.',
|
||||
'Rover controls dim while another person has the turn.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'If you see "Docking in Progress", the rover is currently auto-seeking the dock.',
|
||||
{ segments: ['For manual docking, press "Enter Docking Assist".'] },
|
||||
'Assist mode tilts camera down and limits speed for precise alignment.',
|
||||
'Tap "Dock and charge" when finished.',
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'More controls',
|
||||
items: [
|
||||
'Chat, Activities, VIP, Room Controls, Help, and Settings are below the rover controls.',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
'mobile-landscape': {
|
||||
@@ -148,28 +174,35 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Chat and nicknames',
|
||||
items: [
|
||||
'Scroll down to see more of the page.',
|
||||
'Set a nickname in the user list panel below.',
|
||||
'Tap in the chat box to send messages in the chat.'
|
||||
'Chat and nickname controls are in the Chat tab.',
|
||||
'Tap the chat box to send a message.',
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Driving the rover',
|
||||
items: [
|
||||
{ segments: ['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.'] },
|
||||
'Use the joystick column beside the video feed to move the rover, and hold the aux buttons in the other control column.'
|
||||
'Tap "Your rover is docked" to undock.',
|
||||
'Hold and drag on the drive pad.',
|
||||
'Choose Precision, Normal, or Turbo above the drive pad.',
|
||||
'Use the other column for rover controls.',
|
||||
'Rover controls dim while another person has the turn.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'If you see "Docking in Progress", the rover is currently auto-seeking the dock.',
|
||||
{ segments: ['For manual docking, press "Enter Docking Assist" (or "Dock" button).'] },
|
||||
'Assist mode tilts camera down and limits speed for precise alignment.',
|
||||
'Tap "Dock and charge" when finished.',
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
title: 'More controls',
|
||||
items: [
|
||||
'Chat, Activities, VIP, Room Controls, Help, and Settings are below the rover controls.',
|
||||
],
|
||||
},
|
||||
],
|
||||
// aside: [
|
||||
// {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { SettingsProvider } from './settings/index.js'
|
||||
import DeterrenceChaos from './components/DeterrenceChaos/index.jsx'
|
||||
import AnalyticsReporter from './analytics/AnalyticsReporter.jsx'
|
||||
import PtzAppRoot from './ptz/PtzAppRoot.jsx'
|
||||
import InitialSessionOverlay from './components/InitialSessionOverlay/index.jsx'
|
||||
|
||||
// The reporting route includes the charting and CSV libraries. Loading that
|
||||
// bundle only when `/reports` is visited keeps ordinary rover-control sessions
|
||||
@@ -31,6 +32,11 @@ createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<SocketProvider>
|
||||
<SessionProvider>
|
||||
{/* Every route depends on the first authoritative session snapshot.
|
||||
Mounting this opaque layer at the shared provider boundary prevents
|
||||
incomplete route-specific placeholders from flashing while still
|
||||
allowing every application tree to initialize underneath it. */}
|
||||
<InitialSessionOverlay />
|
||||
<TelemetryProvider>
|
||||
<SettingsProvider>
|
||||
<ChatProvider>
|
||||
|
||||
Reference in New Issue
Block a user