mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
fixing pod collapse expandings
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
@@ -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-CAUR32ig.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D2qTDWX0.css">
|
||||
<script type="module" crossorigin src="/assets/index-D9i-ZujG.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D5gzYj1F.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function BottomLeftPod({ roverId }) {
|
||||
) : (
|
||||
<CornerPodToggle corner="bottom-left" expanded={false} label="Show rover controls" onClick={() => setOpen(true)} />
|
||||
)}
|
||||
{hornDevice ? <HornSettingsExpansion open={hornSettingsOpen} podOpen={open} onOpenChange={setHornSettingsOpen} /> : null}
|
||||
{hornDevice ? <HornSettingsExpansion open={hornSettingsOpen} onOpenChange={setHornSettingsOpen} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// HUD Expansion Panel
|
||||
// Purpose: Gives every independent HUD expansion one fixed toggle anchor and identical open/closed behavior.
|
||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
||||
|
||||
export default function ExpansionPanel({
|
||||
open,
|
||||
onOpenChange,
|
||||
anchorClassName = '',
|
||||
panelClassName = '',
|
||||
panelAlign = 'left',
|
||||
panelVerticalAlign = 'top',
|
||||
openDirection,
|
||||
closeDirection,
|
||||
openLabel,
|
||||
closeLabel,
|
||||
children,
|
||||
}) {
|
||||
const panelAlignmentClass = panelAlign === 'right' ? 'right-0' : 'left-0';
|
||||
const panelVerticalAlignmentClass = panelVerticalAlign === 'bottom' ? 'bottom-0' : 'top-0';
|
||||
|
||||
return (
|
||||
<div className={`pointer-events-auto z-20 h-3 w-8 ${anchorClassName}`}>
|
||||
{/* The content is positioned behind the fixed 32-by-12 anchor. Opening or
|
||||
closing changes only whether this panel exists; it never relocates or
|
||||
restyles the toggle that the user just clicked. */}
|
||||
{open ? (
|
||||
<div className={`absolute z-0 ${panelAlignmentClass} ${panelVerticalAlignmentClass} ${panelClassName}`}>
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
<ExpansionToggle
|
||||
direction={open ? closeDirection : openDirection}
|
||||
label={open ? closeLabel : openLabel}
|
||||
onClick={() => onOpenChange(!open)}
|
||||
className="absolute left-0 top-0"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,16 +6,15 @@ const ICONS = { down: FaChevronDown, left: FaChevronLeft, right: FaChevronRight,
|
||||
|
||||
export default function ExpansionToggle({ direction, label, onClick, className = '' }) {
|
||||
const Icon = ICONS[direction] || FaChevronLeft;
|
||||
const horizontalEdge = direction === 'up' || direction === 'down';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
className={`flex shrink-0 items-center justify-center bg-black/60 text-[0.5rem] text-white/75 hover:bg-black hover:text-white ${horizontalEdge ? 'h-3 w-8' : 'h-8 w-3'} ${className}`}
|
||||
className={`z-10 flex h-3 w-8 shrink-0 items-center justify-center bg-black/60 text-[0.5rem] text-white/75 hover:bg-black hover:text-white ${className}`}
|
||||
>
|
||||
{/* The black strip is intentionally retained around the chevron. A collapsed expansion is
|
||||
therefore still a thin piece of that expansion, never a loose button over a pod. */}
|
||||
{/* ExpansionPanel owns all positioning so this button has exactly one visual
|
||||
form. Only the chevron direction changes between open and closed states. */}
|
||||
<Icon aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback } from 'react';
|
||||
import { HORN_MAX_FREQUENCY } from '../../../../controls/constants.js';
|
||||
import { useSettingsNamespace } from '../../../../settings/index.js';
|
||||
import { HORN_SETTINGS_DEFAULTS } from '../../../../settings/namespaces.js';
|
||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
||||
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||
|
||||
function clampFrequency(value) {
|
||||
const numeric = Number(value);
|
||||
@@ -12,7 +12,7 @@ function clampFrequency(value) {
|
||||
return Math.min(HORN_MAX_FREQUENCY, Math.round(numeric));
|
||||
}
|
||||
|
||||
export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
||||
export default function HornSettingsExpansion({ open, onOpenChange }) {
|
||||
const { value, save } = useSettingsNamespace('horn', HORN_SETTINGS_DEFAULTS);
|
||||
const waveform = value?.waveform === 'sine' ? 'sine' : 'saw';
|
||||
const frequencies = [...(Array.isArray(value?.freqs) ? value.freqs : HORN_SETTINGS_DEFAULTS.freqs), 0, 0, 0, 0]
|
||||
@@ -31,21 +31,25 @@ export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
||||
});
|
||||
}, [save]);
|
||||
|
||||
if (!open) {
|
||||
return (
|
||||
<div className={`pointer-events-auto absolute left-10 z-20 flex h-3 w-8 bg-black/60 ${podOpen ? 'bottom-40' : 'bottom-0'}`}>
|
||||
<ExpansionToggle direction="up" label="Show horn settings" onClick={() => onOpenChange(true)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`pointer-events-auto absolute left-0 z-20 w-52 rounded-tr-xl bg-black/60 p-2 text-xs text-white ${podOpen ? 'bottom-40' : 'bottom-0'}`}>
|
||||
/* Horn settings belongs to the left video wall, not to the peripheral pod.
|
||||
Keeping this anchor fixed above the pod's maximum footprint means the
|
||||
expansion never follows or overlaps the pod as that separate UI closes.
|
||||
Its arrows mirror advanced info on the opposite wall: open points into
|
||||
the screen and close points back toward the wall. */
|
||||
<ExpansionPanel
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
anchorClassName="absolute bottom-40 left-0"
|
||||
panelVerticalAlign="bottom"
|
||||
panelClassName="w-52 rounded-tr-xl bg-black/60 p-2 pb-4 text-xs text-white"
|
||||
openDirection="right"
|
||||
closeDirection="left"
|
||||
openLabel="Show horn settings"
|
||||
closeLabel="Hide horn settings"
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<span className="font-semibold text-cyan-100">Horn settings</span>
|
||||
{/* This arrow belongs to the expansion. Closing the peripheral pod never changes
|
||||
hornSettings visibility; it only moves this panel into the vacated corner. */}
|
||||
<ExpansionToggle direction="down" label="Hide horn settings" onClick={() => onOpenChange(false)} />
|
||||
</div>
|
||||
<label className="flex items-center justify-between gap-2 text-slate-300">
|
||||
<span>Wave</span>
|
||||
@@ -73,6 +77,6 @@ export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ExpansionPanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
||||
import { useSharedClock } from '../../../../hooks/useSharedClock.js';
|
||||
import RoverLabel from '../../../RoverLabel/index.jsx';
|
||||
import CornerPodToggle from './CornerPodToggle.jsx';
|
||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
||||
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||
import usePodVisibility from './usePodVisibility.js';
|
||||
|
||||
export default function TopLeftPod({ roverId }) {
|
||||
@@ -159,16 +159,18 @@ export default function TopLeftPod({ roverId }) {
|
||||
{/* The rover name is an independent edge expansion. Its visibility control lives in
|
||||
the expansion itself, and its position naturally moves into the corner whenever
|
||||
the conditional timer pod is absent or manually collapsed. */}
|
||||
{nameOpen ? (
|
||||
<div className={`flex h-11 items-center gap-2 bg-black/60 px-2 ${showTimer ? '' : 'rounded-br-xl'}`}>
|
||||
<ExpansionPanel
|
||||
open={nameOpen}
|
||||
onOpenChange={setNameOpen}
|
||||
anchorClassName={`relative shrink-0 ${showTimer || !turnActive ? '' : 'ml-10'}`}
|
||||
panelClassName={`flex h-11 min-w-max items-center gap-2 bg-black/60 px-2 pt-3 ${showTimer ? '' : 'rounded-br-xl'}`}
|
||||
openDirection="down"
|
||||
closeDirection="up"
|
||||
openLabel="Show rover name"
|
||||
closeLabel="Hide rover name"
|
||||
>
|
||||
<RoverLabel roverId={roverId} fallback={roverId} className="px-2 py-1 text-base" />
|
||||
<ExpansionToggle direction="up" label="Hide rover name" onClick={() => setNameOpen(false)} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`flex h-3 w-8 bg-black/60 ${showTimer ? '' : 'ml-10'}`}>
|
||||
<ExpansionToggle direction="down" label="Show rover name" onClick={() => setNameOpen(true)} />
|
||||
</div>
|
||||
)}
|
||||
</ExpansionPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
||||
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
||||
import { hostStatsEqual, selectHostStats, selectSpectatorTelemetry, spectatorTelemetryEqual } from '../../../../context/telemetryViews.js';
|
||||
import CornerPodToggle from './CornerPodToggle.jsx';
|
||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
||||
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||
import usePodVisibility from './usePodVisibility.js';
|
||||
|
||||
function finite(value) {
|
||||
@@ -135,9 +135,17 @@ export default function TopRightPod({ roverId }) {
|
||||
|
||||
{/* Advanced power is an independently persisted right-edge expansion. Its own arrow is
|
||||
retained when closed, and the whole panel moves into the corner if the pod closes. */}
|
||||
{powerOpen ? (
|
||||
<div className={`absolute right-0 w-56 rounded-bl-xl bg-black/60 p-1.5 pl-4 text-white ${batteryOpen ? 'top-[8.5rem]' : 'top-0'}`}>
|
||||
<ExpansionToggle direction="right" label="Hide power and computer" onClick={() => setPowerOpen(false)} className="absolute left-0 top-1/2 -translate-y-1/2" />
|
||||
<ExpansionPanel
|
||||
open={powerOpen}
|
||||
onOpenChange={setPowerOpen}
|
||||
anchorClassName={`absolute right-0 ${batteryOpen ? 'top-[8.5rem]' : 'top-10'}`}
|
||||
panelAlign="right"
|
||||
panelClassName="w-56 rounded-bl-xl bg-black/60 p-1.5 pt-4 text-white"
|
||||
openDirection="left"
|
||||
closeDirection="right"
|
||||
openLabel="Show power and computer"
|
||||
closeLabel="Hide power and computer"
|
||||
>
|
||||
<div className="space-y-1.5">
|
||||
<MetricRow icon={FaBolt} label="Roomba voltage" value={voltage == null ? '--' : `${(voltage / 1000).toFixed(1)} V`} percent={voltagePercent} iconClass="text-sky-300" fillClass="bg-sky-400" />
|
||||
<MetricRow icon={FaBolt} label="Roomba current" value={`${current > 0 ? '+' : ''}${Math.round(current)} mA`} percent={currentPercent * 100} iconClass={current < 0 ? 'text-amber-300' : 'text-emerald-300'} fillClass={current < 0 ? 'bg-amber-400' : 'bg-emerald-400'} />
|
||||
@@ -148,12 +156,7 @@ export default function TopRightPod({ roverId }) {
|
||||
<SpeedTile icon={FaArrowDown} label="Download speed" value={download == null ? '--' : `${download.toFixed(1)} Mb/s`} colorClass="text-sky-300" />
|
||||
<SpeedTile icon={FaArrowUp} label="Upload speed" value={upload == null ? '--' : `${upload.toFixed(1)} Mb/s`} colorClass="text-violet-300" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`absolute right-0 flex h-8 w-3 bg-black/60 ${batteryOpen ? 'top-[8.5rem]' : 'top-10'}`}>
|
||||
<ExpansionToggle direction="left" label="Show power and computer" onClick={() => setPowerOpen(true)} />
|
||||
</div>
|
||||
)}
|
||||
</ExpansionPanel>
|
||||
</div>
|
||||
|
||||
{/* Battery danger is a stage-level warning, so it belongs near the user's focus instead
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Scope: Owns presentation and the existing manual-assist lifecycle for the current driver HUD;
|
||||
// the archived desktop layout retains its previous DriveDockAction behavior.
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FaChargingStation, FaChevronDown } from 'react-icons/fa';
|
||||
import { FaChargingStation } from 'react-icons/fa';
|
||||
import { useControlActions, useControlSelector } from '../../../../controls/index.js';
|
||||
import { formatKeyLabel } from '../../../../controls/keymapUtils.js';
|
||||
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
||||
@@ -14,7 +14,7 @@ import useCanControlRover from '../../../../hooks/useCanControlRover.js';
|
||||
import { useDriverLayout } from '../../../../layouts/driver/DriverLayoutContext.jsx';
|
||||
import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
||||
import KeyPill from '../../../vip/VipAudioUploadCard/KeyPill.jsx';
|
||||
import ExpansionToggle from '../CornerPods/ExpansionToggle.jsx';
|
||||
import ExpansionPanel from '../CornerPods/ExpansionPanel.jsx';
|
||||
import usePodVisibility from '../CornerPods/usePodVisibility.js';
|
||||
|
||||
function DockedAction({ driveKeyLabel, pending, controlsDisabled, error, onUndock }) {
|
||||
@@ -101,29 +101,10 @@ function AutoDockingAction({ driveKeyLabel, pending, controlsDisabled, error, on
|
||||
function DockAssistAction({ active, pending, controlsDisabled, error, dockKeyLabel, onDock, onCancel, cornerOffsetClass, open, onOpenChange, batterySeverity }) {
|
||||
const batteryUrgent = batterySeverity === 'urgent';
|
||||
const batteryLow = batterySeverity === 'low';
|
||||
if (!open) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show rover docking control"
|
||||
title="Dock rover"
|
||||
onClick={() => onOpenChange(true)}
|
||||
className={`pointer-events-auto absolute top-0 z-20 flex h-6 w-10 items-center justify-center gap-1 rounded-bl text-[0.6rem] transition ${
|
||||
batteryUrgent
|
||||
? 'bg-red-950 text-red-100 hover:bg-red-900'
|
||||
: batteryLow
|
||||
? 'bg-amber-950 text-amber-100 hover:bg-amber-900'
|
||||
: 'bg-indigo-950/75 text-indigo-100 hover:bg-indigo-900'
|
||||
} ${cornerOffsetClass === 'right-0' ? 'right-10' : cornerOffsetClass}`}
|
||||
>
|
||||
{/* The collapsed tab retains feature identity instead of becoming an
|
||||
anonymous expansion arrow whose purpose must be remembered. */}
|
||||
<FaChargingStation aria-hidden="true" />
|
||||
<FaChevronDown aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// When the battery pod is collapsed its triangular reopen control owns the
|
||||
// outermost forty pixels. Docking uses the next top-edge slot in both open
|
||||
// and collapsed states so the two independent controls never overlap.
|
||||
const dockPositionClass = cornerOffsetClass === 'right-0' ? 'right-10' : cornerOffsetClass;
|
||||
if (active) {
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 z-[60] flex items-center justify-center">
|
||||
@@ -147,29 +128,38 @@ function DockAssistAction({ active, pending, controlsDisabled, error, dockKeyLab
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`pointer-events-auto absolute top-0 z-20 flex items-stretch ${cornerOffsetClass}`}>
|
||||
<ExpansionToggle direction="up" label="Hide dock controls" onClick={() => onOpenChange(false)} />
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Start rover docking assist"
|
||||
disabled={pending || controlsDisabled}
|
||||
onClick={onDock}
|
||||
className={`flex items-center gap-1.5 rounded-bl-xl px-4 py-2 text-base font-bold shadow-xl ring-1 transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-wait disabled:opacity-75 ${
|
||||
batteryUrgent
|
||||
? 'bg-red-950 text-red-50 ring-red-300/80 hover:bg-red-900 focus-visible:ring-red-200'
|
||||
: batteryLow
|
||||
? 'bg-amber-950 text-amber-50 ring-amber-300/80 hover:bg-amber-900 focus-visible:ring-amber-200'
|
||||
: 'bg-indigo-950/60 text-indigo-50 ring-indigo-300/70 hover:bg-indigo-900 focus-visible:ring-indigo-200'
|
||||
}`}
|
||||
>
|
||||
<FaChargingStation className="shrink-0" aria-hidden="true" />
|
||||
<span>{pending ? 'Starting…' : batteryUrgent ? 'Dock now' : batteryLow ? 'Dock soon' : 'Dock rover'}</span>
|
||||
{dockKeyLabel && !pending ? <KeyPill label={dockKeyLabel} /> : null}
|
||||
</button>
|
||||
{/* The expansion toggle stays on the far side of this panel so the battery pod's
|
||||
triangular control remains unobstructed when both occupy the top-right area. */}
|
||||
<ExpansionPanel
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
anchorClassName={`absolute top-0 ${dockPositionClass}`}
|
||||
panelAlign="right"
|
||||
panelClassName="flex items-start"
|
||||
openDirection="down"
|
||||
closeDirection="up"
|
||||
openLabel="Show rover docking control"
|
||||
closeLabel="Hide dock controls"
|
||||
>
|
||||
<div className="relative">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Start rover docking assist"
|
||||
disabled={pending || controlsDisabled}
|
||||
onClick={onDock}
|
||||
className={`flex items-center gap-1.5 rounded-bl-xl px-4 pb-2 pt-4 text-base font-bold shadow-xl ring-1 transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-wait disabled:opacity-75 ${
|
||||
batteryUrgent
|
||||
? 'bg-red-950 text-red-50 ring-red-300/80 hover:bg-red-900 focus-visible:ring-red-200'
|
||||
: batteryLow
|
||||
? 'bg-amber-950 text-amber-50 ring-amber-300/80 hover:bg-amber-900 focus-visible:ring-amber-200'
|
||||
: 'bg-indigo-950/60 text-indigo-50 ring-indigo-300/70 hover:bg-indigo-900 focus-visible:ring-indigo-200'
|
||||
}`}
|
||||
>
|
||||
<FaChargingStation className="shrink-0" aria-hidden="true" />
|
||||
<span>{pending ? 'Starting…' : batteryUrgent ? 'Dock now' : batteryLow ? 'Dock soon' : 'Dock rover'}</span>
|
||||
{dockKeyLabel && !pending ? <KeyPill label={dockKeyLabel} /> : null}
|
||||
</button>
|
||||
</div>
|
||||
{error ? <div className="mt-2 max-w-64 bg-red-950/90 px-3 py-2 text-sm font-semibold text-red-100">{error}</div> : null}
|
||||
</div>
|
||||
</ExpansionPanel>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user