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.
 
 
 

219 lines
6.8 KiB

import { appState } from "./state.js";
import { formatTime } from "./utils.js";
import { showModal } from "./modal.js";
import { pausePlayback } from "./sync.js";
import {
videoPlayer,
timelineSlider,
speedSlider,
speedDisplay,
toggleSnrColor,
toggleClusterColor,
toggleInlierColor,
toggleStationaryColor,
toggleVelocity,
toggleEgoSpeed,
toggleFrameNorm,
toggleTracks,
toggleDebugOverlay,
toggleDebug2Overlay,
toggleCloseUp,
snrMinInput,
snrMaxInput,
applySnrBtn,
timelineTooltip,
playPauseBtn,
updatePersistentOverlays,
updateDebugOverlay,
collapsibleMenu,
toggleMenuBtn,
closeMenuBtn,
menuScrim,
fullscreenBtn,
toggleConfirmedOnly,
shortcutsBtn,
shortcutsModal,
shortcutsModalCloseBtn,
userManualBtn,
guideModal,
guideModalCloseBtn,
} from "./dom.js";
function toggleMenu(show) {
if (show) {
collapsibleMenu.classList.remove("-translate-x-full");
menuScrim.classList.remove("hidden");
} else {
collapsibleMenu.classList.add("-translate-x-full");
menuScrim.classList.add("hidden");
}
}
function toggleShortcutsModal(show) {
if (show) {
shortcutsModal.classList.remove("hidden");
} else {
shortcutsModal.classList.add("hidden");
}
}
function toggleGuideModal(show) {
if (show) {
guideModal.classList.remove("hidden");
} else {
guideModal.classList.add("hidden");
}
}
function handleColorToggles(e) {
const colorToggles = [
toggleSnrColor,
toggleClusterColor,
toggleInlierColor,
toggleStationaryColor,
];
if (e.target.checked) {
colorToggles.forEach((o) => {
if (o !== e.target) o.checked = false;
});
}
if (appState.p5_instance) appState.p5_instance.redraw();
updatePersistentOverlays(videoPlayer.currentTime);
}
export function initUIEventListeners() {
// --- Shortcuts Modal ---
shortcutsBtn.addEventListener("click", (e) => {
e.preventDefault();
toggleShortcutsModal(true);
});
shortcutsModalCloseBtn.addEventListener("click", () => toggleShortcutsModal(false));
shortcutsModal.addEventListener("click", (e) => {
// Close if clicking the background overlay (self), but not children
if (e.target === shortcutsModal) {
toggleShortcutsModal(false);
}
});
// --- Guide Modal ---
userManualBtn.addEventListener("click", (e) => {
e.preventDefault();
toggleGuideModal(true);
});
guideModalCloseBtn.addEventListener("click", () => toggleGuideModal(false));
guideModal.addEventListener("click", (e) => {
if (e.target === guideModal) {
toggleGuideModal(false);
}
});
// Global Key Listener for 'k' and 'ESC'
document.addEventListener("keydown", (e) => {
if (e.key.toLowerCase() === "k") {
// Toggle visibility
const isHidden = shortcutsModal.classList.contains("hidden");
toggleShortcutsModal(isHidden);
}
// Prioritize closing the guide modal if open, then shortcuts modal
if (e.key === "Escape") {
if (!guideModal.classList.contains("hidden")) {
toggleGuideModal(false);
} else if (!shortcutsModal.classList.contains("hidden")) {
toggleShortcutsModal(false);
}
}
});
// --- Menu and Fullscreen ---
toggleMenuBtn.addEventListener("click", () => toggleMenu(true));
closeMenuBtn.addEventListener("click", () => toggleMenu(false));
menuScrim.addEventListener("click", () => toggleMenu(false));
fullscreenBtn.addEventListener("click", () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
});
// --- Timeline Tooltip ---
timelineSlider.addEventListener("mouseover", () => {
if (appState.vizData) timelineTooltip.classList.remove("hidden");
});
timelineSlider.addEventListener("mouseout", () => {
timelineTooltip.classList.add("hidden");
});
timelineSlider.addEventListener("mousemove", (event) => {
if (!appState.vizData) return;
const rect = timelineSlider.getBoundingClientRect();
const hoverFraction = (event.clientX - rect.left) / rect.width;
const sliderMax = parseInt(timelineSlider.max, 10) || appState.vizData.radarFrames.length - 1;
let frameIndex = Math.max(0, Math.min(Math.round(hoverFraction * sliderMax), sliderMax));
const frameData = appState.vizData.radarFrames[frameIndex];
if (!frameData) return;
const formattedTime = formatTime(frameData.relativeTimeSec * 1000);
timelineTooltip.innerHTML = `Frame: ${frameIndex + 1}<br>Time: ${formattedTime}`;
const tooltipX = event.clientX - rect.left;
timelineTooltip.style.left = `${tooltipX}px`;
});
// --- Speed Slider ---
speedSlider.addEventListener("input", (event) => {
const speed = parseFloat(event.target.value);
videoPlayer.playbackRate = speed;
speedDisplay.textContent = `${speed.toFixed(1)}x`;
});
// --- SNR Controls ---
applySnrBtn.addEventListener("click", () => {
const newMin = parseFloat(snrMinInput.value), newMax = parseFloat(snrMaxInput.value);
if (isNaN(newMin) || isNaN(newMax) || newMin >= newMax) {
showModal("Invalid SNR range.");
return;
}
appState.globalMinSnr = newMin;
appState.globalMaxSnr = newMax;
toggleFrameNorm.checked = false;
if (appState.p5_instance) {
appState.p5_instance.drawSnrLegendToBuffer(appState.globalMinSnr, appState.globalMaxSnr);
appState.p5_instance.redraw();
}
});
// --- Feature Toggles ---
[toggleSnrColor, toggleClusterColor, toggleInlierColor, toggleStationaryColor].forEach((t) => {
t.addEventListener("change", handleColorToggles);
});
[toggleVelocity, toggleEgoSpeed, toggleFrameNorm, toggleTracks, toggleDebugOverlay, toggleDebug2Overlay].forEach((t) => {
t.addEventListener("change", () => {
if (appState.p5_instance) appState.p5_instance.redraw();
if (t === toggleDebugOverlay || t === toggleDebug2Overlay) {
updateDebugOverlay(videoPlayer.currentTime);
updatePersistentOverlays(videoPlayer.currentTime);
}
});
});
toggleCloseUp.addEventListener("change", () => {
appState.isCloseUpMode = toggleCloseUp.checked;
if (appState.isCloseUpMode && appState.isPlaying) {
// If entering close-up mode while playing, automatically pause.
pausePlayback();
appState.isPlaying = false;
playPauseBtn.textContent = "Play";
}
if (appState.p5_instance) { // Handle p5 loop state
if (appState.isCloseUpMode) {
appState.p5_instance.loop(); // Start looping for mouse interaction.
} else {
appState.p5_instance.noLoop(); // Stop looping when exiting.
appState.p5_instance.redraw(); // Redraw one last time.
}
}
});
toggleConfirmedOnly.addEventListener("change", () => {
if (appState.p5_instance) appState.p5_instance.redraw();
});
}