mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
guh
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
@@ -5,7 +5,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>webui</title>
|
<title>webui</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DlSe_rwF.js"></script>
|
<script type="module" crossorigin src="/assets/index-CiyUnwMv.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BaCUS21F.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BaCUS21F.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -30,19 +30,6 @@ const CODE_ALIASES = {
|
|||||||
Equal: '=',
|
Equal: '=',
|
||||||
Backquote: '`',
|
Backquote: '`',
|
||||||
};
|
};
|
||||||
const SPECIAL_CODE_FROM_KEY = {
|
|
||||||
'[': 'BracketLeft',
|
|
||||||
']': 'BracketRight',
|
|
||||||
'\\': 'Backslash',
|
|
||||||
';': 'Semicolon',
|
|
||||||
"'": 'Quote',
|
|
||||||
',': 'Comma',
|
|
||||||
'.': 'Period',
|
|
||||||
'/': 'Slash',
|
|
||||||
'-': 'Minus',
|
|
||||||
'=': 'Equal',
|
|
||||||
'`': 'Backquote',
|
|
||||||
};
|
|
||||||
|
|
||||||
function shouldIgnoreEvent(event) {
|
function shouldIgnoreEvent(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
@@ -56,25 +43,35 @@ function shouldIgnoreEvent(event) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canonicalizeBinding(value) {
|
function canonicalizeValue(value) {
|
||||||
if (typeof value !== 'string') return '';
|
if (typeof value !== 'string') return '';
|
||||||
const lower = value.toLowerCase();
|
const lower = value.toLowerCase();
|
||||||
return KEY_ALIASES[lower] ?? lower;
|
return KEY_ALIASES[lower] ?? lower;
|
||||||
}
|
}
|
||||||
|
|
||||||
function deriveCodeFromKey(key) {
|
function resolveCodeToken(code) {
|
||||||
if (!key) return null;
|
if (!code || typeof code !== 'string') return '';
|
||||||
if (SPECIAL_CODE_FROM_KEY[key]) return SPECIAL_CODE_FROM_KEY[key];
|
if (CODE_ALIASES[code]) return CODE_ALIASES[code];
|
||||||
if (key.length === 1) {
|
if (code.startsWith('Key') && code.length === 4) {
|
||||||
if (key >= 'a' && key <= 'z') {
|
return code.slice(3).toLowerCase();
|
||||||
return `Key${key.toUpperCase()}`;
|
|
||||||
}
|
|
||||||
if (key >= '0' && key <= '9') {
|
|
||||||
return `Digit${key}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (key === 'shift') return null;
|
if (code.startsWith('Digit') && code.length === 6) {
|
||||||
return null;
|
return code.slice(5);
|
||||||
|
}
|
||||||
|
if (code.startsWith('Numpad') && code.length > 6) {
|
||||||
|
const suffix = code.slice(6);
|
||||||
|
if (/^[0-9]$/.test(suffix)) return suffix;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventToTokens(event) {
|
||||||
|
const tokens = new Set();
|
||||||
|
const keyToken = canonicalizeValue(event?.key ?? '');
|
||||||
|
if (keyToken) tokens.add(keyToken);
|
||||||
|
const codeToken = resolveCodeToken(event?.code ?? '');
|
||||||
|
if (codeToken) tokens.add(codeToken);
|
||||||
|
return Array.from(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeKeymap(keymap = {}) {
|
function normalizeKeymap(keymap = {}) {
|
||||||
@@ -82,34 +79,14 @@ function normalizeKeymap(keymap = {}) {
|
|||||||
const values = Array.isArray(bindings) ? bindings : [bindings];
|
const values = Array.isArray(bindings) ? bindings : [bindings];
|
||||||
const normalized = new Set();
|
const normalized = new Set();
|
||||||
values.forEach((value) => {
|
values.forEach((value) => {
|
||||||
const canonical = canonicalizeBinding(String(value));
|
const token = canonicalizeValue(String(value));
|
||||||
if (canonical) {
|
if (token) normalized.add(token);
|
||||||
normalized.add(canonical);
|
|
||||||
const derivedCode = deriveCodeFromKey(canonical);
|
|
||||||
if (derivedCode) normalized.add(derivedCode);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return [action, normalized];
|
return [action, normalized];
|
||||||
});
|
});
|
||||||
return Object.fromEntries(entries);
|
return Object.fromEntries(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canonicalizeEventKey(event) {
|
|
||||||
if (!event) return '';
|
|
||||||
const code = event.code;
|
|
||||||
if (code && CODE_ALIASES[code]) {
|
|
||||||
return CODE_ALIASES[code];
|
|
||||||
}
|
|
||||||
const key = event.key ?? '';
|
|
||||||
const lower = key.toLowerCase();
|
|
||||||
return KEY_ALIASES[lower] ?? lower;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindingHas(bindingSet, key) {
|
|
||||||
if (!bindingSet || bindingSet.size === 0) return false;
|
|
||||||
return bindingSet.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindingActive(bindingSet, keys) {
|
function bindingActive(bindingSet, keys) {
|
||||||
if (!bindingSet || bindingSet.size === 0) return false;
|
if (!bindingSet || bindingSet.size === 0) return false;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
@@ -165,20 +142,6 @@ function computeAuxMotors(keys, keymap) {
|
|||||||
return { main, side, vacuum };
|
return { main, side, vacuum };
|
||||||
}
|
}
|
||||||
|
|
||||||
function vectorsEqual(a, b) {
|
|
||||||
return (
|
|
||||||
a &&
|
|
||||||
b &&
|
|
||||||
a.x === b.x &&
|
|
||||||
a.y === b.y &&
|
|
||||||
a.boost === b.boost
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function auxEqual(a, b) {
|
|
||||||
return a && b && a.main === b.main && a.side === b.side && a.vacuum === b.vacuum;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function KeyboardInputManager() {
|
export default function KeyboardInputManager() {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@@ -193,36 +156,46 @@ export default function KeyboardInputManager() {
|
|||||||
},
|
},
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
const keymap = useMemo(() => normalizeKeymap(state.keymap), [state.keymap]);
|
const keymap = useMemo(() => normalizeKeymap(state.keymap), [state.keymap]);
|
||||||
const servoStep = Math.abs(state.camera?.config?.nudgeDegrees) || 1;
|
const actionTokens = useMemo(() => {
|
||||||
const pressedKeysRef = useRef(new Set());
|
const tokens = new Set();
|
||||||
|
Object.values(keymap).forEach((bindingSet) => {
|
||||||
|
if (!bindingSet) return;
|
||||||
|
bindingSet.forEach((token) => tokens.add(token));
|
||||||
|
});
|
||||||
|
return tokens;
|
||||||
|
}, [keymap]);
|
||||||
|
const servoStep = useMemo(
|
||||||
|
() => Math.abs(state.camera?.config?.nudgeDegrees || 1),
|
||||||
|
[state.camera?.config?.nudgeDegrees],
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeKeysRef = useRef(new Set());
|
||||||
const lastVectorRef = useRef(ZERO_VECTOR);
|
const lastVectorRef = useRef(ZERO_VECTOR);
|
||||||
const lastAuxRef = useRef(ZERO_AUX);
|
const lastAuxRef = useRef(ZERO_AUX);
|
||||||
const cameraHoldRef = useRef(new Set());
|
|
||||||
const servoIntervalRef = useRef(null);
|
const servoIntervalRef = useRef(null);
|
||||||
|
|
||||||
const actionKeys = useMemo(() => {
|
|
||||||
const entries = Object.values(keymap);
|
|
||||||
const set = new Set();
|
|
||||||
entries.forEach((bindingSet) => {
|
|
||||||
bindingSet?.forEach((binding) => set.add(binding));
|
|
||||||
});
|
|
||||||
return set;
|
|
||||||
}, [keymap]);
|
|
||||||
|
|
||||||
const driveFromKeys = useCallback(() => {
|
const driveFromKeys = useCallback(() => {
|
||||||
const keys = pressedKeysRef.current;
|
const keysSnapshot = new Set(activeKeysRef.current);
|
||||||
const vector = computeDriveVector(keys, keymap);
|
const vector = computeDriveVector(keysSnapshot, keymap);
|
||||||
const aux = computeAuxMotors(keys, keymap);
|
const aux = computeAuxMotors(keysSnapshot, keymap);
|
||||||
if (!vectorsEqual(vector, lastVectorRef.current)) {
|
if (
|
||||||
|
vector.x !== lastVectorRef.current.x ||
|
||||||
|
vector.y !== lastVectorRef.current.y ||
|
||||||
|
vector.boost !== lastVectorRef.current.boost
|
||||||
|
) {
|
||||||
lastVectorRef.current = vector;
|
lastVectorRef.current = vector;
|
||||||
setDriveVector(vector, { source: SOURCE });
|
setDriveVector(vector, { source: SOURCE });
|
||||||
}
|
}
|
||||||
if (!auxEqual(aux, lastAuxRef.current)) {
|
if (
|
||||||
|
aux.main !== lastAuxRef.current.main ||
|
||||||
|
aux.side !== lastAuxRef.current.side ||
|
||||||
|
aux.vacuum !== lastAuxRef.current.vacuum
|
||||||
|
) {
|
||||||
lastAuxRef.current = aux;
|
lastAuxRef.current = aux;
|
||||||
setAuxMotors(aux);
|
setAuxMotors(aux);
|
||||||
}
|
}
|
||||||
registerInputState(SOURCE, {
|
registerInputState(SOURCE, {
|
||||||
keys: Array.from(keys),
|
keys: Array.from(keysSnapshot),
|
||||||
vector,
|
vector,
|
||||||
aux,
|
aux,
|
||||||
});
|
});
|
||||||
@@ -235,36 +208,39 @@ export default function KeyboardInputManager() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startServoLoop = useCallback(() => {
|
const computeServoDirection = useCallback(() => {
|
||||||
if (servoIntervalRef.current) return;
|
const keysSnapshot = new Set(activeKeysRef.current);
|
||||||
|
const up = bindingActive(keymap.cameraUp, keysSnapshot);
|
||||||
|
const down = bindingActive(keymap.cameraDown, keysSnapshot);
|
||||||
|
return (up ? 1 : 0) - (down ? 1 : 0);
|
||||||
|
}, [keymap]);
|
||||||
|
|
||||||
|
const ensureServoLoop = useCallback(() => {
|
||||||
|
const direction = computeServoDirection();
|
||||||
|
if (direction === 0) {
|
||||||
|
stopServoLoop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (servoIntervalRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
const up = cameraHoldRef.current.has('up') ? 1 : 0;
|
const nextDirection = computeServoDirection();
|
||||||
const down = cameraHoldRef.current.has('down') ? 1 : 0;
|
if (nextDirection === 0) {
|
||||||
const direction = up - down;
|
stopServoLoop();
|
||||||
if (direction === 0) {
|
|
||||||
servoIntervalRef.current = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nudgeServo(direction * servoStep);
|
nudgeServo(nextDirection * servoStep);
|
||||||
servoIntervalRef.current = setTimeout(tick, SERVO_REPEAT_MS);
|
servoIntervalRef.current = setTimeout(tick, SERVO_REPEAT_MS);
|
||||||
};
|
};
|
||||||
servoIntervalRef.current = setTimeout(tick, 0);
|
servoIntervalRef.current = setTimeout(tick, 0);
|
||||||
}, [nudgeServo, servoStep]);
|
}, [computeServoDirection, nudgeServo, servoStep, stopServoLoop]);
|
||||||
|
|
||||||
const updateServoHold = useCallback(() => {
|
|
||||||
if (cameraHoldRef.current.size === 0) {
|
|
||||||
stopServoLoop();
|
|
||||||
} else {
|
|
||||||
startServoLoop();
|
|
||||||
}
|
|
||||||
}, [startServoLoop, stopServoLoop]);
|
|
||||||
|
|
||||||
const resetAll = useCallback(() => {
|
const resetAll = useCallback(() => {
|
||||||
pressedKeysRef.current.clear();
|
activeKeysRef.current.clear();
|
||||||
cameraHoldRef.current.clear();
|
|
||||||
stopServoLoop();
|
|
||||||
lastVectorRef.current = ZERO_VECTOR;
|
lastVectorRef.current = ZERO_VECTOR;
|
||||||
lastAuxRef.current = ZERO_AUX;
|
lastAuxRef.current = ZERO_AUX;
|
||||||
|
stopServoLoop();
|
||||||
stopAllMotion();
|
stopAllMotion();
|
||||||
registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
||||||
}, [registerInputState, stopAllMotion, stopServoLoop]);
|
}, [registerInputState, stopAllMotion, stopServoLoop]);
|
||||||
@@ -272,55 +248,32 @@ export default function KeyboardInputManager() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event) {
|
||||||
if (shouldIgnoreEvent(event)) return;
|
if (shouldIgnoreEvent(event)) return;
|
||||||
const key = canonicalizeEventKey(event);
|
const tokens = eventToTokens(event);
|
||||||
const codeToken = event.code;
|
if (tokens.length === 0) return;
|
||||||
if (!key) return;
|
if (tokens.some((token) => actionTokens.has(token))) {
|
||||||
if (actionKeys.has(key) || (codeToken && actionKeys.has(codeToken))) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
pressedKeysRef.current.add(key);
|
const newlyPressed = tokens.filter((token) => !activeKeysRef.current.has(token));
|
||||||
if (codeToken) {
|
newlyPressed.forEach((token) => activeKeysRef.current.add(token));
|
||||||
pressedKeysRef.current.add(codeToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bindingHas(keymap.driveMacro, key)) {
|
if (newlyPressed.length > 0) {
|
||||||
setMode('drive');
|
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
|
||||||
runMacro('drive-sequence');
|
setMode('drive');
|
||||||
} else if (bindingHas(keymap.dockMacro, key)) {
|
runMacro('drive-sequence');
|
||||||
setMode('dock');
|
} else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
|
||||||
runMacro('seek-dock');
|
setMode('dock');
|
||||||
} else if (bindingHas(keymap.cameraUp, key)) {
|
runMacro('seek-dock');
|
||||||
cameraHoldRef.current.add('up');
|
}
|
||||||
updateServoHold();
|
|
||||||
} else if (bindingHas(keymap.cameraDown, key)) {
|
|
||||||
cameraHoldRef.current.add('down');
|
|
||||||
updateServoHold();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureServoLoop();
|
||||||
driveFromKeys();
|
driveFromKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyUp(event) {
|
function handleKeyUp(event) {
|
||||||
const key = canonicalizeEventKey(event);
|
const tokens = eventToTokens(event);
|
||||||
const codeToken = event.code;
|
tokens.forEach((token) => activeKeysRef.current.delete(token));
|
||||||
if (key) {
|
ensureServoLoop();
|
||||||
pressedKeysRef.current.delete(key);
|
|
||||||
}
|
|
||||||
if (codeToken) {
|
|
||||||
pressedKeysRef.current.delete(codeToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bindingHas(keymap.cameraUp, key) || (codeToken && bindingHas(keymap.cameraUp, codeToken))) {
|
|
||||||
cameraHoldRef.current.delete('up');
|
|
||||||
updateServoHold();
|
|
||||||
} else if (
|
|
||||||
bindingHas(keymap.cameraDown, key) ||
|
|
||||||
(codeToken && bindingHas(keymap.cameraDown, codeToken))
|
|
||||||
) {
|
|
||||||
cameraHoldRef.current.delete('down');
|
|
||||||
updateServoHold();
|
|
||||||
}
|
|
||||||
|
|
||||||
driveFromKeys();
|
driveFromKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +289,7 @@ export default function KeyboardInputManager() {
|
|||||||
window.removeEventListener('keyup', handleKeyUp);
|
window.removeEventListener('keyup', handleKeyUp);
|
||||||
window.removeEventListener('blur', handleBlur);
|
window.removeEventListener('blur', handleBlur);
|
||||||
};
|
};
|
||||||
}, [actionKeys, driveFromKeys, keymap, resetAll, runMacro, setMode, updateServoHold]);
|
}, [actionTokens, driveFromKeys, ensureServoLoop, keymap.dockMacro, keymap.driveMacro, resetAll, runMacro, setMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetAll();
|
resetAll();
|
||||||
|
|||||||
Reference in New Issue
Block a user