diff --git a/steps/src/dataExplorer.js b/steps/src/dataExplorer.js index feb40dd..6ff4054 100644 --- a/steps/src/dataExplorer.js +++ b/steps/src/dataExplorer.js @@ -109,21 +109,6 @@ function hideExplorer() { panel.classList.add('hidden'); } -/* function switchTab(targetTab) { - Object.values(tabs).forEach(tab => { - tab.panel.classList.add('hidden'); - tab.btn.classList.remove('border-blue-500', 'text-gray-900', 'dark:text-white'); - tab.btn.classList.add('text-gray-500', 'dark:text-gray-400', 'border-transparent'); - }); - - tabs[targetTab].panel.classList.remove('hidden'); - tabs[targetTab].btn.classList.add('border-blue-500', 'text-gray-900', 'dark:text-white'); - tabs[targetTab].btn.classList.remove('text-gray-500', 'dark:text-gray-400'); - - footer.classList.toggle('hidden', targetTab !== 'grid'); -} */ -// In src/dataExplorer.js - function switchTab(targetTab) { Object.values(tabs).forEach(tab => { tab.panel.classList.add('hidden'); @@ -262,13 +247,6 @@ function displayTracksInGrid(trackData) { // --- START: New Robust Update Logic --- let throttleTimer = null; let debounceTimer = null; - -/** - * A custom throttled and debounced function for updating the explorer. - * - It throttles calls to prevent updates more than once every 400ms. - * - It debounces calls to ensure a final, guaranteed update happens 500ms - * after the last call, catching the "end" of a seeking action. - */ export function throttledUpdateExplorer() { // Clear any pending final update, as a new call has come in. clearTimeout(debounceTimer); diff --git a/steps/src/db.js b/steps/src/db.js index a94e0e5..76bfa20 100644 --- a/steps/src/db.js +++ b/steps/src/db.js @@ -31,11 +31,7 @@ export function initDB(callback) { }; } -/** - * Saves a file and its metadata to IndexedDB for versioning and integrity checks. - * @param {string} key The key to store the file under (e.g., 'json', 'video'). - * @param {File} file The file object to be cached. - */ + // Saves a file (Blob) along with its metadata into the IndexedDB. export function saveFileWithMetadata(key, file) { if (!db) return; @@ -68,12 +64,7 @@ export function saveFileWithMetadata(key, file) { }; } -/** - * Loads a file from IndexedDB only if its filename and size match expected values. - * @param {string} key The key of the file to load. - * @param {string} expectedFilename The filename we expect to find. - * @returns {Promise} A Promise that resolves with the Blob if it's fresh, otherwise null. - */ + // Loads a file from IndexedDB, performing checks for filename and size to ensure data integrity. export function loadFreshFileFromDB(key, expectedFilename) { return new Promise((resolve) => { diff --git a/steps/src/drawUtils.js b/steps/src/drawUtils.js index 3ed27d8..d9361c7 100644 --- a/steps/src/drawUtils.js +++ b/steps/src/drawUtils.js @@ -112,11 +112,7 @@ export function drawStaticRegionsToBuffer(p, b, plotScales) { b.pop(); } -/** - * Draws the grid and axes for the radar plot. - * @param {p5} p - The p5 instance. - * @param {object} plotScales - The calculated scales for plotting. - */ + export function drawAxes(p, plotScales) { p.push(); // Determine axis and text colors based on the current theme (dark/light mode). @@ -196,12 +192,7 @@ export function drawAxes(p, plotScales) { p.pop(); } -/** - * Draws the point cloud on the radar canvas. - * @param {p5} p - The p5 instance. - * @param {Array} points - The array of point cloud data. - * @param {object} plotScales - The calculated scales for plotting. - */ + export function drawPointCloud(p, points, plotScales) { // Set stroke weight for points. p.strokeWeight(4); @@ -287,11 +278,6 @@ export function drawPointCloud(p, points, plotScales) { } } -/** - * Draws the historical trajectories of tracked objects. - * @param {p5} p - The p5 instance. - * @param {object} plotScales - The calculated scales for plotting. - */ export function drawTrajectories(p, plotScales) { const localTtcColors = ttcColors(p); @@ -418,13 +404,6 @@ export function drawTrajectories(p, plotScales) { } } -/** - * Draws markers for the current position of tracked objects. - * @param {p5} p - The p5 instance. - * @param {object} plotScales - The calculated scales for plotting. - */ -// In src/drawUtils.js - export function drawTrackMarkers(p, plotScales) { const showDetails = toggleVelocity.checked; const useStationary = toggleStationaryColor.checked; @@ -516,11 +495,7 @@ export function drawTrackMarkers(p, plotScales) { } } -/** - * Handles the display of a comprehensive info tooltip for all elements under the mouse. - * @param {p5} p - The p5 instance. - * @param {object} plotScales - The calculated scales for plotting. - */ + export function handleCloseUpDisplay(p, plotScales, mouseX, mouseY) { // --- Step 1: Gather Hovered Items --- const frameData = appState.vizData.radarFrames[appState.currentFrame]; @@ -836,12 +811,7 @@ export function drawCovarianceEllipse( p.pop(); } -// In src/drawUtils.js -/** - * Draws a simple representation of the ego vehicle at the origin (0,0). - * @param {p5.Graphics} b - The p5.Graphics buffer to draw on. - */ export function drawEgoVehicle(p, plotScales) { const isDark = document.documentElement.classList.contains("dark"); const carColor = isDark ? p.color(150, 150, 220) : p.color(151, 151, 220); @@ -861,17 +831,6 @@ export function drawEgoVehicle(p, plotScales) { p.pop(); } -//OLD_Solid Fill Logic - -/** - * Draws the defined regions of interest (ROI) based on dynamic data from the current frame. - * @param {p5} p - The p5 instance to draw on. - * @param {object} frameData - The data for the current radar frame. - * @param {object} plotScales - The calculated scales for plotting. - */ -/** - - */ export function drawRegionsOfInterest(p, frameData, plotScales) { // --- THIS CHECK IS ESSENTIAL AND MUST NOT BE REMOVED --- // It gracefully handles frames that do not have the barrier data. @@ -920,15 +879,7 @@ export function drawRegionsOfInterest(p, frameData, plotScales) { p.pop(); } -//OLD_Solid Fill Logic -/** - * Draws the cluster centroids on the radar canvas as an asterisk. - * Handles cases where a single cluster is an object instead of an array. - * @param {p5} p - The p5 instance. - * @param {Array|object} clustersInput - The cluster data for the current frame. - * @param {object} plotScales - The calculated scales for plotting. - */ export function drawClusterCentroids(p, clustersInput, plotScales) { if (!clustersInput) { return; // Do nothing if there's no cluster data diff --git a/steps/src/fileParsers.js b/steps/src/fileParsers.js index 789b96f..9f00e8c 100644 --- a/steps/src/fileParsers.js +++ b/steps/src/fileParsers.js @@ -1,14 +1,3 @@ -/** - * Parses a JSON file stream using Oboe.js to handle very large files. - * @param {string} fileURL - A temporary URL created from the file object. - * @param {function} onProgress - A callback to update the UI on progress. - * @param {function} onComplete - A callback to run when parsing is complete. - * @param {function} onError - A callback to run if an error occurs. - */ -// This function can be deleted if it exists: parseJsonStream -// This function can be deleted if it exists: parseJsonWithOboe - -// Add this simplified streaming function export function parseJsonWithOboe(fileURL, onComplete, onError, onProgress) { const vizData = { radarFrames: [], diff --git a/steps/src/main.js b/steps/src/main.js index ad048ec..a36d9fc 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -941,7 +941,6 @@ document.addEventListener("keydown", (event) => { "m", "q", "c", - //"i", ]; if (!appState.vizData || !recognizedKeys.includes(key)) { @@ -1008,14 +1007,6 @@ document.addEventListener("keydown", (event) => { appState.p5_instance.redraw(); } } - /* if (key === "i") { - const panel = document.getElementById("data-explorer-panel"); - if (panel.classList.contains("hidden")) { - showExplorer(); - } else { - hideExplorer(); - } - } */ if (key === "p") { togglePredictedPos.click(); appState.p5_instance.redraw(); @@ -1046,26 +1037,7 @@ document.addEventListener("keydown", (event) => { } }); -/* canvasContainer.addEventListener('click', () => { - if (!appState.vizData) return; - // For this example, let's just send the pointCloud of the current frame to the grid. - // A more advanced version could detect if you clicked on a specific track. - const currentFrameData = appState.vizData.radarFrames[appState.currentFrame]; - - if (currentFrameData && currentFrameData.pointCloud) { - displayInGrid(currentFrameData.pointCloud, `Frame ${appState.currentFrame} - Point Cloud`); - } -}); */ -/* explorerBtn.addEventListener('click', () => { - const panel = document.getElementById("data-explorer-panel"); - if (panel.classList.contains("hidden")) { - showExplorer(); - } else { - hideExplorer(); - } -}); - */ function calculateAndSetOffset() { const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename); const videoTimestampInfo = extractTimestampInfo(appState.videoFilename); diff --git a/steps/src/main_old.js b/steps/src/main_old.js deleted file mode 100644 index 259dec8..0000000 --- a/steps/src/main_old.js +++ /dev/null @@ -1,617 +0,0 @@ -// =========================================================================================================== -// REFACTOR PLAN: This monolithic script will be broken down into -// the following modules in the '/src' directory: -// -// - constants.js: Shared constants (VIDEO_FPS, RADAR_X_MAX) -// - utils.js: Pure helper functions (findRadarFrameIndexForTime) -// - state.js: Central application state management -// - dom.js: DOM element references and UI updaters -// - modal.js: Modal dialog logic -// - theme.js: Dark/Light mode theme switcher -// - db.js: IndexedDB caching logic -// - fileParsers.js: JSON and CAN log parsing logic -// - p5/radarSketch.js: The main p5.js radar visualization -// - p5/speedGraph.js: The p5.js speed graph visualization -// - sync.js: Playback and synchronization loop -// - main.js: The main application entry point that wires everything -// =========================================================================================================== - -// import animation loop from './src/sync.js'; - -import { animationLoop } from "./sync.js"; -// import radar sketch from './src/p5/radarSketch.js'; -import { radarSketch } from "./p5/radarSketch.js"; -// import speed graph sketch from './src/p5/speedGraphSketch.js'; -import { speedGraphSketch } from "./p5/speedGraphSketch.js"; -// import JSON parser, can log procesor from './src/fileParsers.js'; -import { processCanLog, parseVisualizationJson } from "./fileParsers.js"; -// import constants from './constants.js'; -import { - MAX_TRAJECTORY_LENGTH, - VIDEO_FPS, - RADAR_X_MIN, - RADAR_X_MAX, - RADAR_Y_MIN, - RADAR_Y_MAX, -} from "./constants.js"; -// import utils and helpers from './src/utils.js'; -import { - findRadarFrameIndexForTime, - findLastCanIndexBefore, - extractTimestampInfo, - parseTimestamp, - throttle, -} from "./utils.js"; -// import state machine from './src/state.js'; -import { appState } from "./state.js"; -// import DOM elements and UI updaters from './src/dom.js'; -import { - //---DOM Elements---// - canvasContainer, - canvasPlaceholder, - videoPlayer, - videoPlaceholder, - loadJsonBtn, - loadVideoBtn, - loadCanBtn, - jsonFileInput, - videoFileInput, - canFileInput, - playPauseBtn, - stopBtn, - timelineSlider, - frameCounter, - offsetInput, - speedSlider, - speedDisplay, - featureToggles, - toggleSnrColor, - toggleClusterColor, - toggleInlierColor, - toggleStationaryColor, - toggleVelocity, - toggleTracks, - toggleEgoSpeed, - toggleFrameNorm, - toggleDebugOverlay, - toggleDebug2Overlay, - egoSpeedDisplay, - canSpeedDisplay, - debugOverlay, - snrMinInput, - snrMaxInput, - applySnrBtn, - autoOffsetIndicator, - clearCacheBtn, - speedGraphContainer, - speedGraphPlaceholder, - toggleCloseUp, - //---UI Updaters---// - updateFrame, - resetVisualization, - updateCanDisplay, - updateDebugOverlay, -} from "./dom.js"; -// Import modal dialog logic from './src/modal.js'. -import { showModal } from "./modal.js"; -// Import theme initialization from './src/theme.js'. -import { initializeTheme } from "./theme.js"; -// Import caching logic from './src/db.js'. -import { initDB, saveFileToDB, loadFileFromDB } from "./db.js"; - -// Sets up the video player with the given file URL. -function setupVideoPlayer(fileURL) { - videoPlayer.src = fileURL; - videoPlayer.classList.remove("hidden"); - videoPlaceholder.classList.add("hidden"); - videoPlayer.playbackRate = parseFloat(speedSlider.value); -} -// Event listener for loading JSON file. -loadJsonBtn.addEventListener("click", () => jsonFileInput.click()); -loadVideoBtn.addEventListener("click", () => videoFileInput.click()); -loadCanBtn.addEventListener("click", () => canFileInput.click()); -clearCacheBtn.addEventListener("click", async () => { - const confirmed = await showModal("Clear all cached data and reload?", true); - if (confirmed) { - indexedDB.deleteDatabase("visualizerDB"); - localStorage.clear(); - window.location.reload(); - } -}); -// Event listener for JSON file input change. -jsonFileInput.addEventListener("change", (event) => { - const file = event.target.files[0]; - if (!file) return; - appState.jsonFilename = file.name; - localStorage.setItem("jsonFilename", appState.jsonFilename); - calculateAndSetOffset(); // This function now correctly sets appState variables - - // Show a modal or loading indicator to the user - showModal("Parsing large JSON file, please wait..."); - - // Get a readable stream from the file - const stream = file.stream(); - - // Call the new streaming parser - parseJsonStream(stream, (parsedData) => { - // This callback runs once the entire file has been parsed - - // Once parsing is complete, continue with the rest of the setup - const result = parseVisualizationJson( - parsedData, // We now pass the parsed object, not a string - appState.radarStartTimeMs, - appState.videoStartDate - ); - - if (result.error) { - showModal(result.error); - return; - } - appState.vizData = result.data; - appState.globalMinSnr = result.minSnr; - appState.globalMaxSnr = result.maxSnr; - - // Update UI - snrMinInput.value = appState.globalMinSnr.toFixed(1); - snrMaxInput.value = appState.globalMaxSnr.toFixed(1); - resetVisualization(); - canvasPlaceholder.style.display = "none"; - featureToggles.classList.remove("hidden"); - - if (!appState.p5_instance) { - appState.p5_instance = new p5(radarSketch); - } - // Hide the loading modal - // Note: You might need to adjust your hideModal logic if it's not already globally accessible - document.getElementById("modal-ok-btn").click(); - }); - - -const reader = new FileReader(); -reader.onload = (e) => { - const jsonString = e.target.result; - saveFileToDB("json", jsonString); - - // 1. Give the raw ingredients to our new JSON "chef" - const result = parseVisualizationJson( - jsonString, - appState.radarStartTimeMs, - appState.videoStartDate - ); - - // 2. Check the result - if (result.error) { - showModal(result.error); - return; - } - - // 3. Update the application's central state with the prepared data - appState.vizData = result.data; - appState.globalMinSnr = result.minSnr; - appState.globalMaxSnr = result.maxSnr; - - // 4. Now, the "waiter" updates the UI - snrMinInput.value = appState.globalMinSnr.toFixed(1); - snrMaxInput.value = appState.globalMaxSnr.toFixed(1); - resetVisualization(); // This UI function is in dom.js - canvasPlaceholder.style.display = "none"; - featureToggles.classList.remove("hidden"); - - if (!appState.p5_instance) { - appState.p5_instance = new p5(radarSketch); - } - - if (appState.speedGraphInstance) { - appState.speedGraphInstance.setData( - appState.canData, - appState.vizData, - videoPlayer.duration - ); - } else { - // Redraw p5 instance with new data - appState.p5_instance.drawSnrLegendToBuffer( - appState.globalMinSnr, - appState.globalMaxSnr - ); - appState.p5_instance.redraw(); - } -}; -reader.readAsText(file); - -}); - -// Event listener for video file input change. -videoFileInput.addEventListener("change", (event) => { - const file = event.target.files[0]; - if (!file) return; - appState.videoFilename = file.name; - localStorage.setItem("videoFilename", appState.videoFilename); - saveFileToDB("video", file); - - // This is the key moment: we now have a video start date. - calculateAndSetOffset(); - - // Now, check if we have pending data that needs this date. - if (appState.rawCanLogText) { - const result = processCanLog( - appState.rawCanLogText, - appState.videoStartDate - ); - if (!result.error) { - appState.canData = result.data; - appState.rawCanLogText = null; - } - } - - // NEW: Re-process vizData if it was loaded before the video. - if (appState.vizData) { - console.log("DEBUG: Video loaded after JSON. Re-calculating timestamps."); - appState.vizData.radarFrames.forEach((frame) => { - frame.timestampMs = - appState.radarStartTimeMs + - frame.timestamp - - appState.videoStartDate.getTime(); - }); - resetVisualization(); // Reset UI to reflect new timestamps - } - - const fileURL = URL.createObjectURL(file); - setupVideoPlayer(fileURL); - - // When the video is ready, update the speed graph - videoPlayer.onloadedmetadata = () => { - if (appState.speedGraphInstance) { - appState.speedGraphInstance.setData( - appState.canData, - appState.vizData, - videoPlayer.duration - ); - } - }; -}); -// Event listener for CAN file input change. -canFileInput.addEventListener("change", (event) => { - const file = event.target.files[0]; - if (!file) return; - appState.canLogFilename = file.name; - localStorage.setItem("canLogFilename", appState.canLogFilename); - - const reader = new FileReader(); - reader.onload = (e) => { - const logContent = e.target.result; - saveFileToDB("canLogText", logContent); - - // 1. Give the raw ingredients to the chef (our parser) - const result = processCanLog(logContent, appState.videoStartDate); - - // 2. Check what the chef gave back - if (result.error) { - // If there was an error, show it and save the raw text for later. - showModal(result.error); - appState.rawCanLogText = result.rawCanLogText; - return; - } - - // 3. If successful, update the application's central state - appState.canData = result.data; - appState.rawCanLogText = null; - - // 4. Now, the waiter updates the UI based on the new state - if (appState.canData.length > 0 || appState.vizData) { - speedGraphPlaceholder.classList.add("hidden"); - if (!appState.speedGraphInstance) { - // We need to pass the speedGraphSketch function definition here - appState.speedGraphInstance = new p5(speedGraphSketch); - } - if (videoPlayer.duration) { - appState.speedGraphInstance.setData( - appState.canData, - appState.vizData, - videoPlayer.duration - ); - } - } else { - showModal(`No CAN messages with ID 0x30F found.`); - } - }; - reader.readAsText(file); -}); -// Event listener for offset input change. -offsetInput.addEventListener("input", () => { - autoOffsetIndicator.classList.add("hidden"); - localStorage.setItem("visualizerOffset", offsetInput.value); -}); -// Event listener for apply SNR button click. -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(); - } -}); -// Event listener for play/pause button click. -playPauseBtn.addEventListener("click", () => { - if (!appState.vizData && !videoPlayer.src) return; - appState.isPlaying = !appState.isPlaying; - playPauseBtn.textContent = appState.isPlaying ? "Pause" : "Play"; - if (appState.isPlaying) { - if (videoPlayer.src && videoPlayer.readyState > 1) { - appState.masterClockStart = performance.now(); - appState.mediaTimeStart = videoPlayer.currentTime; - appState.lastSyncTime = appState.masterClockStart; - videoPlayer.play(); - } - requestAnimationFrame(animationLoop); - } else { - if (videoPlayer.src) videoPlayer.pause(); - } -}); -// Event listener for stop button click. -stopBtn.addEventListener("click", () => { - videoPlayer.pause(); - appState.isPlaying = false; - playPauseBtn.textContent = "Play"; - if (appState.vizData) { - updateFrame(0, true); - } else if (videoPlayer.src) { - videoPlayer.currentTime = 0; - } - if (appState.speedGraphInstance) appState.speedGraphInstance.redraw(); -}); -// Event listener for timeline slider input. -timelineSlider.addEventListener( - "input", - throttle((event) => { - if (!appState.vizData) return; - if (appState.isPlaying) { - videoPlayer.pause(); - appState.isPlaying = false; - playPauseBtn.textContent = "Play"; - } - const frame = parseInt(event.target.value, 10); - updateFrame(frame, true); - appState.mediaTimeStart = videoPlayer.currentTime; - appState.masterClockStart = performance.now(); - }, 16) -); // Throttle delay for smoother updates. -// Currently set at 16 ms to achieve smooth 60fps. -// Event listener for speed slider input. -speedSlider.addEventListener("input", (event) => { - const speed = parseFloat(event.target.value); - videoPlayer.playbackRate = speed; - speedDisplay.textContent = `${speed.toFixed(1)}x`; -}); - -// ADD THE NEW TOGGLE TO THE ARRAY -// Array of color toggles. -const colorToggles = [ - toggleSnrColor, - toggleClusterColor, - toggleInlierColor, - toggleStationaryColor, -]; -colorToggles.forEach((t) => { - t.addEventListener("change", (e) => { - if (e.target.checked) { - colorToggles.forEach((o) => { - if (o !== e.target) o.checked = false; - }); - } - if (appState.p5_instance) appState.p5_instance.redraw(); - }); -}); -// Event listeners for various feature toggles. -[ - toggleVelocity, - toggleEgoSpeed, - toggleFrameNorm, - toggleTracks, - toggleDebugOverlay, - toggleDebug2Overlay, -].forEach((t) => { - t.addEventListener("change", () => { - if (appState.p5_instance) { - if (t === toggleFrameNorm && !toggleFrameNorm.checked) - appState.p5_instance.drawSnrLegendToBuffer( - appState.globalMinSnr, - appState.globalMaxSnr - ); - appState.p5_instance.redraw(); - } - if (t === toggleDebugOverlay || t === toggleDebug2Overlay) { - updateDebugOverlay(videoPlayer.currentTime); - } - }); -}); -// Event listener for close-up toggle. -toggleCloseUp.addEventListener("change", () => { - appState.isCloseUpMode = toggleCloseUp.checked; - if (appState.p5_instance) { - if (appState.isCloseUpMode) { - if (appState.isPlaying) { - playPauseBtn.click(); - } - appState.p5_instance.loop(); - } else { - appState.p5_instance.noLoop(); - appState.p5_instance.redraw(); - } - } -}); -// Event listener for video ended event. -videoPlayer.addEventListener("ended", () => { - appState.isPlaying = false; - playPauseBtn.textContent = "Play"; -}); -// Event listener for keyboard arrow key presses to navigate frames. -document.addEventListener("keydown", (event) => { - if ( - !appState.vizData || - ["ArrowRight", "ArrowLeft"].indexOf(event.key) === -1 - ) - return; - event.preventDefault(); - if (appState.isPlaying) { - appState.isPlaying = false; - playPauseBtn.textContent = "Play"; - videoPlayer.pause(); - } - let newFrame = appState.currentFrame; - if (event.key === "ArrowRight") - newFrame = Math.min( - appState.vizData.radarFrames.length - 1, - appState.currentFrame + 1 - ); - else if (event.key === "ArrowLeft") - newFrame = Math.max(0, appState.currentFrame - 1); - if (newFrame !== appState.currentFrame) { - updateFrame(newFrame, true); - appState.mediaTimeStart = videoPlayer.currentTime; - appState.masterClockStart = performance.now(); - } -}); -// Calculates and sets the time offset between JSON and video timestamps. -function calculateAndSetOffset() { - const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename); - const videoTimestampInfo = extractTimestampInfo(appState.videoFilename); - if (videoTimestampInfo) { - appState.videoStartDate = parseTimestamp( - videoTimestampInfo.timestampStr, - videoTimestampInfo.format - ); - if (appState.videoStartDate) - console.log( - `Video start date set to: ${appState.videoStartDate.toISOString()}` - ); - } - if (jsonTimestampInfo) { - const jsonDate = parseTimestamp( - jsonTimestampInfo.timestampStr, - jsonTimestampInfo.format - ); - if (jsonDate) { - appState.radarStartTimeMs = jsonDate.getTime(); - console.log(`Radar start date set to: ${jsonDate.toISOString()}`); - if (appState.videoStartDate) { - const offset = - appState.radarStartTimeMs - appState.videoStartDate.getTime(); - offsetInput.value = offset; - localStorage.setItem("visualizerOffset", offset); - autoOffsetIndicator.classList.remove("hidden"); - console.log(`Auto-calculated offset: ${offset} ms`); - } - } - } -} - -// Application Initialization: Event listener for DOMContentLoaded. -document.addEventListener("DOMContentLoaded", () => { - initializeTheme(); - console.log("DEBUG: DOMContentLoaded fired. Starting session load."); // Log for debugging. - - initDB(() => { - console.log("DEBUG: Database initialized."); - const savedOffset = localStorage.getItem("visualizerOffset"); - if (savedOffset !== null) { - offsetInput.value = savedOffset; - } - appState.videoFilename = localStorage.getItem("videoFilename"); - appState.jsonFilename = localStorage.getItem("jsonFilename"); - appState.canLogFilename = localStorage.getItem("canLogFilename"); - - // This is important: it sets videoStartDate if a video filename is cached - calculateAndSetOffset(); // Calculate offset based on cached filenames. - - // Promises to load files from IndexedDB. - const videoPromise = new Promise((resolve) => - loadFileFromDB("video", resolve) - ); - const jsonPromise = new Promise((resolve) => - loadFileFromDB("json", resolve) - ); - const canLogPromise = new Promise((resolve) => - loadFileFromDB("canLogText", resolve) - ); - // Once all files are loaded from DB, process them. - Promise.all([videoPromise, jsonPromise, canLogPromise]) - .then(([videoBlob, jsonString, canLogText]) => { - console.log("DEBUG: All data fetched from IndexedDB."); - - const processAllData = () => { - console.log("DEBUG: Processing all loaded data."); - - // 1. Process JSON (only if video start date is available). - if (jsonString && appState.videoStartDate) { - const result = parseVisualizationJson( - jsonString, - appState.radarStartTimeMs, - appState.videoStartDate - ); - if (!result.error) { - appState.vizData = result.data; - appState.globalMinSnr = result.minSnr; - appState.globalMaxSnr = result.maxSnr; - snrMinInput.value = appState.globalMinSnr.toFixed(1); - snrMaxInput.value = appState.globalMaxSnr.toFixed(1); - } else { - showModal(result.error); - } - } - - // 2. Process CAN log (only if video start date is available). - if (canLogText && appState.videoStartDate) { - const result = processCanLog(canLogText, appState.videoStartDate); - if (!result.error) { - appState.canData = result.data; - } - } - - // 3. Update all UI elements now that data is processed. - if (appState.vizData) { - resetVisualization(); - canvasPlaceholder.style.display = "none"; - featureToggles.classList.remove("hidden"); - if (!appState.p5_instance) { - appState.p5_instance = new p5(radarSketch); - } - } - if (appState.canData.length > 0 || appState.vizData) { - speedGraphPlaceholder.classList.add("hidden"); - if (!appState.speedGraphInstance) { - appState.speedGraphInstance = new p5(speedGraphSketch); - } - appState.speedGraphInstance.setData( - appState.canData, - appState.vizData, - videoPlayer.duration - ); - } - }; - - // Main controller for processing data based on video availability. - if (videoBlob) { - const fileURL = URL.createObjectURL(videoBlob); - setupVideoPlayer(fileURL); - // This ensures we ONLY process data once the video's duration is known. - videoPlayer.onloadedmetadata = processAllData; - } else { - // If there's no video, process other data immediately. - processAllData(); - } - }) - .catch((error) => { - console.error("DEBUG: Error during Promise.all data loading:", error); - }); - }); -}); diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js index 8fa85ae..759e417 100644 --- a/steps/src/p5/radarSketch.js +++ b/steps/src/p5/radarSketch.js @@ -456,24 +456,7 @@ export const radarSketch = function (p) { b.pop(); }; - // Handle window resizing event - /* p.windowResized = function () { - p.resizeCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); - // BUG FIX 2: Re-create the buffer instead of resizing it - staticBackgroundBuffer = p.createGraphics(p.width, p.height); - trackLegendBuffer = p.createGraphics(100, 100); - p.drawTrackLegendToBuffer(); - calculatePlotScales(); - drawStaticRegionsToBuffer(p, staticBackgroundBuffer, plotScales); - if (appState.zoomSketchInstance) { - appState.zoomSketchInstance.handleResize(); - } - if (appState.vizData) { - p.redraw(); - } - }; */ - // In src/p5/radarSketch.js p.windowResized = function () { console.log("radarSketch: windowResized triggered!"); diff --git a/steps/src/sync.js b/steps/src/sync.js index 1e707b0..4461f60 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -10,11 +10,7 @@ import { } from "./dom.js"; import { findRadarFrameIndexForTime } from "./utils.js"; -/** - * The main animation loop that drives the synchronized playback. - * It calculates the current media time based on performance.now() for a smooth clock, - * finds the corresponding radar frame, and handles resynchronization with the video element. - */ + export function animationLoop() { if (!appState.isPlaying) return; diff --git a/steps/src/utils.js b/steps/src/utils.js index ca8d497..22939d0 100644 --- a/steps/src/utils.js +++ b/steps/src/utils.js @@ -104,14 +104,6 @@ export function parseTimestamp(timestampStr, format) { // If getTime() returns NaN, the date is invalid. return isNaN(date.getTime()) ? null : date; } - -/** - * Creates a throttled function that only invokes the provided function - * at most once per every `delay` milliseconds. - * @param {Function} func The function to throttle. - * @param {number} delay The number of milliseconds to throttle invocations to. - * @returns {Function} Returns the new throttled function. - */ export function throttle(func, delay) { // `lastCall` keeps track of the timestamp of the last successful invocation. let lastCall = 0; @@ -130,11 +122,6 @@ export function throttle(func, delay) { }; } -/** - * Formats milliseconds into a MM:SS.ms string. - * @param {number} milliseconds The time in milliseconds. - * @returns {string} The formatted time string. - */ export function formatTime(milliseconds) { if (isNaN(milliseconds) || milliseconds < 0) { return "00:00.000"; @@ -147,11 +134,6 @@ export function formatTime(milliseconds) { return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(ms).padStart(3, '0')}`; } -/** - * 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";