From 943cccea3d710e3531b74ca5dc351289c150ebb9 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Fri, 5 Sep 2025 09:30:28 +0530 Subject: [PATCH] Minor BUG fixes and timeline slider overhaul with DEBUG overlay fix. --- steps/index.html | 2 +- steps/src/dom.js | 47 ++++++++++++++++++++++- steps/src/main.js | 96 ++++++++++++++++++++++++++++++++++------------ steps/src/sync.js | 3 +- steps/src/utils.js | 19 ++++++++- 5 files changed, 138 insertions(+), 29 deletions(-) diff --git a/steps/index.html b/steps/index.html index fa94dce..b26b973 100644 --- a/steps/index.html +++ b/steps/index.html @@ -221,7 +221,7 @@

- Load CAN log to see speed graph + Load JSON to see speed graph

diff --git a/steps/src/dom.js b/steps/src/dom.js index 377b97d..d395581 100644 --- a/steps/src/dom.js +++ b/steps/src/dom.js @@ -1,5 +1,7 @@ import { appState } from "./state.js"; -import { VIDEO_FPS } from "./constants.js"; // Import VIDEO_FPS for debug overlay calculations +import { formatUTCTime } from "./utils.js"; +// Also import VIDEO_FPS from constants +import { VIDEO_FPS } from "./constants.js"; // --- DOM Element References --- // @@ -54,6 +56,8 @@ export const modalProgressContainer = document.getElementById("modal-progress-co export const modalProgressBar = document.getElementById("modal-progress-bar"); export const modalProgressText = document.getElementById("modal-progress-text"); export const timelineTooltip = document.getElementById("timeline-tooltip"); +export const radarInfoOverlay = document.getElementById("radar-info-overlay"); +export const videoInfoOverlay = document.getElementById("video-info-overlay"); //----------------------UPDATE FRAME Function----------------------// // Updates the UI to reflect the current radar frame and synchronizes video playback. @@ -120,7 +124,7 @@ export function updateFrame(frame, forceVideoSeek) { if (!appState.isPlaying) { // MODIFIED: Use our new synchronized time variable - updateDebugOverlay(timeForUpdates); + updatePersistentOverlays(timeForUpdates); } // --- End of fix --- @@ -234,3 +238,42 @@ export function updateDebugOverlay(currentMediaTime) { debugOverlay.innerHTML = content.join("
"); // Update debug overlay content. } + + +export function updatePersistentOverlays(currentMediaTime) { + // If we don't have the necessary data, hide the overlays and exit. + if (!appState.vizData || !appState.videoStartDate) { + radarInfoOverlay.classList.add('hidden'); + videoInfoOverlay.classList.add('hidden'); + return; + } + + // Otherwise, make sure they are visible. + radarInfoOverlay.classList.remove('hidden'); + videoInfoOverlay.classList.remove('hidden'); + + // --- Update Radar Overlay --- + const currentRadarFrame = appState.vizData.radarFrames[appState.currentFrame]; + if (currentRadarFrame) { + const absRadarTime = new Date(appState.videoStartDate.getTime() + currentRadarFrame.timestampMs); + const targetRadarTimeMs = currentRadarFrame.timestampMs; + const offsetMs = parseFloat(offsetInput.value) || 0; + const driftMs = (currentMediaTime * 1000 + offsetMs) - targetRadarTimeMs; + const driftColor = Math.abs(driftMs) > 50 ? "#FF6347" : "#98FB98"; // Tomato red or Pale green + + radarInfoOverlay.innerHTML = ` + Frame: ${appState.currentFrame + 1} + Abs Time: ${formatUTCTime(absRadarTime)} + Drift: ${driftMs.toFixed(0)}ms + `; + } + + // --- Update Video Overlay --- + const absVideoTime = new Date(appState.videoStartDate.getTime() + (currentMediaTime * 1000)); + const videoFrame = Math.floor(currentMediaTime * VIDEO_FPS); + + videoInfoOverlay.innerHTML = ` + Frame: ${videoFrame} + Abs Time: ${formatUTCTime(absVideoTime)} + `; +} \ No newline at end of file diff --git a/steps/src/main.js b/steps/src/main.js index 06d9945..dd9547d 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -84,7 +84,9 @@ import { initializeTheme } from "./theme.js"; import { initDB, saveFileWithMetadata, loadFreshFileFromDB } from "./db.js"; -let seekDebounceTimer = null; // Add this line +let seekDebounceTimer = null; //timeline slider variables. +let lastScrollTime = 0; //timeline slider variables. +let scrollSpeed = 0; //timeline slider variables. // Sets up the video player with the given file URL. function setupVideoPlayer(fileURL) { @@ -191,7 +193,8 @@ clearCacheBtn.addEventListener("click", async () => { } }); -// In src/main.js, REPLACE the jsonFileInput event listener with this: +// In main.js, REPLACE your existing jsonFileInput event listener with this entire block: + jsonFileInput.addEventListener("change", (event) => { const file = event.target.files[0]; if (!file) return; @@ -213,24 +216,22 @@ jsonFileInput.addEventListener("change", (event) => { const { type, data, message, percent } = e.data; if (type === "progress") { - // Update the progress bar whenever the worker reports progress updateModalProgress(percent); } else if (type === "complete") { - // Worker is done! Process the data it sent back. updateModalProgress(100); const result = await parseVisualizationJson( - data, // Use the data object directly from the worker + data, appState.radarStartTimeMs, appState.videoStartDate ); if (result.error) { showModal(result.error); + worker.terminate(); // Terminate worker on error return; } - // --- START: New Cleanup Logic --- - // If p5.js instances already exist, remove them completely + if (appState.p5_instance) { appState.p5_instance.remove(); appState.p5_instance = null; @@ -238,10 +239,9 @@ jsonFileInput.addEventListener("change", (event) => { if (appState.speedGraphInstance) { appState.speedGraphInstance.remove(); appState.speedGraphInstance = null; - // Also reset the placeholder text speedGraphPlaceholder.classList.remove("hidden"); } - // --- END: New Cleanup Logic --- + appState.vizData = result.data; appState.globalMinSnr = result.minSnr; appState.globalMaxSnr = result.maxSnr; @@ -255,21 +255,23 @@ jsonFileInput.addEventListener("change", (event) => { if (!appState.p5_instance) { appState.p5_instance = new p5(radarSketch); } - if (appState.vizData && videoPlayer.duration) { - if (!appState.speedGraphInstance) { - appState.speedGraphInstance = new p5(speedGraphSketch); - } - appState.speedGraphInstance.setData( - appState.vizData, - videoPlayer.duration - ); + + // --- START: This is the new, corrected logic --- + // After processing the new JSON, check if a video is already loaded and ready. + // If it is, this is the trigger to create or update the speed graph. + if (appState.vizData && videoPlayer.duration > 0) { + speedGraphPlaceholder.classList.add("hidden"); + if (!appState.speedGraphInstance) { + appState.speedGraphInstance = new p5(speedGraphSketch); + } + appState.speedGraphInstance.setData(appState.vizData, videoPlayer.duration); } + // --- END: This is the new, corrected logic --- - // Close the modal and terminate the worker document.getElementById("modal-ok-btn").click(); worker.terminate(); + } else if (type === "error") { - // The worker ran into an error showModal(message); worker.terminate(); } @@ -390,8 +392,53 @@ timelineSlider.addEventListener("input", (event) => { updateDebugOverlay(videoPlayer.currentTime); }, 250); // Wait for 250ms of inactivity before firing }); -// In src/main.js, add this new block of event listeners +// --- Timeline Scroll-to-Seek Logic --- + +timelineSlider.addEventListener("wheel", (event) => { + if (!appState.vizData) return; + + // 1. Prevent the page from scrolling up and down + event.preventDefault(); + + // 2. Calculate scroll speed + const now = performance.now(); + const timeDelta = now - (lastScrollTime || now); // Handle first scroll + lastScrollTime = now; + // Calculate speed as "events per second", giving more weight to recent, fast scrolls + scrollSpeed = timeDelta > 0 ? 1000 / timeDelta : scrollSpeed; + + // 3. Map scroll speed to a dynamic seek multiplier + // This creates a nice acceleration curve. The '50' is a sensitivity value you can adjust. + const speedMultiplier = 1 + Math.floor(scrollSpeed / 4); + const baseSeekAmount = 1; // Base frames to move on a slow scroll + let seekAmount = Math.max(baseSeekAmount, speedMultiplier); + + // 4. Calculate the new frame index + const direction = Math.sign(event.deltaY); // +1 for down/right, -1 for up/left + const currentFrame = parseInt(timelineSlider.value, 10); + let newFrame = currentFrame + seekAmount * direction; + + // Clamp the new frame to the valid range + const totalFrames = appState.vizData.radarFrames.length - 1; + newFrame = Math.max(0, Math.min(newFrame, totalFrames)); + + // 5. Update the UI + if (appState.isPlaying) { + playPauseBtn.click(); // Pause if playing + } + updateFrame(newFrame, true); + + // 6. Reuse the debouncer for a final, precise sync after scrolling stops + clearTimeout(seekDebounceTimer); + seekDebounceTimer = setTimeout(() => { + console.log("Scrolling stopped. Performing final, debounced resync."); + updateFrame(newFrame, true); + updatePersistentOverlays(videoPlayer.currentTime); + }, 300); // Wait 300ms after the last scroll event +}); + +// In src/main.js, add this new block of event listeners // --- Timeline Scrub-to-Seek Preview Logic --- timelineSlider.addEventListener("mouseover", () => { @@ -412,10 +459,11 @@ timelineSlider.addEventListener("mousemove", (event) => { const hoverFraction = (event.clientX - rect.left) / rect.width; // 2. Calculate the corresponding frame index - const sliderMax = parseInt(timelineSlider.max, 10) || (appState.vizData.radarFrames.length - 1); -let frameIndex = Math.round(hoverFraction * sliderMax); -// The value is already clamped by this calculation, but an extra check is safe -frameIndex = Math.max(0, Math.min(frameIndex, sliderMax)); + const sliderMax = + parseInt(timelineSlider.max, 10) || appState.vizData.radarFrames.length - 1; + let frameIndex = Math.round(hoverFraction * sliderMax); + // The value is already clamped by this calculation, but an extra check is safe + frameIndex = Math.max(0, Math.min(frameIndex, sliderMax)); const frameData = appState.vizData.radarFrames[frameIndex]; if (!frameData) return; diff --git a/steps/src/sync.js b/steps/src/sync.js index 4c9a568..298516c 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -6,6 +6,7 @@ import { stopBtn, updateFrame, updateDebugOverlay, + updatePersistentOverlays, } from "./dom.js"; import { findRadarFrameIndexForTime } from "./utils.js"; @@ -67,7 +68,7 @@ export function animationLoop() { } // Update debug overlay information - updateDebugOverlay(currentMediaTime); + updatePersistentOverlays(currentMediaTime); // Redraw the speed graph if an instance exists if (appState.speedGraphInstance) appState.speedGraphInstance.redraw(); diff --git a/steps/src/utils.js b/steps/src/utils.js index e59e2c3..ca8d497 100644 --- a/steps/src/utils.js +++ b/steps/src/utils.js @@ -145,4 +145,21 @@ export function formatTime(milliseconds) { const ms = Math.round(milliseconds % 1000); return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(ms).padStart(3, '0')}`; -} \ No newline at end of file +} + +/** + * Formats a Date object into a HH:MM:SS.ms UTC string. + * @param {Date} date The date object to format. + * @returns {string} The formatted time string. + */ +export function formatUTCTime(date) { + if (!date || isNaN(date.getTime())) { + return "00:00:00.000"; + } + const hours = String(date.getUTCHours()).padStart(2, '0'); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + const milliseconds = String(date.getUTCMilliseconds()).padStart(3, '0'); + return `${hours}:${minutes}:${seconds}.${milliseconds}`; +} +