refactorio

This commit is contained in:
legop3
2026-05-17 21:35:33 -04:00
parent 31f98a8e32
commit 27f31531ac
10 changed files with 234 additions and 196 deletions
+9 -22
View File
@@ -1,10 +1,11 @@
// Drive Dock Action
// 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 { useEffect, useMemo, useRef, useState } from 'react';
import { useMemo, useState } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
export function deriveDriveDockState(frame) {
const sensors = frame?.sensors || {};
@@ -103,17 +104,17 @@ export default function DriveDockAction({
}) {
const isMobile = layout === 'mobile';
const {
state: { roverId, keymap, manualDockAssist },
state: { roverId, keymap },
actions,
} = useControlSystem();
const dockAssist = useManualDockAssist();
const frame = useTelemetryFrame(roverId);
const state = driveDockState ?? deriveDriveDockState(frame);
const { driving, docked, charging, dockingInProgress } = state;
const [pending, setPending] = useState(null);
const [confirmOpen, setConfirmOpen] = useState(false);
const [showModal, setShowModal] = useState(false);
const manualAssistActive = Boolean(manualDockAssist?.active);
const wasDockedRef = useRef(false);
const manualAssistActive = Boolean(dockAssist.active);
const driveDisabled = !roverId || pending !== null;
const dockDisabled = !roverId || pending !== null;
@@ -139,25 +140,11 @@ export default function DriveDockAction({
const chargeValue = charging ? 'Charging' : docked ? 'Charging soon...' : '—';
const chargeTone = charging ? 'good' : docked ? 'warn' : 'bad';
useEffect(() => {
const justDocked = docked && !wasDockedRef.current;
if (manualAssistActive && justDocked) {
actions.sendSong([{ note: 84, duration: 6 }], { slot: 1 });
}
wasDockedRef.current = docked;
}, [actions, docked, manualAssistActive]);
useEffect(() => {
if (!manualAssistActive) return;
if (!charging) return;
actions.setManualDockAssistActive(false);
}, [actions, charging, manualAssistActive]);
const handleReturnToDrive = async () => {
if (!roverId || pending) return;
setPending('drive');
try {
actions.setManualDockAssistActive(false);
dockAssist.exitAssist();
actions.setMode('drive');
await actions.runMacro('drive-sequence');
} catch (err) {
@@ -173,7 +160,7 @@ export default function DriveDockAction({
setShowModal(false);
setPending('drive');
try {
actions.setManualDockAssistActive(false);
dockAssist.exitAssist();
actions.setMode('drive');
await actions.runMacro('drive-sequence');
} catch (err) {
@@ -187,7 +174,7 @@ export default function DriveDockAction({
if (!roverId || pending) return;
setPending('dock');
try {
actions.setManualDockAssistActive(true);
dockAssist.enterAssist();
} catch (err) {
alert(err.message);
} finally {
@@ -200,7 +187,7 @@ export default function DriveDockAction({
const handleOpenDock = () => {
if (dockDisabled) return;
if (manualAssistActive) {
actions.setManualDockAssistActive(false);
dockAssist.exitAssist();
return;
}
setShowModal(true);
@@ -1,31 +1,15 @@
import React from 'react';
import { useControlSystem } from '../../../controls/index.js';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useManualDockAssist } from '../../../features/manualDockAssist/useManualDockAssist.js';
function ManualDockAssistOverlay({ mobileHud = false }) {
const {
state: { manualDockAssist },
} = useControlSystem();
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const frame = useTelemetryFrame(roverId);
const sensors = frame?.sensors || {};
const chargingLabel = sensors?.chargingState?.label || '';
const docked = Boolean(sensors?.chargingSources?.homeBase);
const charging = docked && chargingLabel.toLowerCase() !== 'not charging' && chargingLabel !== '';
const active = Boolean(manualDockAssist?.active);
if (!active && !docked) return null;
const status = charging
? 'Docked and charging'
: docked
? 'Docked'
: 'Docking assist active';
const toneClass = charging
? 'border-emerald-200/70 bg-emerald-900/85 text-emerald-50'
: docked
? 'border-amber-200/70 bg-amber-900/85 text-amber-50'
: 'border-indigo-200/70 bg-indigo-900/85 text-indigo-50';
const { visible, statusLabel, statusTone } = useManualDockAssist({ manageLifecycle: true });
if (!visible) return null;
const toneClass =
statusTone === 'good'
? 'border-emerald-200/70 bg-emerald-900/85 text-emerald-50'
: statusTone === 'warn'
? 'border-amber-200/70 bg-amber-900/85 text-amber-50'
: 'border-indigo-200/70 bg-indigo-900/85 text-indigo-50';
return (
<div className="pointer-events-none absolute inset-0">
@@ -34,7 +18,7 @@ function ManualDockAssistOverlay({ mobileHud = false }) {
mobileHud ? 'text-[0.65rem]' : 'text-xs'
}`}
>
{status}
{statusLabel}
</div>
</div>
);
@@ -18,6 +18,7 @@ import {
AUX_ALL_FORWARD,
AUX_ALL_BACKWARD,
} from './constants.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
function MobileJoystickPanel({ layout }) {
const {
@@ -95,10 +96,11 @@ function MobileJoystickPanel({ layout }) {
function MobileActionsColumnContent({ layout }) {
const {
state: { roverId, camera, horn, manualDockAssist },
state: { roverId, camera, horn },
pipeline,
actions: { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn },
} = useControlSystem();
const dockAssist = useManualDockAssist();
const disabled = !roverId;
const activeRef = useRef(null);
const cameraConfig = camera?.config;
@@ -115,7 +117,7 @@ function MobileActionsColumnContent({ layout }) {
: typeof cameraConfig?.homeAngle === 'number'
? cameraConfig.homeAngle
: (cameraMin + cameraMax) / 2;
const cameraDisabled = Boolean(disabled || manualDockAssist?.active);
const cameraDisabled = Boolean(disabled || dockAssist.cameraLocked);
const handleNightVisionToggle = useCallback(
(nextOn) => {
+4 -2
View File
@@ -24,6 +24,7 @@ import { useSessionSelector } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js';
import ButtonBoxPanel from '../ButtonBoxPanel/index.jsx';
import { useState } from 'react';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
function TopDownMapPanel() {
const {
@@ -43,10 +44,11 @@ function TopDownMapPanel() {
function DriveDockPanel() {
const {
state: { roverId, keymap, camera, horn, manualDockAssist },
state: { roverId, keymap, camera, horn },
pipeline,
actions: { setServoAngle, setNightVision, startHorn, stopHorn },
} = useControlSystem();
const dockAssist = useManualDockAssist();
const driveDockState = useDriveDockState(roverId);
const hideInlineControls = driveDockState.docked && !driveDockState.driving;
@@ -68,7 +70,7 @@ function DriveDockPanel() {
const hornLabel = formatKeyLabel(keymap?.hornHonk?.[0]);
const upLabel = formatKeyLabel(keymap?.cameraUp?.[0]);
const downLabel = formatKeyLabel(keymap?.cameraDown?.[0]);
const cameraDisabled = Boolean(!roverId || manualDockAssist?.active);
const cameraDisabled = Boolean(!roverId || dockAssist.cameraLocked);
return (
<section className="panel-section grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] gap-0.5">
@@ -11,6 +11,7 @@ import {
} from './gamepadBindings.js';
import { subscribeGamepadHub } from './gamepadHub.js';
import { isTextEntryActive } from './inputFocusUtils.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
const SOURCE = 'gamepad';
const ZERO_VECTOR = { x: 0, y: 0, boost: false };
@@ -50,8 +51,6 @@ export default function GamepadInputManager() {
state,
actions: {
setMode,
setManualDockAssistActive,
toggleManualDockAssist,
setDriveVector,
setAuxMotors,
setServoAngle,
@@ -60,6 +59,7 @@ export default function GamepadInputManager() {
registerInputState,
},
} = useControlSystem();
const dockAssist = useManualDockAssist();
const { value: gamepadSettings, save: saveGamepadSettings } = useSettingsNamespace(
'gamepad',
GAMEPAD_SETTINGS_DEFAULTS,
@@ -245,7 +245,7 @@ export default function GamepadInputManager() {
}
if (outputs.buttons.driveMacro && handleButtonEdge('driveMacro', true)) {
setManualDockAssistActive(false);
dockAssist.exitAssist();
setMode('drive');
runMacro('drive-sequence');
} else if (!outputs.buttons.driveMacro) {
@@ -253,7 +253,7 @@ export default function GamepadInputManager() {
}
if (outputs.buttons.dockMacro && handleButtonEdge('dockMacro', true)) {
toggleManualDockAssist();
dockAssist.toggleAssist();
} else if (!outputs.buttons.dockMacro) {
handleButtonEdge('dockMacro', false);
}
@@ -290,12 +290,11 @@ export default function GamepadInputManager() {
handleCameraAxis,
registerInputState,
runMacro,
setManualDockAssistActive,
setAuxMotors,
setDriveVector,
setMode,
toggleManualDockAssist,
toggleNightVision,
dockAssist,
]);
return null;
@@ -9,6 +9,7 @@ import { isKeyboardCaptureLocked } from './keyboardCaptureLock.js';
import { isTextInputElement } from './inputFocusUtils.js';
import { useSettingsNamespace } from '../../settings/index.js';
import { INPUT_SETTINGS_DEFAULTS } from '../../settings/namespaces.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
import {
SONG_DEFAULT_DURATION,
SONG_DEFAULT_NOTE,
@@ -122,8 +123,6 @@ export default function KeyboardInputManager() {
state,
actions: {
setMode,
setManualDockAssistActive,
toggleManualDockAssist,
setDriveVector,
setAuxMotors,
nudgeServo,
@@ -139,6 +138,7 @@ export default function KeyboardInputManager() {
},
} = useControlSystem();
const homeAssistant = useSessionSelector((state) => state.session?.homeAssistant || null);
const dockAssist = useManualDockAssist();
const { homeAssistantSetState } = useSessionActions();
const { focusChat, blurChat, isChatFocused } = useChat();
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
@@ -374,11 +374,11 @@ export default function KeyboardInputManager() {
if (newlyPressed.length > 0) {
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
setManualDockAssistActive(false);
dockAssist.exitAssist();
setMode('drive');
runMacro('drive-sequence');
} else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
toggleManualDockAssist();
dockAssist.toggleAssist();
} else if (newlyPressed.some((token) => keymap.nightVisionToggle?.has(token))) {
toggleNightVision();
} else if (newlyPressed.some((token) => keymap.hornHonk?.has(token))) {
@@ -443,15 +443,14 @@ export default function KeyboardInputManager() {
keymap.micPtt,
resetAll,
runMacro,
setManualDockAssistActive,
setMicPttActive,
setMode,
stopAllMotion,
stopSongLoop,
startHorn,
stopHorn,
toggleManualDockAssist,
triggerHomeAssistantCycle,
dockAssist,
]);
const latestResetAllRef = useRef(resetAll);
@@ -0,0 +1,65 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useControlSystem } from '../../controls/index.js';
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
export function useManualDockAssist(options = {}) {
const { manageLifecycle = false } = options;
const {
state: { roverId, manualDockAssist },
actions,
} = useControlSystem();
const frame = useTelemetryFrame(roverId);
const sensors = frame?.sensors || {};
const chargingLabel = sensors?.chargingState?.label || '';
const docked = Boolean(sensors?.chargingSources?.homeBase);
const charging = docked && chargingLabel.toLowerCase() !== 'not charging' && chargingLabel !== '';
const active = Boolean(manualDockAssist?.active);
const wasDockedRef = useRef(false);
const enterAssist = useCallback(() => {
actions.setManualDockAssistActive(true);
}, [actions]);
const exitAssist = useCallback(() => {
actions.setManualDockAssistActive(false);
}, [actions]);
const toggleAssist = useCallback(() => {
actions.toggleManualDockAssist();
}, [actions]);
useEffect(() => {
if (!manageLifecycle) return;
const justDocked = docked && !wasDockedRef.current;
if (active && justDocked) {
actions.sendSong([{ note: 84, duration: 6 }], { slot: 1 });
}
wasDockedRef.current = docked;
}, [actions, active, docked, manageLifecycle]);
useEffect(() => {
if (!manageLifecycle || !active || !charging) return;
exitAssist();
}, [active, charging, exitAssist, manageLifecycle]);
const statusLabel = charging ? 'Docked and charging' : docked ? 'Docked' : 'Docking assist active';
const statusTone = charging ? 'good' : docked ? 'warn' : 'active';
const visible = active || docked;
return useMemo(
() => ({
active,
docked,
charging,
cameraLocked: active,
visible,
statusLabel,
statusTone,
enterAssist,
exitAssist,
toggleAssist,
}),
[active, charging, docked, enterAssist, exitAssist, statusLabel, statusTone, toggleAssist, visible],
);
}