mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
loading screen, better HUD, etc.
This commit is contained in:
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
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
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
Binary file not shown.
|
After Width: | Height: | Size: 337 KiB |
@@ -12,8 +12,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<!-- site-metadata:inject -->
|
||||
<!-- analytics:inject -->
|
||||
<script type="module" crossorigin src="/assets/index-C0lpCbci.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BITzjVQo.css">
|
||||
<script type="module" crossorigin src="/assets/index-BZ2ymoHR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BFNKIMjg.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
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