mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 01:50:47 -04:00
awawea
This commit is contained in:
File diff suppressed because one or more lines are too long
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
@@ -78,8 +78,8 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-aRmVid5V.js"></script>
|
<script type="module" crossorigin src="/assets/index-CYuyI8Zg.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BU1LuRPM.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CoyqmROJ.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// Scope: Owns PTZ UI state only; server-side PTZ ownership, rover handoff, and command authorization remain authoritative.
|
// Scope: Owns PTZ UI state only; server-side PTZ ownership, rover handoff, and command authorization remain authoritative.
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import CardFrame from '../CardFrame/index.jsx';
|
import CardFrame from '../CardFrame/index.jsx';
|
||||||
import CameraTiltControl from '../CameraTiltControl/index.jsx';
|
|
||||||
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
|
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
|
||||||
import ReplaySourcesPanel from '../ReplaySourcesPanel/index.jsx';
|
import ReplaySourcesPanel from '../ReplaySourcesPanel/index.jsx';
|
||||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
||||||
@@ -16,8 +15,7 @@ import { isFeatureEnabled } from '../../lib/features.js';
|
|||||||
|
|
||||||
const PTZ_CAMERA_ID = 'ptz-camera';
|
const PTZ_CAMERA_ID = 'ptz-camera';
|
||||||
const PTZ_AUDIO_RETRY_MS = 1000;
|
const PTZ_AUDIO_RETRY_MS = 1000;
|
||||||
const PTZ_ZOOM_SLIDER_MIN = -1;
|
const PTZ_ZOOM_SPEED = 0.55;
|
||||||
const PTZ_ZOOM_SLIDER_MAX = 1;
|
|
||||||
|
|
||||||
function formatRemaining(deadline) {
|
function formatRemaining(deadline) {
|
||||||
const remaining = Math.max(0, Math.ceil((Number(deadline || 0) - Date.now()) / 1000));
|
const remaining = Math.max(0, Math.ceil((Number(deadline || 0) - Date.now()) / 1000));
|
||||||
@@ -291,61 +289,81 @@ function PtzLightingControls({ ptz, disabled = false }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PtzZoomControl({ disabled = false }) {
|
function PtzMobileZoomButtons({ disabled = false }) {
|
||||||
const { ptzMove, ptzStop } = useSessionActions();
|
const { ptzMove, ptzStop } = useSessionActions();
|
||||||
const sendZoom = useCallback(
|
|
||||||
(value) => {
|
|
||||||
if (disabled) return;
|
|
||||||
const zoom = Math.max(-1, Math.min(1, Number(value) || 0));
|
|
||||||
if (!zoom) {
|
|
||||||
ptzStop().catch(() => {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ptzMove({ pan: 0, tilt: 0, zoom }).catch(() => {});
|
|
||||||
},
|
|
||||||
[disabled, ptzMove, ptzStop],
|
|
||||||
);
|
|
||||||
const stopZoom = useCallback(() => {
|
const stopZoom = useCallback(() => {
|
||||||
ptzStop().catch(() => {});
|
ptzStop().catch(() => {});
|
||||||
}, [ptzStop]);
|
}, [ptzStop]);
|
||||||
|
const startZoom = useCallback(
|
||||||
|
(direction) => (event) => {
|
||||||
|
/*
|
||||||
|
Mobile needs explicit zoom targets because the regular mobile drive pad
|
||||||
|
is already used for pan/tilt. Desktop does not render these buttons; it
|
||||||
|
uses the mapped camera up/down controls shown in the reference panel.
|
||||||
|
*/
|
||||||
|
event.preventDefault();
|
||||||
|
if (disabled) return;
|
||||||
|
ptzMove({ pan: 0, tilt: 0, zoom: direction * PTZ_ZOOM_SPEED }).catch(() => {});
|
||||||
|
},
|
||||||
|
[disabled, ptzMove],
|
||||||
|
);
|
||||||
|
const stopFromPointer = useCallback(
|
||||||
|
(event) => {
|
||||||
|
event?.preventDefault?.();
|
||||||
|
if (disabled) return;
|
||||||
|
stopZoom();
|
||||||
|
},
|
||||||
|
[disabled, stopZoom],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardFrame title="Zoom" bodyClassName="p-1 text-sm">
|
<CardFrame title="Zoom" className="md:hidden" bodyClassName="grid grid-cols-2 gap-0.5 p-1 text-sm">
|
||||||
<CameraTiltControl
|
<button
|
||||||
value={0}
|
type="button"
|
||||||
min={PTZ_ZOOM_SLIDER_MIN}
|
className="mobile-touch-control button-dark min-h-12 text-xs disabled:opacity-50"
|
||||||
max={PTZ_ZOOM_SLIDER_MAX}
|
|
||||||
step={1}
|
|
||||||
label="Zoom"
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={sendZoom}
|
onPointerDown={startZoom(-1)}
|
||||||
onCommit={stopZoom}
|
onPointerUp={stopFromPointer}
|
||||||
className="rounded-xl border-2 border-emerald-300/70 bg-emerald-900 px-1 py-1 text-emerald-50"
|
onPointerCancel={stopFromPointer}
|
||||||
labelRowClass="text-xs text-emerald-100"
|
onPointerLeave={stopFromPointer}
|
||||||
labelClass="text-sm font-semibold"
|
onContextMenu={(event) => event.preventDefault()}
|
||||||
valueClass="hidden"
|
>
|
||||||
sliderClass="w-full"
|
Zoom out
|
||||||
accentClass="accent-emerald-400"
|
</button>
|
||||||
endpointClass="text-[0.7rem] text-emerald-100/80"
|
<button
|
||||||
endpointLabelClass="w-14"
|
type="button"
|
||||||
/>
|
className="mobile-touch-control button-dark min-h-12 text-xs disabled:opacity-50"
|
||||||
<div className="mt-0.5 flex justify-between text-[0.7rem] text-slate-400">
|
disabled={disabled}
|
||||||
<span>out</span>
|
onPointerDown={startZoom(1)}
|
||||||
<span>release stops</span>
|
onPointerUp={stopFromPointer}
|
||||||
<span>in</span>
|
onPointerCancel={stopFromPointer}
|
||||||
|
onPointerLeave={stopFromPointer}
|
||||||
|
onContextMenu={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
Zoom in
|
||||||
|
</button>
|
||||||
|
<div className="col-span-2 text-center text-[0.7rem] text-slate-400">
|
||||||
|
Hold to zoom, release to stop
|
||||||
</div>
|
</div>
|
||||||
</CardFrame>
|
</CardFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyLabelFor(keymap, actionId) {
|
||||||
|
return formatKeyLabel(keymap?.[actionId]?.[0]);
|
||||||
|
}
|
||||||
|
|
||||||
function PtzControlReference() {
|
function PtzControlReference() {
|
||||||
const keymap = useControlSelector((control) => control.state.keymap);
|
const keymap = useControlSelector((control) => control.state.keymap);
|
||||||
const rows = [
|
const rows = [
|
||||||
['Pan / tilt', 'Drive movement'],
|
['Tilt up', keyLabelFor(keymap, 'driveForward')],
|
||||||
['Zoom in', formatKeyLabel(keymap?.cameraUp?.[0]) || 'camera up'],
|
['Tilt down', keyLabelFor(keymap, 'driveBackward')],
|
||||||
['Zoom out', formatKeyLabel(keymap?.cameraDown?.[0]) || 'camera down'],
|
['Pan left', keyLabelFor(keymap, 'driveLeft')],
|
||||||
['Spotlight', formatKeyLabel(keymap?.headlightToggle?.[0]) || 'headlight'],
|
['Pan right', keyLabelFor(keymap, 'driveRight')],
|
||||||
['Infrared mode', formatKeyLabel(keymap?.laserToggle?.[0]) || 'laser'],
|
['Zoom in', keyLabelFor(keymap, 'cameraUp')],
|
||||||
|
['Zoom out', keyLabelFor(keymap, 'cameraDown')],
|
||||||
|
['Spotlight', keyLabelFor(keymap, 'headlightToggle')],
|
||||||
|
['Infrared mode', keyLabelFor(keymap, 'laserToggle')],
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -410,7 +428,7 @@ function PtzController({ open, onClose }) {
|
|||||||
<PtzLightingControls ptz={ptz} />
|
<PtzLightingControls ptz={ptz} />
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<PtzZoomControl />
|
<PtzMobileZoomButtons />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user