This commit is contained in:
legop3
2025-11-17 23:22:15 -05:00
parent f8d01a810c
commit a038bf775a
4 changed files with 106 additions and 153 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
@@ -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') { if (code.startsWith('Digit') && code.length === 6) {
return `Digit${key}`; return code.slice(5);
} }
if (code.startsWith('Numpad') && code.length > 6) {
const suffix = code.slice(6);
if (/^[0-9]$/.test(suffix)) return suffix;
} }
if (key === 'shift') return null; return '';
return null; }
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 tick = () => { const up = bindingActive(keymap.cameraUp, keysSnapshot);
const up = cameraHoldRef.current.has('up') ? 1 : 0; const down = bindingActive(keymap.cameraDown, keysSnapshot);
const down = cameraHoldRef.current.has('down') ? 1 : 0; return (up ? 1 : 0) - (down ? 1 : 0);
const direction = up - down; }, [keymap]);
const ensureServoLoop = useCallback(() => {
const direction = computeServoDirection();
if (direction === 0) { if (direction === 0) {
servoIntervalRef.current = null; stopServoLoop();
return; return;
} }
nudgeServo(direction * servoStep); if (servoIntervalRef.current) {
return;
}
const tick = () => {
const nextDirection = computeServoDirection();
if (nextDirection === 0) {
stopServoLoop();
return;
}
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) {
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
setMode('drive'); setMode('drive');
runMacro('drive-sequence'); runMacro('drive-sequence');
} else if (bindingHas(keymap.dockMacro, key)) { } else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
setMode('dock'); setMode('dock');
runMacro('seek-dock'); runMacro('seek-dock');
} else if (bindingHas(keymap.cameraUp, key)) { }
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();