slopfixing / issue 002

This commit is contained in:
legop3
2026-06-12 02:19:08 -04:00
parent be8c3d7b29
commit c0050498b2
36 changed files with 5841 additions and 299 deletions
@@ -2,7 +2,7 @@
// Purpose: Defines the Drive Dock Action module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo, useState } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
@@ -103,10 +103,9 @@ export default function DriveDockAction({
compactHeightClass = '',
}) {
const isMobile = layout === 'mobile';
const {
state: { roverId, keymap },
actions,
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const keymap = useControlSelector((control) => control.state.keymap);
const actions = useControlActions();
const dockAssist = useManualDockAssist();
const frame = useTelemetryFrame(roverId);
const state = driveDockState ?? deriveDriveDockState(frame);
+2 -4
View File
@@ -1,6 +1,6 @@
import { useSessionSelector } from '../../context/SessionContext.jsx';
import RoverMediaPlayer from '../RoverMediaPlayer/index.jsx';
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import { useDriverVideoModePolicy } from '../../hooks/useDriverVideoModePolicy.js';
import TurnsOverlay from '../HudOverlays/TurnsOverlay/index.jsx';
import HudOverlay from '../HudOverlays/HudOverlay/index.jsx';
@@ -15,9 +15,7 @@ import CardFrame from '../CardFrame/index.jsx';
export default function DriverVideo({ layoutFormat = 'desktop' }) {
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const videoMode = useDriverVideoModePolicy(roverId);
const {
state: { lastControlIntentAt },
} = useControlSystem();
const lastControlIntentAt = useControlSelector((control) => control.state.lastControlIntentAt);
if (!roverId) {
return (
+4 -4
View File
@@ -1,13 +1,13 @@
// Help Overlay
// Purpose: Defines the Help Overlay module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import HelpContentView from '../HelpContentView/index.jsx';
export default function HelpOverlay({ visible, layout, onClose, showOnLoad, onToggleShowOnLoad }) {
if (!visible) return null;
const keymap = useControlSelector((control) => control.state.keymap);
const { state } = useControlSystem();
if (!visible) return null;
const handleCheckbox = (event) => {
const keepShowing = !event.target.checked;
@@ -39,7 +39,7 @@ export default function HelpOverlay({ visible, layout, onClose, showOnLoad, onTo
</div>
</div>
<div className="max-h-[85vh] overflow-y-auto p-0.5">
<HelpContentView layout={layout} keymap={state?.keymap || {}} />
<HelpContentView layout={layout} keymap={keymap || {}} />
</div>
</div>
</div>
+3 -3
View File
@@ -1,12 +1,12 @@
// Help Panel
// Purpose: Defines the Help Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import CardFrame from '../CardFrame/index.jsx';
import HelpContentView from '../HelpContentView/index.jsx';
export default function HelpPanel({ layout, onOpenOverlay }) {
const { state } = useControlSystem();
const keymap = useControlSelector((control) => control.state.keymap);
const actions = (
<button
@@ -20,7 +20,7 @@ export default function HelpPanel({ layout, onOpenOverlay }) {
return (
<CardFrame title="Help" actions={actions} bodyClassName="space-y-0.5 text-sm">
<HelpContentView layout={layout} keymap={state?.keymap || {}} />
<HelpContentView layout={layout} keymap={keymap || {}} />
</CardFrame>
);
}
@@ -3,7 +3,7 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo } from 'react';
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import CardFrame from '../CardFrame/index.jsx';
@@ -169,9 +169,7 @@ function LampTile({ entity, connected, controlsLocked, onToggle, onSetColor, onS
}
export default function HomeAssistantControls() {
const {
state: { keymap },
} = useControlSystem();
const keymap = useControlSelector((control) => control.state.keymap);
const ha = useSessionSelector((state) => state.session?.homeAssistant || null);
const { homeAssistantToggle, homeAssistantSetLightColor, homeAssistantSetLightWhite } =
useSessionActions();
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useControlSystem } from '../../../controls/index.js';
import { useControlSelector } from '../../../controls/index.js';
import SocialButton from '../../SocialButton/index.jsx';
function TurnsOverlay({
@@ -15,9 +15,7 @@ function TurnsOverlay({
const turnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
const socketId = useSessionSelector((state) => state.session?.socketId || null);
const activeDrivers = useSessionSelector((state) => state.session?.activeDrivers ?? {});
const {
state: { lastControlIntentAt },
} = useControlSystem();
const lastControlIntentAt = useControlSelector((control) => control.state.lastControlIntentAt);
const [now, setNow] = useState(() => Date.now());
const [showTurnCue, setShowTurnCue] = useState(false);
const [turnCueStartAt, setTurnCueStartAt] = useState(null);
@@ -2,7 +2,7 @@
// Purpose: Defines the Keymap Settings module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo, useState, useEffect, useCallback } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import { DEFAULT_KEYMAP } from '../../controls/constants.js';
import { canonicalizeKeyInput, formatKeyLabel } from '../../controls/keymapUtils.js';
import { setKeyboardCaptureLocked } from '../../controls/inputs/keyboardCaptureLock.js';
@@ -191,10 +191,8 @@ function BindingRow({ action, value, isActive, onCaptureStart, onCancel, onCaptu
}
export default function KeymapSettings() {
const {
state: { keymap },
actions: { updateKeyBinding, resetKeyBindings },
} = useControlSystem();
const keymap = useControlSelector((control) => control.state.keymap);
const { updateKeyBinding, resetKeyBindings } = useControlActions();
const { value: inputSettings, save: saveInputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const keyboardSettings = {
...INPUT_SETTINGS_DEFAULTS.keyboard,
@@ -2,7 +2,7 @@
// Purpose: Assembles the mobile auxiliary controls column, which is the left column by default.
// Scope: Owns mobile aux/camera/night vision/horn wiring while reusing desktop variation components where intended.
import { useCallback, useRef } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
import HornControl from '../HornControl/index.jsx';
import NightVisionControl from '../NightVisionControl/index.jsx';
@@ -11,19 +11,20 @@ import VacuumControls from './VacuumControls.jsx';
import VerticalCameraTilt from './VerticalCameraTilt.jsx';
function AuxColumnContent() {
const {
state: { roverId, camera, horn },
pipeline,
actions: { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn },
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const camera = useControlSelector((control) => control.state.camera);
const horn = useControlSelector((control) => control.state.horn);
const nightVision = useControlSelector((control) => control.pipeline?.nightVision);
const nightVisionState = useControlSelector((control) => control.pipeline?.nightVisionState);
const pipelineHorn = useControlSelector((control) => control.pipeline?.horn);
const { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn } = useControlActions();
const dockAssist = useManualDockAssist();
const disabled = !roverId;
const activeAuxButtonRef = useRef(null);
const cameraConfig = camera?.config;
const cameraEnabled = Boolean(roverId && camera?.enabled && cameraConfig);
const nightVisionAvailable = Boolean(roverId && pipeline?.nightVision);
const nightVisionState = pipeline?.nightVisionState;
const hornAvailable = Boolean(roverId && pipeline?.horn);
const nightVisionAvailable = Boolean(roverId && nightVision);
const hornAvailable = Boolean(roverId && pipelineHorn);
const hornBlocked = horn?.overheated;
const cameraMin = typeof cameraConfig?.minAngle === 'number' ? cameraConfig.minAngle : -45;
const cameraMax = typeof cameraConfig?.maxAngle === 'number' ? cameraConfig.maxAngle : 45;
@@ -2,7 +2,7 @@
// Purpose: Provides the mobile movement control pad and speed mode selector.
// Scope: Converts touch pad cells into keyboard-style drive vectors; movement column owns drive/dock placement.
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import { normalizeKeymapEntries } from '../../controls/keymapUtils.js';
import {
computeKeyboardDriveVector,
@@ -29,10 +29,8 @@ function getSpeedModeConfig(speedMode) {
}
export default function ControlPadPanel({ disabled = false }) {
const {
state: { keymap: rawKeymap },
actions: { setDriveVector, registerInputState },
} = useControlSystem();
const rawKeymap = useControlSelector((control) => control.state.keymap);
const { setDriveVector, registerInputState } = useControlActions();
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const [speedMode, setSpeedMode] = useState('normal');
const [activeInputLabel, setActiveInputLabel] = useState('stop');
@@ -1,14 +1,12 @@
// Movement Column
// Purpose: Assembles the mobile movement column, which is the right column by default.
// Scope: Integrates drive/dock actions with the mobile control pad without hiding that dependency inside the pad.
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import DriveDockAction, { useDriveDockState } from '../DriveDockAction/index.jsx';
import ControlPadPanel from './ControlPadPanel.jsx';
function MovementColumnContent({ layout }) {
const {
state: { roverId },
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const driveDockState = useDriveDockState(roverId);
const dockedNotDriving = driveDockState.docked && !driveDockState.driving;
const expandAction = dockedNotDriving || driveDockState.dockingInProgress;
@@ -2,7 +2,7 @@
// Purpose: Defines the Overcurrent Limiter Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import { OVERCURRENT_GROUPS } from '../../controls/overcurrentLimiter.js';
import CardFrame from '../CardFrame/index.jsx';
@@ -26,10 +26,8 @@ function ProgressBar({ value, color = 'bg-emerald-500' }) {
}
export default function OvercurrentLimiterPanel() {
const {
state: { roverId },
overcurrentLimiter,
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const overcurrentLimiter = useControlSelector((control) => control.overcurrentLimiter);
const groups = useMemo(() => OVERCURRENT_GROUPS.map((group) => group.key), []);
return (
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlSelector } from '../../controls/index.js';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import NicknameForm from '../NicknameForm/index.jsx';
import SocialButton from '../SocialButton/index.jsx';
@@ -53,9 +53,9 @@ export default function QuickstartOverlay({
onOpenHelp,
onClose,
}) {
const { state } = useControlSystem();
const rawKeymap = useControlSelector((control) => control.state.keymap);
const isDesktop = layout === 'desktop';
const keymap = useMemo(() => state?.keymap || {}, [state?.keymap]);
const keymap = useMemo(() => rawKeymap || {}, [rawKeymap]);
if (!visible) return null;
+13 -14
View File
@@ -13,7 +13,7 @@ import Tabs, { Tab, TabList, TabPanel, TabPanels } from '../Tabs/index.jsx';
import TopDownMap from '../TopDownMap/index.jsx';
import DriveDockAction, { useDriveDockState } from '../DriveDockAction/index.jsx';
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import RoverQueuesPanel from '../RoverQueuesPanel/index.jsx';
import RawUserPilePanel from '../RawUserPilePanel/index.jsx';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
@@ -36,9 +36,7 @@ const CHAT_DOCK_MAX_HEIGHT = 300;
const CHAT_DOCK_BOTTOM_INSET = 8;
function TopDownMapPanel() {
const {
state: { roverId },
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const frame = useTelemetryFrame(roverId);
const sensors = frame?.sensors || {};
@@ -54,20 +52,22 @@ function TopDownMapPanel() {
}
function DriveDockPanel() {
const {
state: { roverId, keymap, camera, horn },
pipeline,
actions: { setServoAngle, setNightVision, startHorn, stopHorn },
} = useControlSystem();
const roverId = useControlSelector((control) => control.state.roverId);
const keymap = useControlSelector((control) => control.state.keymap);
const camera = useControlSelector((control) => control.state.camera);
const horn = useControlSelector((control) => control.state.horn);
const nightVision = useControlSelector((control) => control.pipeline?.nightVision);
const nightVisionState = useControlSelector((control) => control.pipeline?.nightVisionState);
const pipelineHorn = useControlSelector((control) => control.pipeline?.horn);
const { setServoAngle, setNightVision, startHorn, stopHorn } = useControlActions();
const dockAssist = useManualDockAssist();
const driveDockState = useDriveDockState(roverId);
const hideInlineControls = driveDockState.docked && !driveDockState.driving;
const config = camera?.config;
const cameraEnabled = Boolean(roverId && camera?.enabled && config);
const nightVisionAvailable = Boolean(roverId && pipeline?.nightVision);
const nightVisionState = pipeline?.nightVisionState;
const hornAvailable = Boolean(roverId && pipeline?.horn);
const nightVisionAvailable = Boolean(roverId && nightVision);
const hornAvailable = Boolean(roverId && pipelineHorn);
const hornBlocked = horn?.overheated;
const min = typeof config?.minAngle === 'number' ? config.minAngle : -30;
const max = typeof config?.maxAngle === 'number' ? config.maxAngle : 30;
@@ -146,10 +146,9 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
const roverId = String(state.session?.assignment?.roverId || '').trim();
return roverId ? state.session?.audioForward?.[roverId] || null : null;
});
const { state: controlState } = useControlSystem();
const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive));
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
const vipDotClass = isVerified ? 'bg-emerald-400' : 'bg-red-600';
const pttActive = Boolean(controlState?.mic?.pttActive);
const openMicEnabled = Boolean(vipAudio?.openMicEnabled);
const pttMode = vipAudio?.pttMode === 'clip' ? 'clip' : 'live';
const vipMicActive = Boolean(
+4 -5
View File
@@ -2,7 +2,7 @@
// Purpose: Defines the Settings Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import AuthPanel from '../AuthPanel/index.jsx';
import AdminPanel from '../AdminPanel/index.jsx';
import KeymapSettings from '../KeymapSettings/index.jsx';
@@ -91,10 +91,9 @@ function RangeSetting({ label, value, disabled = false, onChange }) {
}
export default function SettingsPanel() {
const {
state: { keymap, roverId },
actions: { sendOiCommand, setSensorStream },
} = useControlSystem();
const keymap = useControlSelector((control) => control.state.keymap);
const roverId = useControlSelector((control) => control.state.roverId);
const { sendOiCommand, setSensorStream } = useControlActions();
const canControl = Boolean(roverId);
const [hudMapDesktop, setHudMapDesktop] = useHudMapSetting();
const socket = useSocket();
@@ -3,7 +3,7 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { fieldClass } from '../constants.js';
import { useControlSystem } from '../../../controls/index.js';
import { useControlSelector } from '../../../controls/index.js';
import { formatKeyLabel } from '../../../controls/keymapUtils.js';
import { useSettingsNamespace } from '../../../settings/index.js';
import { MAX_UPLOAD_BYTES, TARGET_SAMPLE_RATE, RTC_CONFIG } from './constants.js';
@@ -28,7 +28,8 @@ export default function VipAudioUploadCard({
readyMicWhip,
stopMicWhip,
}) {
const { state: controlState } = useControlSystem();
const keymap = useControlSelector((control) => control.state.keymap);
const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive));
const { value: vipAudio, save: saveVipAudio } = useSettingsNamespace('vipAudio', {
openMicEnabled: false,
pttMode: 'live',
@@ -60,7 +61,6 @@ export default function VipAudioUploadCard({
const clipRecordingRef = useRef(false);
const clipSampleRateRef = useRef(TARGET_SAMPLE_RATE);
const pttActive = Boolean(controlState?.mic?.pttActive);
const selectedForwardState = useMemo(
() => (roverId ? audioForwardByRover?.[roverId] || null : null),
@@ -81,7 +81,7 @@ export default function VipAudioUploadCard({
const whipLinkActive = !clipMode && (micState === 'live' || micState === 'starting');
const clipRecording = clipMode && clipState === 'recording';
const clipSending = clipMode && clipState === 'sending';
const pttKeyLabel = formatKeyLabel(controlState?.keymap?.micPtt?.[0]) || 'M';
const pttKeyLabel = formatKeyLabel(keymap?.micPtt?.[0]) || 'M';
const setPttMode = useCallback(
(nextMode) => {