import { useEffect, useRef, useState } from 'react'; import { useControlSystem } from '../controls/index.js'; const SLIDER_THROTTLE_MS = 150; function formatDegrees(value) { if (typeof value !== 'number' || Number.isNaN(value)) return '—'; return `${value > 0 ? '+' : ''}${value.toFixed(1)}°`; } export default function CameraServoPanel() { const { state: { roverId, camera }, actions: { setServoAngle, nudgeServo, goServoHome }, } = useControlSystem(); const config = camera?.config; const enabled = Boolean(roverId && camera?.enabled && config); const min = typeof config?.minAngle === 'number' ? config.minAngle : -30; const max = typeof config?.maxAngle === 'number' ? config.maxAngle : 30; const value = typeof camera?.angle === 'number' ? camera.angle : typeof config?.homeAngle === 'number' ? config.homeAngle : (min + max) / 2; if (!enabled) return null; const [pendingAngle, setPendingAngle] = useState(value); const throttleRef = useRef(null); const draggingRef = useRef(false); useEffect(() => { if (!draggingRef.current) { setPendingAngle(value); } }, [value]); useEffect( () => () => { if (throttleRef.current) { clearTimeout(throttleRef.current); } }, [], ); const step = typeof config?.nudgeDegrees === 'number' && config.nudgeDegrees > 0 ? config.nudgeDegrees : 1; const scheduleSend = (next) => { if (throttleRef.current) { clearTimeout(throttleRef.current); } throttleRef.current = setTimeout(() => { setServoAngle(next); }, SLIDER_THROTTLE_MS); }; const handleSlider = (event) => { const next = Number.parseFloat(event.target.value); if (Number.isNaN(next)) return; draggingRef.current = true; setPendingAngle(next); scheduleSend(next); }; const commitSlider = () => { draggingRef.current = false; if (throttleRef.current) { clearTimeout(throttleRef.current); } setServoAngle(pendingAngle); }; const handleNudge = (direction) => { const delta = step * direction; nudgeServo(delta); }; return (
Camera Tilt {formatDegrees(value)}
{formatDegrees(min)} {formatDegrees(max)}

Step: {formatDegrees(step)} · Pin GPIO {config?.pin}

); }