Browse Source

Some minor code cleanup. Commented out blocks being removed.

refactor/modularize
RUSHIL AMBARISH KADU 6 months ago
parent
commit
8fb6ca01b4
  1. 22
      steps/src/dataExplorer.js
  2. 13
      steps/src/db.js
  3. 55
      steps/src/drawUtils.js
  4. 11
      steps/src/fileParsers.js
  5. 28
      steps/src/main.js
  6. 617
      steps/src/main_old.js
  7. 17
      steps/src/p5/radarSketch.js
  8. 6
      steps/src/sync.js
  9. 18
      steps/src/utils.js

22
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);

13
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<Blob|null>} 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) => {

55
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

11
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: [],

28
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);

617
steps/src/main_old.js

@ -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);
});
});
});

17
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!");

6
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;

18
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";

Loading…
Cancel
Save