mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
refactorio
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user