Visualizer work
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

201 lines
5.3 KiB

import { appState } from "./state.js";
import {
playPauseBtn,
videoPlayer,
toggleSnrColor,
toggleClusterColor,
toggleInlierColor,
toggleStationaryColor,
themeToggleBtn,
toggleTracks,
toggleVelocity,
toggleCloseUp,
togglePredictedPos,
toggleDebugOverlay,
toggleDebug2Overlay,
collapsibleMenu,
toggleMenuBtn,
closeMenuBtn,
updatePersistentOverlays,
} from "./dom.js";
import { updateFrame, resetVisualization } from "./sync.js";
import { VIDEO_FPS } from "./constants.js";
import { findRadarFrameIndexForTime } from "./utils.js";
function handleKeyDown(event) {
// --- FIX APPLIED HERE ---
// We only want to block shortcuts if the user is actively typing in a text or number input.
// This allows shortcuts to work even when other elements, like the timeline slider, are focused.
const isTextInputFocused =
event.target.tagName === "INPUT" &&
(event.target.type === "text" || event.target.type === "number");
if (isTextInputFocused) {
return;
}
// --- END OF FIX ---
const key = event.key;
// We can add any new shortcut keys to this array.
const recognizedKeys = [
"ArrowRight",
"ArrowLeft",
"ArrowUp",
"ArrowDown",
" ",
"1",
"2",
"3",
"4",
"t",
"d",
"g",
"r",
"p",
"a",
"s",
"m",
"q",
"c",
];
// Keys that function globally, even without loaded data
const globalKeys = ["q", "m"];
if (!recognizedKeys.includes(key)) {
return;
}
// If no data is loaded, block keys unless they are global (like theme or menu)
if (!appState.vizData && !globalKeys.includes(key)) {
return;
}
event.preventDefault();
// --- Spacebar for Play/Pause ---
if (key === " ") {
playPauseBtn.click();
}
// --- Arrow keys for frame-by-frame seeking ---
if (key === "ArrowRight" || key === "ArrowLeft") {
if (appState.isPlaying) {
playPauseBtn.click();
}
let newFrame = appState.currentFrame;
if (key === "ArrowRight") {
newFrame = Math.min(
appState.vizData.radarFrames.length - 1,
appState.currentFrame + 1
);
} else if (key === "ArrowLeft") {
newFrame = Math.max(0, appState.currentFrame - 1);
}
if (newFrame !== appState.currentFrame) {
updateFrame(newFrame, true);
// Manually trigger redraws since the animation loop is paused
// This is the fix to ensure the radar plot updates on seek.
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
}
}
// --- Arrow keys for video frame-by-frame seeking ---
if (key === "ArrowUp" || key === "ArrowDown") {
if (appState.isPlaying) {
playPauseBtn.click(); // Pause playback to allow for precise stepping
}
const frameDuration = 1 / VIDEO_FPS;
let newVideoTime = videoPlayer.currentTime;
if (key === "ArrowUp") {
newVideoTime += frameDuration;
} else if (key === "ArrowDown") {
newVideoTime -= frameDuration;
}
// Clamp the new time to be within the video's bounds
videoPlayer.currentTime = Math.max(
0,
Math.min(newVideoTime, videoPlayer.duration)
);
// Find the corresponding radar frame for the new video time
const newFrameIndex = findRadarFrameIndexForTime(
videoPlayer.currentTime,
appState.vizData
);
// Update the application state, but don't force another video seek
updateFrame(newFrameIndex, false);
// Manually trigger redraws since the animation loop is paused
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
}
// --- Number keys for color modes ---
if (key >= "1" && key <= "4") {
const colorToggles = [
toggleSnrColor,
toggleClusterColor,
toggleInlierColor,
toggleStationaryColor,
];
const toggleIndex = parseInt(key) - 1;
if (colorToggles[toggleIndex]) {
colorToggles[toggleIndex].click();
}
}
if (key === "q") {
themeToggleBtn.click();
}
if (key === "t") {
toggleTracks.click();
}
if (key === "d") {
toggleVelocity.click();
}
if (key === "g") {
toggleCloseUp.click();
}
if (key === "r") {
resetVisualization();
}
if (key === "c") {
appState.isRawOnlyMode = !appState.isRawOnlyMode;
if (appState.p5_instance) {
appState.p5_instance.redraw();
}
}
if (key === "p") {
togglePredictedPos.click();
appState.p5_instance.redraw();
}
if (key === "s") {
toggleSnrColor.click();
}
if (key === "a") {
toggleDebugOverlay.click();
toggleDebug2Overlay.click();
updatePersistentOverlays(videoPlayer.currentTime);
// The 'a' key is a shortcut to toggle all debug overlays on/off.
// The `updateDebugOverlay` and `updatePersistentOverlays` functions,
// which are called by the toggle's 'change' event listener,
// already handle the logic for showing/hiding the other overlays.
}
if (key === "m") {
if (collapsibleMenu.classList.contains("-translate-x-full")) {
// If the menu is hidden (closed), trigger a click on the OPEN button.
toggleMenuBtn.click();
} else {
// If the menu is not hidden (it's open), trigger a click on the CLOSE button.
closeMenuBtn.click();
}
}
}
export function initKeyboardShortcuts() {
document.addEventListener("keydown", handleKeyDown);
}