mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
ui tweak fling
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-DsdqxXyd.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-0J6fxLEg.css">
|
||||
<script type="module" crossorigin src="/assets/index-UV1Q4Mdm.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B5TEaoXl.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -16,22 +16,23 @@ export default function AccessoriesExpansion({ roverId }) {
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 z-20 flex items-center">
|
||||
<AccessoriesToggle
|
||||
label="Accessories"
|
||||
ariaLabel={open ? 'Hide accessory controls' : 'Show accessory controls'}
|
||||
onClick={() => setOpen(!open)}
|
||||
hud
|
||||
className="pointer-events-auto !h-28"
|
||||
/>
|
||||
{open ? (
|
||||
<div className="pointer-events-auto w-64 overflow-hidden rounded-r-xl bg-black/60 p-0.5">
|
||||
{/* This is exactly the renderer mounted by AuxColumn. The desktop
|
||||
wrapper changes available dimensions, never control behavior.
|
||||
Content determines the normal panel height; max-height becomes a
|
||||
scrolling boundary only for genuinely long accessory lists. */}
|
||||
<RoverAccessoryControls roverId={roverId} className="max-h-[70vh]" />
|
||||
</div>
|
||||
) : null}
|
||||
{/* The tab remains attached to the video's left wall. When open, this
|
||||
single shell grows around both the unchanged tab position and the
|
||||
controls to its right, so the controls are not rendered as a second
|
||||
disconnected panel. Its height follows content until the shared
|
||||
renderer reaches the scrolling boundary. */}
|
||||
<div className="pointer-events-auto flex max-h-[70vh] items-center overflow-hidden rounded-r-xl bg-black/60">
|
||||
<AccessoriesToggle
|
||||
label="Accessories"
|
||||
ariaLabel={open ? 'Hide accessory controls' : 'Show accessory controls'}
|
||||
onClick={() => setOpen(!open)}
|
||||
hud
|
||||
className="!h-28"
|
||||
/>
|
||||
{open ? (
|
||||
<RoverAccessoryControls roverId={roverId} className="w-64 max-h-[70vh]" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ function RoverAuxColumn({ roverId, layout, className }) {
|
||||
className="h-full"
|
||||
headerAction={(
|
||||
<AccessoriesToggle
|
||||
label="Aux"
|
||||
label="Back"
|
||||
ariaLabel="Return to auxiliary controls"
|
||||
compact
|
||||
onClick={() => setShowAccessories(false)}
|
||||
|
||||
@@ -12,10 +12,17 @@ export default function AccessoriesToggle({
|
||||
hud = false,
|
||||
className = '',
|
||||
}) {
|
||||
const sizeClass = compact ? 'h-6 w-10' : 'h-full w-8';
|
||||
// The Back action is still shorter than the vertical launcher, but it needs
|
||||
// a normal touch target and a readable word instead of the previous tiny
|
||||
// abbreviated control. The full launcher continues to fill the height its
|
||||
// desktop or mobile parent assigns to it.
|
||||
const sizeClass = compact ? 'h-8 w-14' : 'h-full w-8';
|
||||
const toneClass = hud
|
||||
? 'rounded-none border-0 bg-black/60 text-white/75 shadow-none hover:bg-black hover:text-white'
|
||||
: 'rounded-xl border-2 border-cyan-300/70 bg-cyan-900 text-cyan-50 shadow-md hover:brightness-110 active:brightness-125';
|
||||
// On desktop the tab sits inside the shared popout shell. Rounding only
|
||||
// its exposed right edge preserves its left-wall attachment without
|
||||
// introducing a separate accessory-specific panel treatment.
|
||||
? 'rounded-r-xl border-0 bg-black/60 text-white shadow-none'
|
||||
: 'rounded-xl border-2 border-cyan-300/70 bg-cyan-900 text-cyan-50 shadow-md';
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -25,10 +32,10 @@ export default function AccessoriesToggle({
|
||||
triggerTouchHaptic('button');
|
||||
onClick();
|
||||
}}
|
||||
className={`mobile-touch-control flex shrink-0 items-center justify-center text-sm font-semibold transition active:scale-[0.98] ${sizeClass} ${toneClass} ${className}`.trim()}
|
||||
className={`mobile-touch-control flex shrink-0 items-center justify-center text-sm font-semibold ${sizeClass} ${toneClass} ${className}`.trim()}
|
||||
>
|
||||
{/* Full launchers use vertical writing in the narrow wall space. The
|
||||
compact Aux return stays horizontal so it consumes only one heading. */}
|
||||
compact Back action stays horizontal so it fits in the heading. */}
|
||||
<span className={compact ? 'flex items-center' : 'flex items-center gap-1 [writing-mode:vertical-rl] rotate-180'}>
|
||||
{!compact ? <FaPuzzlePiece className="shrink-0 text-sm" aria-hidden="true" /> : null}
|
||||
<span>{label}</span>
|
||||
|
||||
@@ -21,6 +21,10 @@ function trimUnicode(value, maximumLength) {
|
||||
}
|
||||
|
||||
const CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-2 py-1 text-slate-50';
|
||||
// Sliders already contain a label row and a separate track. Giving that
|
||||
// two-row control the same vertical padding as a one-row button made it much
|
||||
// taller than its neighbors without improving its touch target.
|
||||
const SLIDER_CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-1.5 py-0.5 text-slate-50';
|
||||
const DISABLED_CLASS = 'disabled:cursor-not-allowed disabled:opacity-40';
|
||||
|
||||
function SliderControl({ peripheralId, control, disabled, send, value: storedValue }) {
|
||||
@@ -97,7 +101,7 @@ function SliderControl({ peripheralId, control, disabled, send, value: storedVal
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${CARD_CLASS} border-emerald-300/70 bg-emerald-900 ${disabled ? 'cursor-not-allowed opacity-40' : ''}`}>
|
||||
<div className={`${SLIDER_CARD_CLASS} border-emerald-300/70 bg-emerald-900 ${disabled ? 'cursor-not-allowed opacity-40' : ''}`}>
|
||||
<div className="flex items-center justify-between gap-1 text-sm font-semibold">
|
||||
<span className="min-w-0 truncate">{control.name}</span>
|
||||
<span className="shrink-0 font-mono text-emerald-100">{value}</span>
|
||||
@@ -121,7 +125,7 @@ function SliderControl({ peripheralId, control, disabled, send, value: storedVal
|
||||
onKeyDown={handleKeyDown}
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
style={{ touchAction: 'none' }}
|
||||
className="mobile-touch-control mobile-drag-control relative mt-1 h-7 w-full rounded-full border border-emerald-100/80 bg-emerald-950 shadow-inner focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200"
|
||||
className="mobile-touch-control mobile-drag-control relative mt-0.5 h-6 w-full rounded-full border border-emerald-100/80 bg-emerald-950 shadow-inner focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200"
|
||||
>
|
||||
{/* An inset track gives the thumb room to remain entirely inside the
|
||||
card at both endpoints without browser-specific range styling. */}
|
||||
@@ -155,7 +159,7 @@ function ToggleControl({ peripheralId, control, disabled, send, value }) {
|
||||
aria-pressed={enabled}
|
||||
disabled={disabled}
|
||||
onClick={toggle}
|
||||
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-between gap-1 font-semibold transition active:scale-[0.99] ${enabled ? 'border-emerald-300/70 bg-emerald-800 text-emerald-50' : 'border-amber-300/70 bg-amber-900 text-amber-50'}`}
|
||||
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-between gap-1 font-semibold ${enabled ? 'border-emerald-300/70 bg-emerald-800 text-emerald-50' : 'border-amber-300/70 bg-amber-900 text-amber-50'}`}
|
||||
>
|
||||
<span className="min-w-0 truncate">{control.name}</span>
|
||||
<span className="shrink-0 text-xs">{enabled ? 'On' : 'Off'}</span>
|
||||
@@ -221,7 +225,7 @@ function MomentaryControl({ peripheralId, control, disabled, send, value }) {
|
||||
}
|
||||
}}
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-center text-center font-semibold transition active:scale-[0.99] ${pressed ? 'border-fuchsia-200 bg-fuchsia-600 text-white' : 'border-fuchsia-300/70 bg-fuchsia-900 text-fuchsia-50'}`}
|
||||
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-center text-center font-semibold ${pressed ? 'border-fuchsia-200 bg-fuchsia-600 text-white' : 'border-fuchsia-300/70 bg-fuchsia-900 text-fuchsia-50'}`}
|
||||
>
|
||||
{control.name}
|
||||
</button>
|
||||
|
||||
@@ -29,17 +29,17 @@ export default function RoverAccessoryControls({ roverId, headerAction = null, c
|
||||
aria-label="Rover accessories"
|
||||
>
|
||||
{peripherals.map((peripheral, peripheralIndex) => {
|
||||
const showHeading = peripherals.length > 1 || (peripheralIndex === 0 && headerAction);
|
||||
return (
|
||||
<section key={peripheral.id} className="mb-0.5 last:mb-0">
|
||||
<section key={peripheral.id} className="mb-0.5 flex flex-col gap-0.5 last:mb-0">
|
||||
{/* The firmware's array order is authoritative. Mapping directly over
|
||||
it keeps physical authoring order intact across every UI host. */}
|
||||
{showHeading ? (
|
||||
<div className="mb-0.5 flex min-h-7 items-center gap-1 bg-black/60 px-1 text-xs font-semibold text-cyan-100">
|
||||
<h3 className="min-w-0 flex-1 truncate">{peripheral.name}</h3>
|
||||
{peripheralIndex === 0 ? headerAction : null}
|
||||
</div>
|
||||
) : null}
|
||||
it keeps physical authoring order intact across every UI host.
|
||||
Every peripheral keeps its heading even when it is the only
|
||||
device, because its firmware-provided name identifies which
|
||||
physical accessory owns the controls below it. */}
|
||||
<div className="flex min-h-8 items-center gap-1 bg-black/60 px-1 text-xs font-semibold text-white">
|
||||
<h3 className="min-w-0 flex-1 truncate">{peripheral.name}</h3>
|
||||
{peripheralIndex === 0 ? headerAction : null}
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{peripheral.controls.map((control) => (
|
||||
<AccessoryControlField
|
||||
|
||||
Reference in New Issue
Block a user