Browse Source
feat: Implement robust two-stage file loading and fix race condition
feat: Implement robust two-stage file loading and fix race condition
This commit introduces a major improvement to the file loading pipeline, resolving a critical race condition that occurred during fresh loads and drag-and-drop actions. Previously, the application would attempt to initialize data-dependent components (like the speed graph) and manage the loading modal simultaneously, leading to timing issues. The core of this fix is a new, robust processFilePipeline function in main.js that implements a two-stage video loading process. This decouples data initialization from UI updates, ensuring each occurs at the correct point in the browser's file loading lifecycle. Key Changes & Bug Fixes: main.js: Refactored processFilePipeline Two-Stage Video Loading: The video loading process now uses two distinct event listeners: loadedmetadata: Fires as soon as the video's duration is known. This event now immediately triggers finalizeSetup(), ensuring that the speedGraphSketch is created with the correct time axis, fixing the blank graph bug. canplaythrough: Fires only after the video has buffered enough for smooth playback. The resolution of the main videoReadyPromise is tied to this event, guaranteeing the loading modal is hidden at the appropriate time and resolving the "stuck modal" bug. Explicit Data Synchronization: A final, crucial fix was added to finalizeSetup() to re-synchronize all radar frame timestamps against the video's confirmed start time. This eliminates data mismatches that previously caused NaN errors on fresh loads. speedGraphSketch.js: Enhanced Robustness The sketch's draw() and drawTimeIndicator() functions have been made more defensive. They now check that both videoDuration and appState.currentFrame are valid before attempting to render, preventing crashes and NaN errors if the sketch is asked to draw before all data is ready. modal.js: Improved Loading Modal The modal logic was updated to support a dedicated loading state with a progress bar, providing better user feedback during the file parsing and video buffering stages.refactor/modularize
8 changed files with 2359 additions and 292 deletions
-
40steps/src/dom.js
-
515steps/src/main.js
-
63steps/src/modal.js
-
75steps/src/p5/speedGraphSketch.js
-
1steps/src/p5/zoomSketch.js
-
423zoomsketch-issue/dom.js
-
1429zoomsketch-issue/main.js
-
97zoomsketch-issue/modal.js
@ -0,0 +1,423 @@ |
|||
import { appState } from "./state.js"; |
|||
import { formatUTCTime } from "./utils.js"; |
|||
// Also import VIDEO_FPS from constants
|
|||
import { VIDEO_FPS } from "./constants.js"; |
|||
|
|||
// --- DOM Element References --- //
|
|||
|
|||
export const themeToggleBtn = document.getElementById("theme-toggle"); |
|||
export const canvasContainer = document.getElementById("canvas-container"); |
|||
export const canvasPlaceholder = document.getElementById("canvas-placeholder"); |
|||
export const videoPlayer = document.getElementById("video-player"); |
|||
export const videoPlaceholder = document.getElementById("video-placeholder"); |
|||
export const loadJsonBtn = document.getElementById("load-json-btn"); |
|||
export const loadVideoBtn = document.getElementById("load-video-btn"); |
|||
export const loadCanBtn = document.getElementById("load-can-btn"); |
|||
export const jsonFileInput = document.getElementById("json-file-input"); |
|||
export const videoFileInput = document.getElementById("video-file-input"); |
|||
export const canFileInput = document.getElementById("can-file-input"); |
|||
export const playPauseBtn = document.getElementById("play-pause-btn"); |
|||
export const stopBtn = document.getElementById("stop-btn"); |
|||
export const timelineSlider = document.getElementById("timeline-slider"); |
|||
export const frameCounter = document.getElementById("frame-counter"); |
|||
export const offsetInput = document.getElementById("offset-input"); |
|||
export const speedSlider = document.getElementById("speed-slider"); |
|||
export const speedDisplay = document.getElementById("speed-display"); |
|||
export const featureToggles = document.getElementById("feature-toggles"); |
|||
export const toggleSnrColor = document.getElementById("toggle-snr-color"); |
|||
export const toggleClusterColor = document.getElementById("toggle-cluster-color"); |
|||
export const toggleInlierColor = document.getElementById("toggle-inlier-color"); |
|||
export const toggleStationaryColor = document.getElementById("toggle-stationary-color"); |
|||
export const toggleVelocity = document.getElementById("toggle-velocity"); |
|||
export const toggleTracks = document.getElementById("toggle-tracks"); |
|||
export const toggleEgoSpeed = document.getElementById("toggle-ego-speed"); |
|||
export const toggleFrameNorm = document.getElementById("toggle-frame-norm"); |
|||
export const toggleDebugOverlay = document.getElementById("toggle-debug-overlay"); |
|||
export const egoSpeedDisplay = document.getElementById("ego-speed-display"); |
|||
export const canSpeedDisplay = document.getElementById("can-speed-display"); |
|||
export const debugOverlay = document.getElementById("debug-overlay"); |
|||
export const toggleDebug2Overlay = document.getElementById("toggle-debug2-overlay"); |
|||
export const snrMinInput = document.getElementById("snr-min-input"); |
|||
export const snrMaxInput = document.getElementById("snr-max-input"); |
|||
export const applySnrBtn = document.getElementById("apply-snr-btn"); |
|||
export const autoOffsetIndicator = document.getElementById("auto-offset-indicator"); |
|||
export const clearCacheBtn = document.getElementById("clear-cache-btn"); |
|||
export const speedGraphContainer = document.getElementById("speed-graph-container"); |
|||
export const speedGraphPlaceholder = document.getElementById("speed-graph-placeholder"); |
|||
export const modalContainer = document.getElementById("modal-container"); |
|||
export const modalOverlay = document.getElementById("modal-overlay"); |
|||
export const modalContent = document.getElementById("modal-content"); |
|||
export const modalText = document.getElementById("modal-text"); |
|||
export const modalOkBtn = document.getElementById("modal-ok-btn"); |
|||
export const modalCancelBtn = document.getElementById("modal-cancel-btn"); |
|||
export const toggleCloseUp = document.getElementById("toggle-close-up"); |
|||
export const togglePredictedPos = document.getElementById("toggle-predicted-pos"); |
|||
export const toggleCovariance = document.getElementById("toggle-covariance"); |
|||
export const modalProgressContainer = document.getElementById("modal-progress-container"); |
|||
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"); |
|||
export const saveSessionBtn = document.getElementById("save-session-btn"); |
|||
export const loadSessionBtn = document.getElementById("load-session-btn"); |
|||
export const sessionFileInput = document.getElementById("session-file-input"); |
|||
export const ttcModeDefault = document.getElementById("ttc-mode-default"); |
|||
export const ttcModeCustom = document.getElementById("ttc-mode-custom"); |
|||
export const customTtcPanel = document.getElementById("custom-ttc-panel"); |
|||
export const ttcColorCritical = document.getElementById("ttc-color-critical"); |
|||
export const ttcTimeCritical = document.getElementById("ttc-time-critical"); |
|||
export const ttcColorHigh = document.getElementById("ttc-color-high"); |
|||
export const ttcTimeHigh = document.getElementById("ttc-time-high"); |
|||
export const ttcColorMedium = document.getElementById("ttc-color-medium"); |
|||
export const ttcTimeMedium = document.getElementById("ttc-time-medium"); |
|||
export const ttcColorLow = document.getElementById("ttc-color-low"); |
|||
export const collapsibleMenu = document.getElementById("collapsible-menu"); |
|||
export const toggleMenuBtn = document.getElementById("toggle-menu-btn"); |
|||
export const fullscreenBtn = document.getElementById("fullscreen-btn"); |
|||
export const mainContent = document.querySelector("main"); |
|||
export const closeMenuBtn = document.getElementById("close-menu-btn"); |
|||
export const fullscreenEnterIcon = document.getElementById("fullscreen-enter-icon"); |
|||
export const fullscreenExitIcon = document.getElementById("fullscreen-exit-icon"); |
|||
export const menuScrim = document.getElementById("menu-scrim"); |
|||
export const toggleConfirmedOnly = document.getElementById("toggle-confirmed-only"); |
|||
|
|||
|
|||
//----------------------UPDATE FRAME Function----------------------//
|
|||
// Updates the UI to reflect the current radar frame and synchronizes video playback.
|
|||
export function updateFrame(frame, forceVideoSeek) { |
|||
const startTime = performance.now(); //start emasuring timer of performance.
|
|||
if ( |
|||
!appState.vizData || |
|||
frame < 0 || |
|||
frame >= appState.vizData.radarFrames.length |
|||
) |
|||
// Exit if no visualization data or invalid frame.
|
|||
return; // Exit if no visualization data or invalid frame
|
|||
appState.currentFrame = frame; |
|||
timelineSlider.value = appState.currentFrame; |
|||
frameCounter.textContent = `Frame: ${appState.currentFrame + 1} / ${ |
|||
appState.vizData.radarFrames.length |
|||
}`;
|
|||
const frameData = appState.vizData.radarFrames[appState.currentFrame]; |
|||
if (toggleEgoSpeed.checked && frameData) { |
|||
// Update ego speed display if enabled.
|
|||
const egoVy_kmh = (frameData.egoVelocity[1] * 3.6).toFixed(1); // Convert m/s to km/h and format
|
|||
egoSpeedDisplay.textContent = `Ego: ${egoVy_kmh} km/h`; |
|||
egoSpeedDisplay.classList.remove("hidden"); |
|||
} else { |
|||
egoSpeedDisplay.classList.add("hidden"); // Hide ego speed display.
|
|||
} |
|||
|
|||
// --- ADD THIS NEW BLOCK ---
|
|||
if ( |
|||
frameData && |
|||
frameData.canVehSpeed_kmph !== null && |
|||
!isNaN(frameData.canVehSpeed_kmph) |
|||
) { |
|||
canSpeedDisplay.textContent = `CAN: ${frameData.canVehSpeed_kmph.toFixed( |
|||
1 |
|||
)} km/h`;
|
|||
canSpeedDisplay.classList.remove("hidden"); |
|||
} else { |
|||
canSpeedDisplay.classList.add("hidden"); |
|||
} |
|||
// --- END OF NEW BLOCK ---
|
|||
|
|||
let timeForUpdates = videoPlayer.currentTime; // NEW: Default to the video's current time
|
|||
|
|||
if ( |
|||
forceVideoSeek && |
|||
videoPlayer.src && |
|||
videoPlayer.readyState > 1 && |
|||
appState.videoStartDate && |
|||
frameData |
|||
) { |
|||
const offsetMs = parseFloat(offsetInput.value) || 0; |
|||
const targetRadarTimeMs = frameData.timestampMs; |
|||
const targetVideoTimeSec = (targetRadarTimeMs - offsetMs) / 1000; |
|||
if (targetVideoTimeSec >= 0 && targetVideoTimeSec <= videoPlayer.duration) { |
|||
// Ensure target time is within video duration
|
|||
if (Math.abs(videoPlayer.currentTime - targetVideoTimeSec) > 0.05) { |
|||
// Check for significant drift
|
|||
videoPlayer.currentTime = targetVideoTimeSec; // Seek video if drift is significant
|
|||
} |
|||
// MODIFIED: Use the calculated target time for our updates, not the stale videoPlayer.currentTime
|
|||
timeForUpdates = targetVideoTimeSec; // Update time for subsequent UI updates
|
|||
} |
|||
} // End of forceVideoSeek block
|
|||
|
|||
if (!appState.isPlaying) { |
|||
// MODIFIED: Use our new synchronized time variable
|
|||
updatePersistentOverlays(timeForUpdates); |
|||
} |
|||
// --- End of fix ---
|
|||
|
|||
if (appState.p5_instance) appState.p5_instance.redraw(); // Redraw radar sketch
|
|||
if (appState.speedGraphInstance && !appState.isPlaying) |
|||
// Redraw speed graph if not playing.
|
|||
appState.speedGraphInstance.redraw(); |
|||
const endTime = performance.now(); |
|||
appState.lastFrameRenderTime = endTime - startTime; // <-- End timer and update state
|
|||
|
|||
} |
|||
|
|||
//----------------------Reset UI for New file Load----------------------//
|
|||
// Resets the UI to make sure everything is clean before new files load.
|
|||
export function resetUIForNewLoad() { |
|||
console.log("Resetting UI for new file load."); |
|||
|
|||
// Hide feature toggles
|
|||
featureToggles.classList.add("hidden"); |
|||
|
|||
// Show placeholders
|
|||
canvasPlaceholder.style.display = 'flex'; |
|||
videoPlaceholder.classList.remove('hidden'); |
|||
|
|||
// Hide video player and overlays
|
|||
videoPlayer.classList.add('hidden'); |
|||
videoPlayer.src = ''; // Clear the video source
|
|||
radarInfoOverlay.classList.add('hidden'); |
|||
videoInfoOverlay.classList.add('hidden'); |
|||
|
|||
// Remove the p5 sketches completely
|
|||
if (appState.p5_instance) { |
|||
appState.p5_instance.remove(); |
|||
appState.p5_instance = null; |
|||
} |
|||
if (appState.rawP5_instance) { |
|||
appState.rawP5_instance.remove(); |
|||
appState.rawP5_instance = null; |
|||
} |
|||
if (appState.zoomSketchInstance) { |
|||
appState.zoomSketchInstance.remove(); |
|||
appState.zoomSketchInstance = null; |
|||
} |
|||
if (appState.speedGraphInstance) { |
|||
appState.speedGraphInstance.remove(); |
|||
appState.speedGraphInstance = null; |
|||
} |
|||
|
|||
// Reset the speed graph container
|
|||
speedGraphPlaceholder.classList.remove('hidden'); |
|||
} |
|||
|
|||
//----------------------RESET VISUALIZATION Function----------------------//
|
|||
// Resets the visualization to its initial state.
|
|||
export function resetVisualization() { |
|||
appState.isPlaying = false; |
|||
playPauseBtn.textContent = "Play"; |
|||
const numFrames = appState.vizData.radarFrames.length; |
|||
timelineSlider.max = numFrames > 0 ? numFrames - 1 : 0; |
|||
updateFrame(0, true); // Update to the first frame and force video seek
|
|||
} |
|||
|
|||
//----------------------CAN DISPLAY UPDATE Function----------------------//
|
|||
// Updates the CAN speed display based on the current media time.
|
|||
|
|||
//----------------------DEBUG OVERLAY UPDATE Function----------------------//
|
|||
// Updates the debug overlay with various synchronization and time information.
|
|||
export function updateDebugOverlay(currentMediaTime) { |
|||
// Check the state of both debug toggles
|
|||
const isDebug1Visible = toggleDebugOverlay.checked; |
|||
const isDebug2Visible = toggleDebug2Overlay.checked; |
|||
|
|||
// If neither is checked, hide the overlay and stop
|
|||
if (!isDebug1Visible && !isDebug2Visible) { |
|||
debugOverlay.classList.add("hidden"); // Hide debug overlay
|
|||
return; |
|||
} |
|||
// If at least one is checked, show the overlay
|
|||
debugOverlay.classList.remove("hidden"); // Show debug overlay.
|
|||
let content = []; |
|||
|
|||
// --- Logic for the original debug overlay ---
|
|||
if (isDebug1Visible) { |
|||
content.push(`--- Basic Info ---`); |
|||
if (appState.videoStartDate) { |
|||
const videoAbsoluteTimeMs = |
|||
appState.videoStartDate.getTime() + currentMediaTime * 1000; |
|||
content.push(`Media Time (s): ${currentMediaTime.toFixed(3)}`); |
|||
content.push(`Video Frame: ${Math.floor(currentMediaTime * VIDEO_FPS)}`); |
|||
content.push( |
|||
`Vid Abs Time: ${new Date(videoAbsoluteTimeMs) |
|||
.toISOString() |
|||
.split("T")[1] |
|||
.replace("Z", "")}`
|
|||
); // Format and display video absolute time
|
|||
} else { |
|||
content.push("Video not loaded..."); // Indicate video not loaded.
|
|||
} |
|||
if ( |
|||
appState.vizData && |
|||
appState.vizData.radarFrames[appState.currentFrame] |
|||
) { |
|||
content.push(`Radar Frame: ${appState.currentFrame + 1}`); |
|||
const frameTime = |
|||
appState.vizData.radarFrames[appState.currentFrame].timestampMs; |
|||
content.push( |
|||
`Radar Abs Time: ${new Date( |
|||
appState.videoStartDate.getTime() + frameTime |
|||
) |
|||
.toISOString() |
|||
.split("T")[1] |
|||
.replace("Z", "")}`
|
|||
); // Format and display radar absolute time
|
|||
} |
|||
} |
|||
|
|||
// --- Logic for the new advanced debug overlay ---
|
|||
if (isDebug2Visible) { |
|||
content.push(`--- Sync Diagnostics ---`); |
|||
if ( |
|||
appState.videoStartDate && |
|||
appState.vizData && |
|||
appState.vizData.radarFrames[appState.currentFrame] |
|||
) { |
|||
// --- START: Corrected Debug Logic ---
|
|||
const currentRadarFrame = |
|||
appState.vizData.radarFrames[appState.currentFrame]; |
|||
const targetRadarTimeMs = currentRadarFrame.timestampMs; |
|||
const offsetMs = parseFloat(offsetInput.value) || 0; // Read the current offset
|
|||
|
|||
// Make the drift calculation "offset-aware"
|
|||
const driftMs = currentMediaTime * 1000 + offsetMs - targetRadarTimeMs; |
|||
// --- END: Corrected Debug Logic ---
|
|||
|
|||
// Style the drift value to be green if sync is good, and red if it's off.
|
|||
const driftColor = Math.abs(driftMs) > 40 ? "#FF6347" : "#98FB98"; // Tomato red or Pale green
|
|||
|
|||
content.push(`Video Time (s): ${currentMediaTime.toFixed(3)}`); // Display current video time
|
|||
content.push(`Target Radar Time (ms): ${targetRadarTimeMs.toFixed(0)}`); |
|||
content.push(`Drift (ms): <b style="color: ${driftColor};">${driftMs.toFixed(0)}</b>`); |
|||
content.push(`Video Start Time: ${appState.videoStartDate.toISOString()}`); |
|||
content.push(`Radar Start Time: ${new Date(appState.radarStartTimeMs).toISOString()}`); |
|||
content.push(`Calculated Offset (ms): ${offsetInput.value}`); // Display calculated offset.
|
|||
const renderTime = appState.lastFrameRenderTime; |
|||
// Color is green if render time is under 33ms (~30fps budget), otherwise red
|
|||
const renderTimeColor = renderTime > 33 ? "#FF6347" : "#98FB98"; |
|||
content.push(`Frame Render Time: <b style="color: ${renderTimeColor};">${renderTime.toFixed(1)}ms</b>`); |
|||
const videoRenderTime = appState.videoFrameRenderTime; |
|||
// Color is green if render time is under 34ms (~30fps), otherwise red
|
|||
const videoRenderTimeColor = videoRenderTime > 34 ? "#FF6347" : "#98FB98"; |
|||
content.push(`Video Frame Time: <b style="color: ${videoRenderTimeColor};">${videoRenderTime.toFixed(1)}ms</b>`); |
|||
} else { |
|||
content.push("Load video and radar data to see sync info."); // Prompt to load data.
|
|||
} |
|||
} |
|||
|
|||
debugOverlay.innerHTML = content.join("<br>"); // Update debug overlay content.
|
|||
} |
|||
|
|||
// This function checks the state of the color toggles and returns the active mode.
|
|||
function getCurrentColorMode() { |
|||
if (toggleSnrColor.checked) return "Color by SNR (1)"; |
|||
if (toggleClusterColor.checked) return "Color by Cluster (2)"; |
|||
if (toggleInlierColor.checked) return "Color by Inlier (3)"; |
|||
if (toggleStationaryColor.checked) return "Color by Stationary (4)"; |
|||
return "Default"; // The default mode when no specific color toggle is checked
|
|||
} |
|||
|
|||
export function updatePersistentOverlays(currentMediaTime) { |
|||
// If we don't have the necessary data, hide the overlays and exit.
|
|||
const isDebug1Visible = toggleDebugOverlay.checked; |
|||
const isDebug2Visible = toggleDebug2Overlay.checked; |
|||
|
|||
if (!appState.vizData || !appState.videoStartDate) { |
|||
radarInfoOverlay.classList.add("hidden"); |
|||
videoInfoOverlay.classList.add("hidden"); |
|||
return; |
|||
} |
|||
if (isDebug1Visible && isDebug2Visible) { |
|||
radarInfoOverlay.classList.add("hidden"); |
|||
videoInfoOverlay.classList.add("hidden"); |
|||
return; |
|||
} |
|||
if(isDebug1Visible || isDebug2Visible){ |
|||
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]; |
|||
const frameData = appState.vizData.radarFrames[appState.currentFrame]; |
|||
const motionState = frameData.motionState; |
|||
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
|
|||
const colorMode = getCurrentColorMode(); |
|||
|
|||
radarInfoOverlay.innerHTML = `
|
|||
Frame: ${appState.currentFrame + 1} |
|||
Motion State: ${motionState} |
|||
| Abs Time: ${formatUTCTime(absRadarTime)} |
|||
| Color Mode: <b>${colorMode}</b> |
|||
| 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); |
|||
//console.warn('Could not load radarframes ', appState.vizData.radarFrames) console warning for reference
|
|||
|
|||
videoInfoOverlay.innerHTML = `
|
|||
Frame: ${videoFrame} |
|||
| Abs Time: ${formatUTCTime(absVideoTime)} |
|||
`;
|
|||
} |
|||
|
|||
const customTtcInputs = [ |
|||
ttcColorCritical, |
|||
ttcTimeCritical, |
|||
ttcColorHigh, |
|||
ttcTimeHigh, |
|||
ttcColorMedium, |
|||
ttcTimeMedium, |
|||
]; |
|||
|
|||
function updateCustomTtcScheme() { |
|||
appState.customTtcScheme.critical.time = parseFloat(ttcTimeCritical.value); |
|||
appState.customTtcScheme.critical.color = ttcColorCritical.value; |
|||
appState.customTtcScheme.high.time = parseFloat(ttcTimeHigh.value); |
|||
appState.customTtcScheme.high.color = ttcColorHigh.value; |
|||
appState.customTtcScheme.medium.time = parseFloat(ttcTimeMedium.value); |
|||
appState.customTtcScheme.medium.color = ttcColorMedium.value; |
|||
|
|||
if (appState.p5_instance) { |
|||
appState.p5_instance.redraw(); |
|||
} |
|||
} |
|||
|
|||
ttcModeDefault.addEventListener("change", () => { |
|||
if (ttcModeDefault.checked) { |
|||
appState.useCustomTtcScheme = false; |
|||
customTtcPanel.classList.add("hidden"); |
|||
if (appState.p5_instance) appState.p5_instance.redraw(); |
|||
} |
|||
}); |
|||
|
|||
ttcModeCustom.addEventListener("change", () => { |
|||
if (ttcModeCustom.checked) { |
|||
appState.useCustomTtcScheme = true; |
|||
customTtcPanel.classList.remove("hidden"); |
|||
updateCustomTtcScheme(); // Apply current custom values immediately
|
|||
} |
|||
}); |
|||
|
|||
// Add listeners to all custom inputs to update the scheme on the fly
|
|||
customTtcInputs.forEach((input) => { |
|||
input.addEventListener("input", updateCustomTtcScheme); |
|||
}); |
|||
1429
zoomsketch-issue/main.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,97 @@ |
|||
import { |
|||
modalCancelBtn, |
|||
modalContainer, |
|||
modalOverlay, |
|||
modalContent, |
|||
modalText, |
|||
modalOkBtn, |
|||
modalProgressContainer, |
|||
modalProgressBar, |
|||
modalProgressText, |
|||
} from "./dom.js"; |
|||
|
|||
let modalResolve = null; |
|||
|
|||
// The showModal function is now simpler.
|
|||
/* export function showModal(message, isConfirm = false) { |
|||
return new Promise((resolve) => { |
|||
modalText.textContent = message; |
|||
modalCancelBtn.classList.toggle("hidden"); |
|||
modalOkBtn.classList.toggle("hidden", isConfirm); |
|||
modalProgressContainer.classList.add("hidden"); // Hide progress by default
|
|||
|
|||
modalContainer.classList.remove("hidden"); |
|||
setTimeout(() => { |
|||
modalOverlay.classList.remove("opacity-0"); |
|||
modalContent.classList.remove("scale-95"); |
|||
}, 10); |
|||
modalResolve = resolve; |
|||
}); |
|||
} */ |
|||
|
|||
|
|||
|
|||
export function showModal(message, isConfirm = false) { |
|||
return new Promise((resolve) => { |
|||
modalText.textContent = message; |
|||
// This line correctly shows the "Cancel" button only when needed.
|
|||
modalCancelBtn.classList.toggle("hidden", !isConfirm); |
|||
|
|||
// --- THIS IS THE FIX ---
|
|||
// This ensures the "OK" button is always visible for this modal.
|
|||
modalOkBtn.classList.remove("hidden"); |
|||
|
|||
modalProgressContainer.classList.add("hidden"); |
|||
|
|||
modalContainer.classList.remove("hidden"); |
|||
setTimeout(() => { |
|||
modalOverlay.classList.remove("opacity-0"); |
|||
modalContent.classList.remove("scale-95"); |
|||
}, 10); |
|||
modalResolve = resolve; |
|||
}); |
|||
} |
|||
// A new function specifically for the loading modal
|
|||
export function showLoadingModal(message) { |
|||
modalText.textContent = message; |
|||
modalOkBtn.classList.add('hidden'); |
|||
modalCancelBtn.classList.add('hidden'); |
|||
modalProgressContainer.classList.remove('hidden'); |
|||
modalProgressBar.style.width = '0%'; |
|||
modalProgressText.textContent = 'Initializing...'; |
|||
|
|||
modalContainer.classList.remove("hidden"); |
|||
setTimeout(() => { |
|||
modalOverlay.classList.remove("opacity-0"); |
|||
modalContent.classList.remove("scale-95"); |
|||
}, 10); |
|||
} |
|||
|
|||
// A new function to update the progress bar and text
|
|||
export function updateLoadingModal(percent, message) { |
|||
if (modalProgressBar && modalProgressText) { |
|||
const p = Math.max(0, Math.min(100, Math.round(percent))); // Clamp between 0-100
|
|||
modalProgressBar.style.width = `${p}%`; |
|||
modalProgressText.textContent = message; |
|||
} |
|||
} |
|||
|
|||
// The hideModal function now also resets the progress bar
|
|||
export function hideModal(value) { |
|||
modalOverlay.classList.add("opacity-0"); |
|||
modalContent.classList.add("scale-95"); |
|||
setTimeout(() => { |
|||
modalContainer.classList.add("hidden"); |
|||
if (modalProgressContainer && modalProgressBar && modalProgressText) { |
|||
modalProgressContainer.classList.add("hidden"); |
|||
modalProgressBar.style.width = "0%"; |
|||
modalProgressText.textContent = ""; |
|||
} |
|||
if (modalResolve) modalResolve(value); |
|||
}, 200); |
|||
} |
|||
|
|||
// Event listeners remain the same
|
|||
modalOkBtn.addEventListener("click", () => hideModal(true)); |
|||
modalCancelBtn.addEventListener("click", () => hideModal(false)); |
|||
modalOverlay.addEventListener("click", () => hideModal(false)); |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue