Files
MultiRoombaRover/webui/src/controls/controlReducer.js
T
2025-11-17 21:18:43 -05:00

167 lines
4.0 KiB
JavaScript

import { DEFAULT_KEYMAP, DEFAULT_MACROS } from './constants.js';
function createDriveState() {
return {
vector: { x: 0, y: 0, boost: false },
speeds: { left: 0, right: 0 },
source: null,
lastUpdatedAt: 0,
};
}
function createAuxState() {
return { main: 0, side: 0, vacuum: 0 };
}
function createCameraState() {
return {
enabled: false,
angle: null,
config: null,
};
}
export const initialControlState = {
roverId: null,
mode: 'drive',
drive: createDriveState(),
aux: createAuxState(),
camera: createCameraState(),
macros: DEFAULT_MACROS,
keymap: DEFAULT_KEYMAP,
inputs: {},
settings: {
status: 'idle',
error: null,
lastLoadedAt: null,
lastSavedAt: null,
data: null,
},
};
function mergeSettings(state, payload) {
const nextSettings = {
status: 'ready',
error: null,
lastLoadedAt: Date.now(),
lastSavedAt: state.settings.lastSavedAt,
data: payload ?? state.settings.data,
};
const nextState = { ...state, settings: nextSettings };
if (payload?.keymap) {
nextState.keymap = { ...state.keymap, ...payload.keymap };
}
if (payload?.macros) {
nextState.macros = payload.macros;
}
return nextState;
}
export function controlReducer(state, action) {
switch (action.type) {
case 'control/set-rover':
return {
...state,
roverId: action.payload ?? null,
drive: action.payload ? state.drive : createDriveState(),
aux: action.payload ? state.aux : createAuxState(),
};
case 'control/set-mode':
return state.mode === action.payload
? state
: { ...state, mode: action.payload === 'dock' ? 'dock' : 'drive' };
case 'control/update-drive': {
const { vector, speeds, source } = action.payload;
return {
...state,
drive: {
vector: vector ?? state.drive.vector,
speeds: speeds ?? state.drive.speeds,
source: source ?? state.drive.source,
lastUpdatedAt: Date.now(),
},
};
}
case 'control/set-aux-motors':
return {
...state,
aux: {
...state.aux,
...action.payload,
},
};
case 'control/set-camera-config':
return {
...state,
camera: {
...state.camera,
enabled: Boolean(action.payload?.config),
config: action.payload?.config ?? null,
angle:
typeof action.payload?.angle === 'number'
? action.payload.angle
: action.payload?.config
? action.payload.config.homeAngle ?? state.camera.angle
: null,
},
};
case 'control/set-camera-angle':
return {
...state,
camera: { ...state.camera, angle: action.payload },
};
case 'control/register-input-state': {
const sourceKey = action.payload?.source || 'unknown';
return {
...state,
inputs: {
...state.inputs,
[sourceKey]: {
...(state.inputs[sourceKey] ?? {}),
...action.payload?.state,
updatedAt: Date.now(),
},
},
};
}
case 'control/settings/loading':
return {
...state,
settings: {
...state.settings,
status: 'loading',
error: null,
},
};
case 'control/settings/loaded':
return mergeSettings(state, action.payload);
case 'control/settings/error':
return {
...state,
settings: {
...state.settings,
status: 'error',
error: action.payload,
},
};
case 'control/set-keymap':
return {
...state,
keymap: { ...state.keymap, ...(action.payload ?? {}) },
};
case 'control/set-macros':
return {
...state,
macros: Array.isArray(action.payload) ? action.payload : state.macros,
};
case 'control/reset':
return {
...state,
drive: createDriveState(),
aux: createAuxState(),
};
default:
return state;
}
}