import { useCallback, useEffect } from 'react'; import { useControlActions, useControlSelector } from '../../controls/index.js'; import { triggerTouchHaptic } from '../../lib/touchHaptics.js'; import CardFrame from '../../components/CardFrame/index.jsx'; import ControlPadPanel from '../../components/MobileControls/ControlPadPanel.jsx'; import GPIOToggleControl from '../../components/GPIOToggleControl/index.jsx'; import KeyPill from '../../components/vip/VipAudioUploadCard/KeyPill.jsx'; import ControlHint from '../../components/ControlHint/index.jsx'; function PtzLightingControls({ disabled = false }) { const { setHeadlight, setLaser } = useControlActions(); const { spotlightOn, irMode, pending } = useControlSelector((control) => control.ptzControls.lighting); const canControl = useControlSelector((control) => control.ptzControls.isActive); const blocked = disabled || !canControl; const cycleIr = () => { triggerTouchHaptic('button'); setLaser(); }; return (
); } function PtzMobileZoomButtons({ disabled = false }) { const { setCameraAxisIntent } = useControlActions(); const stopZoom = useCallback(() => { /* Zero only releases the zoom axis. The PTZ adapter combines it with any pan/tilt direction still held on the movement pad, so lifting one finger cannot erase the other finger's intent. */ setCameraAxisIntent(0); }, [setCameraAxisIntent]); const startZoom = useCallback( (direction) => (event) => { /* Publish held state once. The adapter owns the single motion heartbeat, so this button no longer creates a second interval whose queued callback could run after pointerup and restart zoom. */ event.preventDefault(); if (disabled) return; triggerTouchHaptic('button'); event.currentTarget.setPointerCapture?.(event.pointerId); setCameraAxisIntent(direction); }, [disabled, setCameraAxisIntent], ); const stopFromPointer = useCallback( (event) => { event?.preventDefault?.(); if (disabled) return; stopZoom(); }, [disabled, stopZoom], ); useEffect( () => () => { /* A touch surface can unmount during orientation changes or fullscreen close while a pointer is still down. Explicitly clear zoom here because an unmounted DOM node cannot deliver its pointerup/pointercancel event. */ setCameraAxisIntent(0); }, [setCameraAxisIntent], ); return (
); } function PtzMobileControlsPanel({ disabled = false }) { return (
{/* Reuse the rover control pad so touch intent still enters the normal control system. The PTZ adapter translates that same drive vector into pan/tilt commands only while this user is the PTZ operator. */}
); } function PtzControlReference() { const rows = [ ['Tilt up', 'driveForward'], ['Tilt down', 'driveBackward'], ['Pan left', 'driveLeft'], ['Pan right', 'driveRight'], ['Zoom in', 'cameraUp'], ['Zoom out', 'cameraDown'], ['Spotlight', 'headlightToggle'], ['Infrared mode', 'laserToggle'], ]; return ( {rows.map(([label, actionId]) => (
{label} } />
))}
); } export { PtzLightingControls, PtzMobileZoomButtons, PtzMobileControlsPanel, PtzControlReference };