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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-X92JgDHr.js"></script>
<script type="module" crossorigin src="/assets/index-D-6ytlM1.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CkqCBh_q.css">
</head>
<body>
+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],
);
}