mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -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/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>
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// queueable controllable target instead of a VIP-panel card.
|
||||
// Scope: Owns PTZ entry card and fullscreen composition; PTZ command authority,
|
||||
// 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 CardFrame from '../CardFrame/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 { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||
|
||||
const PTZ_ZOOM_SPEED = 0.55;
|
||||
const PTZ_DEFAULT_COLOR = '#38bdf8';
|
||||
|
||||
function formatRemaining(deadline, now) {
|
||||
@@ -220,21 +219,41 @@ function PtzLightingControls({ ptz, disabled = false }) {
|
||||
}
|
||||
|
||||
function PtzMobileZoomButtons({ disabled = false }) {
|
||||
const { ptzMove, ptzStop } = useSessionActions();
|
||||
const { nudgeServo, stopAllMotion } = useControlActions();
|
||||
const repeatTimerRef = useRef(null);
|
||||
|
||||
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(
|
||||
(direction) => (event) => {
|
||||
/*
|
||||
The rover mobile movement pad is reused for PTZ pan/tilt through the
|
||||
control adapter, so zoom needs its own two hold buttons on mobile.
|
||||
Send an immediate nudge and then repeat while held. The adapter turns
|
||||
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();
|
||||
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(
|
||||
(event) => {
|
||||
@@ -245,6 +264,22 @@ function PtzMobileZoomButtons({ disabled = false }) {
|
||||
[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 (
|
||||
<div className="mobile-touch-control grid grid-cols-2 gap-0.5 text-sm">
|
||||
<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 topHeightClass = landscape ? 'h-[72dvh]' : 'h-[48dvh]';
|
||||
const topGridClass = landscape
|
||||
@@ -415,7 +450,15 @@ function PtzMobileFullscreen({ ptz, layout }) {
|
||||
return (
|
||||
<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`}>
|
||||
<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} />
|
||||
</main>
|
||||
<aside className="min-h-0 overflow-y-auto">
|
||||
@@ -462,19 +505,25 @@ export function PtzFullscreenController({ open, onClose, layout = 'desktop' }) {
|
||||
const controller = (
|
||||
<div className="fixed inset-0 z-[110] h-[100dvh] w-[100vw] overflow-hidden bg-black text-slate-100">
|
||||
<CardFrame
|
||||
title={ptz?.name || 'PTZ Camera'}
|
||||
actions={(
|
||||
title={isMobile ? '' : ptz?.name || 'PTZ Camera'}
|
||||
actions={isMobile ? null : (
|
||||
<button type="button" className="button-dark text-xs" disabled={releasePending} onClick={releaseAndClose}>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
hideHeader={isMobile}
|
||||
fillHeight
|
||||
clipOverflow={false}
|
||||
className="h-[100dvh] w-[100vw] rounded-none border-0 !bg-black"
|
||||
bodyClassName="relative min-h-0 flex-1"
|
||||
>
|
||||
{isMobile ? (
|
||||
<PtzMobileFullscreen ptz={ptz} layout={layout} />
|
||||
<PtzMobileFullscreen
|
||||
ptz={ptz}
|
||||
layout={layout}
|
||||
onClose={releaseAndClose}
|
||||
releasePending={releasePending}
|
||||
/>
|
||||
) : (
|
||||
<PtzDesktopFullscreen ptz={ptz} releasePending={releasePending} />
|
||||
)}
|
||||
@@ -563,11 +612,11 @@ export default function PtzQueueCard({ layout = 'desktop' }) {
|
||||
id: ptz?.id || PTZ_CAMERA_ID,
|
||||
name: ptz?.name || 'PTZ Camera',
|
||||
color: ptz?.color || PTZ_DEFAULT_COLOR,
|
||||
description: ptz?.isOperator
|
||||
? 'Live camera turn active'
|
||||
: ptz?.queuedPosition
|
||||
? `Queue position ${ptz.queuedPosition}`
|
||||
: 'Pan, tilt, and zoom camera',
|
||||
// description: ptz?.isOperator
|
||||
// ? 'Live camera turn active'
|
||||
// : ptz?.queuedPosition
|
||||
// ? `Queue position ${ptz.queuedPosition}`
|
||||
// : 'Pan, tilt, and zoom camera',
|
||||
}}
|
||||
queue={queue}
|
||||
currentId={currentId}
|
||||
|
||||
Reference in New Issue
Block a user