mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
peteze
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
@@ -78,7 +78,7 @@
|
|||||||
<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-9iBY2t1u.js"></script>
|
<script type="module" crossorigin src="/assets/index-CQLzYm8d.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C4u1g5xI.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C4u1g5xI.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// queueable controllable target instead of a VIP-panel card.
|
// queueable controllable target instead of a VIP-panel card.
|
||||||
// Scope: Owns PTZ entry card and fullscreen composition; PTZ command authority,
|
// Scope: Owns PTZ entry card and fullscreen composition; PTZ command authority,
|
||||||
// queue ownership, and stream authorization remain server-owned.
|
// queue ownership, and stream authorization remain server-owned.
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import CardFrame from '../CardFrame/index.jsx';
|
import CardFrame from '../CardFrame/index.jsx';
|
||||||
import ChatPanel from '../ChatPanel/index.jsx';
|
import ChatPanel from '../ChatPanel/index.jsx';
|
||||||
@@ -22,7 +22,6 @@ import { useSharedClock } from '../../hooks/useSharedClock.js';
|
|||||||
import { isFeatureEnabled } from '../../lib/features.js';
|
import { isFeatureEnabled } from '../../lib/features.js';
|
||||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||||
|
|
||||||
const PTZ_ZOOM_SPEED = 0.55;
|
|
||||||
const PTZ_DEFAULT_COLOR = '#38bdf8';
|
const PTZ_DEFAULT_COLOR = '#38bdf8';
|
||||||
|
|
||||||
function formatRemaining(deadline, now) {
|
function formatRemaining(deadline, now) {
|
||||||
@@ -220,21 +219,41 @@ function PtzLightingControls({ ptz, disabled = false }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PtzMobileZoomButtons({ disabled = false }) {
|
function PtzMobileZoomButtons({ disabled = false }) {
|
||||||
const { ptzMove, ptzStop } = useSessionActions();
|
const { nudgeServo, stopAllMotion } = useControlActions();
|
||||||
|
const repeatTimerRef = useRef(null);
|
||||||
|
|
||||||
const stopZoom = useCallback(() => {
|
const stopZoom = useCallback(() => {
|
||||||
ptzStop().catch(() => {});
|
/*
|
||||||
}, [ptzStop]);
|
Mobile zoom is intentionally routed through the normal camera-up/down
|
||||||
|
control action instead of emitting PTZ socket commands directly. That
|
||||||
|
keeps the zoom buttons on the same path as keyboard/gamepad camera tilt,
|
||||||
|
and the PTZ adapter remains the one place that translates "camera nudge"
|
||||||
|
into Reolink zoom pulses.
|
||||||
|
*/
|
||||||
|
if (repeatTimerRef.current) {
|
||||||
|
clearInterval(repeatTimerRef.current);
|
||||||
|
repeatTimerRef.current = null;
|
||||||
|
}
|
||||||
|
stopAllMotion();
|
||||||
|
}, [stopAllMotion]);
|
||||||
|
|
||||||
const startZoom = useCallback(
|
const startZoom = useCallback(
|
||||||
(direction) => (event) => {
|
(direction) => (event) => {
|
||||||
/*
|
/*
|
||||||
The rover mobile movement pad is reused for PTZ pan/tilt through the
|
Send an immediate nudge and then repeat while held. The adapter turns
|
||||||
control adapter, so zoom needs its own two hold buttons on mobile.
|
each nudge into a short zoom pulse, so repeating the standard action is
|
||||||
|
the simplest way to get continuous hold-to-zoom without adding another
|
||||||
|
PTZ-specific command loop.
|
||||||
*/
|
*/
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
ptzMove({ pan: 0, tilt: 0, zoom: direction * PTZ_ZOOM_SPEED }).catch(() => {});
|
stopZoom();
|
||||||
|
nudgeServo(direction);
|
||||||
|
repeatTimerRef.current = setInterval(() => {
|
||||||
|
nudgeServo(direction);
|
||||||
|
}, 120);
|
||||||
},
|
},
|
||||||
[disabled, ptzMove],
|
[disabled, nudgeServo, stopZoom],
|
||||||
);
|
);
|
||||||
const stopFromPointer = useCallback(
|
const stopFromPointer = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -245,6 +264,22 @@ function PtzMobileZoomButtons({ disabled = false }) {
|
|||||||
[disabled, stopZoom],
|
[disabled, stopZoom],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
/*
|
||||||
|
A touch surface can unmount during orientation changes or fullscreen
|
||||||
|
close while a pointer is still down. Clear the repeat timer here so a
|
||||||
|
held zoom button cannot keep firing camera-up/down actions after the
|
||||||
|
mobile controls have disappeared.
|
||||||
|
*/
|
||||||
|
if (repeatTimerRef.current) {
|
||||||
|
clearInterval(repeatTimerRef.current);
|
||||||
|
repeatTimerRef.current = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mobile-touch-control grid grid-cols-2 gap-0.5 text-sm">
|
<div className="mobile-touch-control grid grid-cols-2 gap-0.5 text-sm">
|
||||||
<button
|
<button
|
||||||
@@ -405,7 +440,7 @@ function PtzDesktopFullscreen({ ptz, releasePending }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PtzMobileFullscreen({ ptz, layout }) {
|
function PtzMobileFullscreen({ ptz, layout, onClose, releasePending = false }) {
|
||||||
const landscape = layout === 'mobile-landscape';
|
const landscape = layout === 'mobile-landscape';
|
||||||
const topHeightClass = landscape ? 'h-[72dvh]' : 'h-[48dvh]';
|
const topHeightClass = landscape ? 'h-[72dvh]' : 'h-[48dvh]';
|
||||||
const topGridClass = landscape
|
const topGridClass = landscape
|
||||||
@@ -415,7 +450,15 @@ function PtzMobileFullscreen({ ptz, layout }) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto p-0.5">
|
<div className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto p-0.5">
|
||||||
<section className={`mobile-touch-control grid ${topHeightClass} min-h-48 shrink-0 ${topGridClass} gap-0.5`}>
|
<section className={`mobile-touch-control grid ${topHeightClass} min-h-48 shrink-0 ${topGridClass} gap-0.5`}>
|
||||||
<main className="min-h-0 overflow-hidden bg-black">
|
<main className="relative min-h-0 overflow-hidden bg-black">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute left-1 top-1 z-50 rounded border border-white/40 bg-black/80 px-2 py-1 text-xs font-semibold text-white shadow disabled:opacity-50"
|
||||||
|
disabled={releasePending}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
<PtzMediaPane ptz={ptz} open framed={false} />
|
<PtzMediaPane ptz={ptz} open framed={false} />
|
||||||
</main>
|
</main>
|
||||||
<aside className="min-h-0 overflow-y-auto">
|
<aside className="min-h-0 overflow-y-auto">
|
||||||
@@ -462,19 +505,25 @@ export function PtzFullscreenController({ open, onClose, layout = 'desktop' }) {
|
|||||||
const controller = (
|
const controller = (
|
||||||
<div className="fixed inset-0 z-[110] h-[100dvh] w-[100vw] overflow-hidden bg-black text-slate-100">
|
<div className="fixed inset-0 z-[110] h-[100dvh] w-[100vw] overflow-hidden bg-black text-slate-100">
|
||||||
<CardFrame
|
<CardFrame
|
||||||
title={ptz?.name || 'PTZ Camera'}
|
title={isMobile ? '' : ptz?.name || 'PTZ Camera'}
|
||||||
actions={(
|
actions={isMobile ? null : (
|
||||||
<button type="button" className="button-dark text-xs" disabled={releasePending} onClick={releaseAndClose}>
|
<button type="button" className="button-dark text-xs" disabled={releasePending} onClick={releaseAndClose}>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
hideHeader={isMobile}
|
||||||
fillHeight
|
fillHeight
|
||||||
clipOverflow={false}
|
clipOverflow={false}
|
||||||
className="h-[100dvh] w-[100vw] rounded-none border-0 !bg-black"
|
className="h-[100dvh] w-[100vw] rounded-none border-0 !bg-black"
|
||||||
bodyClassName="relative min-h-0 flex-1"
|
bodyClassName="relative min-h-0 flex-1"
|
||||||
>
|
>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<PtzMobileFullscreen ptz={ptz} layout={layout} />
|
<PtzMobileFullscreen
|
||||||
|
ptz={ptz}
|
||||||
|
layout={layout}
|
||||||
|
onClose={releaseAndClose}
|
||||||
|
releasePending={releasePending}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PtzDesktopFullscreen ptz={ptz} releasePending={releasePending} />
|
<PtzDesktopFullscreen ptz={ptz} releasePending={releasePending} />
|
||||||
)}
|
)}
|
||||||
@@ -563,11 +612,11 @@ export default function PtzQueueCard({ layout = 'desktop' }) {
|
|||||||
id: ptz?.id || PTZ_CAMERA_ID,
|
id: ptz?.id || PTZ_CAMERA_ID,
|
||||||
name: ptz?.name || 'PTZ Camera',
|
name: ptz?.name || 'PTZ Camera',
|
||||||
color: ptz?.color || PTZ_DEFAULT_COLOR,
|
color: ptz?.color || PTZ_DEFAULT_COLOR,
|
||||||
description: ptz?.isOperator
|
// description: ptz?.isOperator
|
||||||
? 'Live camera turn active'
|
// ? 'Live camera turn active'
|
||||||
: ptz?.queuedPosition
|
// : ptz?.queuedPosition
|
||||||
? `Queue position ${ptz.queuedPosition}`
|
// ? `Queue position ${ptz.queuedPosition}`
|
||||||
: 'Pan, tilt, and zoom camera',
|
// : 'Pan, tilt, and zoom camera',
|
||||||
}}
|
}}
|
||||||
queue={queue}
|
queue={queue}
|
||||||
currentId={currentId}
|
currentId={currentId}
|
||||||
|
|||||||
Reference in New Issue
Block a user