Browse Source

Minor BUG fixes and timeline slider overhaul with DEBUG overlay fix.

refactor/modularize
RUSHIL AMBARISH KADU 9 months ago
parent
commit
943cccea3d
  1. 2
      steps/index.html
  2. 47
      steps/src/dom.js
  3. 96
      steps/src/main.js
  4. 3
      steps/src/sync.js
  5. 19
      steps/src/utils.js

2
steps/index.html

@ -221,7 +221,7 @@
<div id="speed-graph-container"
class="w-full h-[27vh] bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-inner flex items-center justify-center">
<p id="speed-graph-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">
Load CAN log to see speed graph
Load JSON to see speed graph
</p>
</div>
</div>

47
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("<br>"); // 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: <b style="color: ${driftColor};">${driftMs.toFixed(0)}ms</b>
`;
}
// --- 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)}
`;
}

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

3
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();

19
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')}`;
}
}
/**
* 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}`;
}
Loading…
Cancel
Save