mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
awawaa
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
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
@@ -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-73WeVot9.js"></script>
|
<script type="module" crossorigin src="/assets/index-aRmVid5V.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CsVbjGQr.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BU1LuRPM.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Purpose: Provides the verified-user entry point and fullscreen controller for the single Reolink PTZ camera.
|
// Purpose: Provides the verified-user entry point and fullscreen controller for the single Reolink PTZ camera.
|
||||||
// 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 { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaSearchMinus, FaSearchPlus, FaStop } from 'react-icons/fa';
|
|
||||||
import CardFrame from '../CardFrame/index.jsx';
|
import CardFrame from '../CardFrame/index.jsx';
|
||||||
import CameraTiltControl from '../CameraTiltControl/index.jsx';
|
import CameraTiltControl from '../CameraTiltControl/index.jsx';
|
||||||
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
|
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
|
||||||
@@ -14,7 +13,6 @@ import { usePtzCameraSnapshot } from '../../hooks/usePtzCameraSnapshot.js';
|
|||||||
import { useVideoRequests } from '../../hooks/useVideoRequests.js';
|
import { useVideoRequests } from '../../hooks/useVideoRequests.js';
|
||||||
import { WhepPlayer } from '../../lib/whepPlayer.js';
|
import { WhepPlayer } from '../../lib/whepPlayer.js';
|
||||||
import { isFeatureEnabled } from '../../lib/features.js';
|
import { isFeatureEnabled } from '../../lib/features.js';
|
||||||
import { innerFlowClass } from './constants.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;
|
||||||
@@ -105,44 +103,6 @@ function PtzQueueList({ queue = [], operatorLabel = '' }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ControlButton({ title, children, onHold, className = '' }) {
|
|
||||||
const activeRef = useRef(false);
|
|
||||||
|
|
||||||
const start = useCallback(
|
|
||||||
(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
activeRef.current = true;
|
|
||||||
onHold?.('start');
|
|
||||||
},
|
|
||||||
[onHold],
|
|
||||||
);
|
|
||||||
|
|
||||||
const stop = useCallback(
|
|
||||||
(event) => {
|
|
||||||
event?.preventDefault?.();
|
|
||||||
if (!activeRef.current) return;
|
|
||||||
activeRef.current = false;
|
|
||||||
onHold?.('stop');
|
|
||||||
},
|
|
||||||
[onHold],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
title={title}
|
|
||||||
aria-label={title}
|
|
||||||
onPointerDown={start}
|
|
||||||
onPointerUp={stop}
|
|
||||||
onPointerCancel={stop}
|
|
||||||
onPointerLeave={stop}
|
|
||||||
className={`button-dark flex h-10 min-w-10 items-center justify-center text-sm ${className}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PtzStatePanel({ ptz, onClose, onRelease, releaseDisabled = false }) {
|
function PtzStatePanel({ ptz, onClose, onRelease, releaseDisabled = false }) {
|
||||||
const spotlightOn = isSpotlightOn(ptz?.light);
|
const spotlightOn = isSpotlightOn(ptz?.light);
|
||||||
const irMode = normalizeIrMode(ptz?.ir?.state);
|
const irMode = normalizeIrMode(ptz?.ir?.state);
|
||||||
@@ -331,46 +291,6 @@ function PtzLightingControls({ ptz, disabled = false }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PtzManualControls({ disabled = false }) {
|
|
||||||
const { ptzMove, ptzStop } = useSessionActions();
|
|
||||||
const stopMotion = useCallback(() => {
|
|
||||||
ptzStop().catch(() => {});
|
|
||||||
}, [ptzStop]);
|
|
||||||
const holdMove = useCallback(
|
|
||||||
(payload) => (phase) => {
|
|
||||||
/*
|
|
||||||
These visible controls are only a fallback/secondary surface. Keyboard,
|
|
||||||
gamepad, and mobile drive input still route through the PTZ control
|
|
||||||
adapter so all normal movement mixing stays in the camera layer.
|
|
||||||
*/
|
|
||||||
if (disabled) return;
|
|
||||||
if (phase === 'start') ptzMove(payload).catch(() => {});
|
|
||||||
else stopMotion();
|
|
||||||
},
|
|
||||||
[disabled, ptzMove, stopMotion],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardFrame title="Manual controls" bodyClassName="space-y-0.5 p-1 text-sm">
|
|
||||||
<div className="grid grid-cols-3 gap-0.5">
|
|
||||||
<div />
|
|
||||||
<ControlButton title="Tilt up" onHold={holdMove({ tilt: 0.55 })} className="h-9"><FaArrowUp /></ControlButton>
|
|
||||||
<div />
|
|
||||||
<ControlButton title="Pan left" onHold={holdMove({ pan: -0.55 })} className="h-9"><FaArrowLeft /></ControlButton>
|
|
||||||
<ControlButton title="Stop" onHold={(phase) => phase === 'start' && !disabled && stopMotion()} className="h-9"><FaStop /></ControlButton>
|
|
||||||
<ControlButton title="Pan right" onHold={holdMove({ pan: 0.55 })} className="h-9"><FaArrowRight /></ControlButton>
|
|
||||||
<div />
|
|
||||||
<ControlButton title="Tilt down" onHold={holdMove({ tilt: -0.55 })} className="h-9"><FaArrowDown /></ControlButton>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-0.5">
|
|
||||||
<ControlButton title="Zoom in" onHold={holdMove({ zoom: 0.55 })} className="h-9"><FaSearchPlus /></ControlButton>
|
|
||||||
<ControlButton title="Zoom out" onHold={holdMove({ zoom: -0.55 })} className="h-9"><FaSearchMinus /></ControlButton>
|
|
||||||
</div>
|
|
||||||
</CardFrame>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PtzZoomControl({ disabled = false }) {
|
function PtzZoomControl({ disabled = false }) {
|
||||||
const { ptzMove, ptzStop } = useSessionActions();
|
const { ptzMove, ptzStop } = useSessionActions();
|
||||||
const sendZoom = useCallback(
|
const sendZoom = useCallback(
|
||||||
@@ -460,33 +380,52 @@ function PtzController({ open, onClose }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[110] bg-black text-slate-100">
|
<div className="fixed inset-0 z-[110] h-[100dvh] w-[100vw] overflow-hidden bg-black text-slate-100">
|
||||||
<CardFrame hideHeader fillHeight clipOverflow={false} className="h-screen w-screen border-0" bodyClassName="grid h-full min-h-0 grid-cols-[minmax(0,1fr)_8.75rem] md:grid-cols-[minmax(0,1fr)_18rem]">
|
<CardFrame
|
||||||
|
hideHeader
|
||||||
|
fillHeight
|
||||||
|
clipOverflow={false}
|
||||||
|
className="h-[100dvh] w-[100vw] rounded-none border-0 !bg-black"
|
||||||
|
bodyClassName="grid h-full min-h-0 overflow-hidden grid-cols-[minmax(0,1fr)_12rem] md:grid-cols-[minmax(0,1fr)_20rem]"
|
||||||
|
>
|
||||||
<main className="relative min-h-0 min-w-0 bg-black">
|
<main className="relative min-h-0 min-w-0 bg-black">
|
||||||
{isOperator ? <PtzLiveVideo enabled /> : <PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />}
|
{isOperator ? <PtzLiveVideo enabled /> : <PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />}
|
||||||
</main>
|
</main>
|
||||||
<aside className="flex min-h-0 flex-col gap-0.5 overflow-y-auto border-l border-neutral-600 bg-neutral-950 p-0.5 text-sm">
|
<aside className="flex h-full min-h-0 items-stretch overflow-hidden border-l border-neutral-600 bg-neutral-950 text-sm">
|
||||||
<PtzStatePanel
|
<div className="flex min-h-0 w-full flex-col gap-0.5 overflow-y-auto p-0.5">
|
||||||
ptz={ptz}
|
<div className="shrink-0">
|
||||||
onClose={onClose}
|
<PtzStatePanel
|
||||||
onRelease={isOperator ? releaseAndClose : null}
|
ptz={ptz}
|
||||||
releaseDisabled={releasePending}
|
onClose={onClose}
|
||||||
/>
|
onRelease={isOperator ? releaseAndClose : null}
|
||||||
<PtzQueueList queue={ptz?.queue} operatorLabel={ptz?.operatorLabel} />
|
releaseDisabled={releasePending}
|
||||||
{isOperator ? (
|
/>
|
||||||
<>
|
</div>
|
||||||
<PtzLightingControls ptz={ptz} />
|
<div className="shrink-0">
|
||||||
<PtzZoomControl />
|
<PtzQueueList queue={ptz?.queue} operatorLabel={ptz?.operatorLabel} />
|
||||||
<PtzManualControls />
|
</div>
|
||||||
</>
|
{isOperator ? (
|
||||||
) : (
|
<>
|
||||||
<CardFrame title="Controls" bodyClassName="p-1 text-xs text-slate-400">
|
<div className="shrink-0">
|
||||||
Live PTZ controls unlock when your camera turn is active.
|
<PtzLightingControls ptz={ptz} />
|
||||||
</CardFrame>
|
</div>
|
||||||
)}
|
<div className="shrink-0">
|
||||||
<PtzControlReference />
|
<PtzZoomControl />
|
||||||
<div className="min-h-[18rem]">
|
</div>
|
||||||
<ReplaySourcesPanel panelId="ptz-controller-replay" fillHeight />
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="shrink-0">
|
||||||
|
<CardFrame title="Controls" bodyClassName="p-1 text-xs text-slate-400">
|
||||||
|
Live PTZ controls unlock when your camera turn is active.
|
||||||
|
</CardFrame>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="shrink-0">
|
||||||
|
<PtzControlReference />
|
||||||
|
</div>
|
||||||
|
<div className="shrink-0">
|
||||||
|
<ReplaySourcesPanel panelId="ptz-controller-replay" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</CardFrame>
|
</CardFrame>
|
||||||
@@ -548,7 +487,7 @@ export default function VipPtzCameraCard({ onMessage, fullWidth = false }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CardFrame title="PTZ camera" className={wrapClass} bodyClassName="text-sm text-slate-300">
|
<CardFrame title="PTZ camera" className={wrapClass} bodyClassName="text-sm text-slate-300">
|
||||||
<div className={`${innerFlowClass} md:grid md:grid-cols-[minmax(0,16rem)_minmax(0,1fr)] md:items-start`}>
|
<div className="grid w-full gap-0.5 md:grid-cols-[minmax(16rem,0.8fr)_minmax(0,1.2fr)] md:items-start">
|
||||||
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />
|
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />
|
||||||
<div className="flex w-full min-w-0 flex-col gap-0.5">
|
<div className="flex w-full min-w-0 flex-col gap-0.5">
|
||||||
<div className="grid w-full grid-cols-2 gap-0.5 text-left text-xs">
|
<div className="grid w-full grid-cols-2 gap-0.5 text-left text-xs">
|
||||||
|
|||||||
Reference in New Issue
Block a user