diff --git a/steps/index.html b/steps/index.html index 4626960..3afd1a9 100644 --- a/steps/index.html +++ b/steps/index.html @@ -223,6 +223,70 @@ + + +
+ + +
+ + + + +
+ +
+

Data Synchronizer

+

Provide radar dataset and video parameters to initialize workspace

+ +
+ + + +

Drag & Drop files here

+

Requires .json (and optionally video) format

+
+ +
+ + +
+ +
+ +
+ + + +
+

Radar and Video Synchronizer @@ -399,7 +463,7 @@
-

Load JSON data to start +

@@ -407,7 +471,7 @@ 80m + style="writing-mode: bt-lr; -webkit-appearance: slider-vertical; appearance: slider-vertical; width: 8px;" /> Range
@@ -425,7 +489,7 @@
-

Load a video file

+ diff --git a/steps/src/dom.js b/steps/src/dom.js index 65be056..afe226d 100644 --- a/steps/src/dom.js +++ b/steps/src/dom.js @@ -23,6 +23,22 @@ function getTimingColor(diffMs) { // --- DOM Element References --- // +export const startScreenModal = document.getElementById("start-screen-modal"); +export const startDropZone = document.getElementById("start-drop-zone"); +export const startLoadJsonBtn = document.getElementById("start-load-json-btn"); +export const startLoadVideoBtn = document.getElementById("start-load-video-btn"); +export const startClearCacheBtn = document.getElementById("start-clear-cache-btn"); +export const startProgressContainer = document.getElementById("start-progress-container"); +export const startProgressBar = document.getElementById("start-progress-bar"); +export const startProgressText = document.getElementById("start-progress-text"); + +export const startUserManualBtn = document.getElementById("start-user-manual-btn"); +export const startCodebaseBtn = document.getElementById("start-codebase-btn"); +export const startChangelogBtn = document.getElementById("start-changelog-btn"); +export const startThemeToggleBtn = document.getElementById("start-theme-toggle"); +export const startThemeToggleDarkIcon = document.getElementById("start-theme-toggle-dark-icon"); +export const startThemeToggleLightIcon = document.getElementById("start-theme-toggle-light-icon"); + export const themeToggleBtn = document.getElementById("theme-toggle"); export const canvasContainer = document.getElementById("canvas-container"); export const canvasPlaceholder = document.getElementById("canvas-placeholder"); diff --git a/steps/src/fileLoader.js b/steps/src/fileLoader.js index a6d4ee7..904b164 100644 --- a/steps/src/fileLoader.js +++ b/steps/src/fileLoader.js @@ -31,6 +31,7 @@ import { updatePersistentOverlays, updateDebugOverlay, resetUIForNewLoad, + startScreenModal, } from "./dom.js"; import { forceResyncWithOffset } from "./sync.js"; @@ -164,11 +165,12 @@ async function processFilePipeline(jsonFile, videoFile, fromCache) { // --- PART F: Finalize UI --- finalizeSetup(); - // Hide modal only if the video didn't fail. If it failed, the video - // loader has already handled showing an error/choice modal. if (!appState.videoMissing) { updateLoadingModal(100, "Complete!"); - setTimeout(hideModal, 300); + setTimeout(() => { + hideModal(); + startScreenModal.classList.add("hidden"); + }, 300); } // Log the results of the non-blocking cache operations once they complete. diff --git a/steps/src/main.js b/steps/src/main.js index 8c5364c..cf60f98 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -53,6 +53,11 @@ import { shortcutsModal, shortcutsModalCloseBtn, guideModalCloseBtn, + startScreenModal, + startDropZone, + startLoadJsonBtn, + startLoadVideoBtn, + startClearCacheBtn, } from "./dom.js"; import { initializeTheme } from "./theme.js"; @@ -69,23 +74,35 @@ videoFileInput.addEventListener("change", (event) => handleFiles(event.target.files) ); -// Wire up the drag-and-drop functionality -const dropZone = document.querySelector("main"); -dropZone.addEventListener("dragover", (event) => { +// Wire up the drag-and-drop functionality for the start screen +startDropZone.addEventListener("dragover", (event) => { event.preventDefault(); - dropZone.style.border = "2px dashed #3b82f6"; + startDropZone.classList.add("border-blue-500", "bg-blue-50", "dark:bg-gray-700"); }); -dropZone.addEventListener("dragleave", () => { - dropZone.style.border = "none"; +startDropZone.addEventListener("dragleave", () => { + startDropZone.classList.remove("border-blue-500", "bg-blue-50", "dark:bg-gray-700"); }); -dropZone.addEventListener("drop", (event) => { +startDropZone.addEventListener("drop", (event) => { event.preventDefault(); - dropZone.style.border = "none"; + startDropZone.classList.remove("border-blue-500", "bg-blue-50", "dark:bg-gray-700"); handleFiles(event.dataTransfer.files); }); +// Also keep the main body as a backup drop zone for modifying active sessions +const mainDropZone = document.querySelector("main"); +mainDropZone.addEventListener("dragover", (event) => { + event.preventDefault(); +}); +mainDropZone.addEventListener("drop", (event) => { + event.preventDefault(); + handleFiles(event.dataTransfer.files); +}); + +// Event listeners for loading files (Start Screen) +startLoadJsonBtn.addEventListener("click", () => jsonFileInput.click()); +startLoadVideoBtn.addEventListener("click", () => videoFileInput.click()); -// Event listener for loading JSON file. +// Event listeners for loading files (Workspace Footer - Legacy) loadJsonBtn.addEventListener("click", () => jsonFileInput.click()); loadVideoBtn.addEventListener("click", () => videoFileInput.click()); @@ -98,6 +115,15 @@ clearCacheBtn.addEventListener("click", async () => { } }); +startClearCacheBtn.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 offset input change. offsetInput.addEventListener("input", () => { autoOffsetIndicator.classList.add("hidden"); diff --git a/steps/src/modal.js b/steps/src/modal.js index 35974f7..fe850c2 100644 --- a/steps/src/modal.js +++ b/steps/src/modal.js @@ -8,6 +8,13 @@ import { modalProgressContainer, modalProgressBar, modalProgressText, + startScreenModal, + startProgressContainer, + startProgressBar, + startProgressText, + startDropZone, + startLoadJsonBtn, + startLoadVideoBtn, } from "./dom.js"; let modalResolve = null; @@ -38,25 +45,46 @@ export function showModal( } // A new function specifically for the loading modal -export function showLoadingModal(message) { - modalText.textContent = message; - modalOkBtn.classList.add('hidden'); // Hide OK button for loading - modalCancelBtn.classList.add('hidden'); // Initially hide cancel button - 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); +export function showLoadingModal(message, forcePopup = false) { + if (!startScreenModal.classList.contains('hidden') && !forcePopup) { + // Integrated start screen flow + startProgressContainer.classList.remove('hidden'); + startProgressBar.style.width = '0%'; + startProgressText.textContent = message; + + // Optionally disable the interactive elements so users don't multi-click + startDropZone.classList.add('opacity-50', 'pointer-events-none'); + startLoadJsonBtn.classList.add('opacity-50', 'pointer-events-none'); + startLoadVideoBtn.classList.add('opacity-50', 'pointer-events-none'); + } else { + // Fallback generic popup modal flow + modalText.textContent = message; + modalOkBtn.classList.add('hidden'); // Hide OK button for loading + modalCancelBtn.classList.add('hidden'); // Initially hide cancel button + 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 + const p = Math.max(0, Math.min(100, Math.round(percent))); // Clamp between 0-100 + + // Update integrated start screen loader if active + if (!startProgressContainer.classList.contains('hidden')) { + startProgressBar.style.width = `${p}%`; + startProgressText.textContent = message; + } + + // Update generic modal loader if active + if (!modalProgressContainer.classList.contains('hidden')) { modalProgressBar.style.width = `${p}%`; modalProgressText.textContent = message; } @@ -64,7 +92,7 @@ export function updateLoadingModal(percent, message) { export function runStartupLoader(durationMs = 10000) { return new Promise((resolve, reject) => { - showLoadingModal("Opening Quick Start Guide..."); + showLoadingModal("Opening Quick Start Guide...", true); modalCancelBtn.textContent = "Skip Guide"; modalCancelBtn.classList.remove("hidden"); // Show cancel button for startup loader @@ -103,6 +131,17 @@ export function runStartupLoader(durationMs = 10000) { // The hideModal function now also resets the progress bar export function hideModal(value) { // This now returns a promise return new Promise(resolve => { + // Hide the Integrated Loading elements if active + if (!startProgressContainer.classList.contains('hidden')) { + startProgressContainer.classList.add('hidden'); + startProgressBar.style.width = '0%'; + startProgressText.textContent = ''; + + startDropZone.classList.remove('opacity-50', 'pointer-events-none'); + startLoadJsonBtn.classList.remove('opacity-50', 'pointer-events-none'); + startLoadVideoBtn.classList.remove('opacity-50', 'pointer-events-none'); + } + modalOverlay.classList.add("opacity-0"); modalContent.classList.add("scale-95"); setTimeout(() => { diff --git a/steps/src/theme.js b/steps/src/theme.js index e147fab..5ce4242 100644 --- a/steps/src/theme.js +++ b/steps/src/theme.js @@ -1,5 +1,5 @@ import { appState } from "./state.js"; -import { videoPlayer, themeToggleBtn} from "./dom.js"; +import { videoPlayer, themeToggleBtn, startThemeToggleBtn, startThemeToggleDarkIcon, startThemeToggleLightIcon } from "./dom.js"; const darkIcon = document.getElementById("theme-toggle-dark-icon"); const lightIcon = document.getElementById("theme-toggle-light-icon"); @@ -8,11 +8,15 @@ function setTheme(theme) { document.documentElement.classList.add("dark"); lightIcon.classList.remove("hidden"); darkIcon.classList.add("hidden"); + if (startThemeToggleLightIcon) startThemeToggleLightIcon.classList.remove("hidden"); + if (startThemeToggleDarkIcon) startThemeToggleDarkIcon.classList.add("hidden"); localStorage.setItem("color-theme", "dark"); } else { document.documentElement.classList.remove("dark"); darkIcon.classList.remove("hidden"); lightIcon.classList.add("hidden"); + if (startThemeToggleDarkIcon) startThemeToggleDarkIcon.classList.remove("hidden"); + if (startThemeToggleLightIcon) startThemeToggleLightIcon.classList.add("hidden"); localStorage.setItem("color-theme", "light"); } @@ -62,4 +66,12 @@ export function initializeTheme() { setTheme("dark"); } }); + + startThemeToggleBtn.addEventListener("click", () => { + if (document.documentElement.classList.contains("dark")) { + setTheme("light"); + } else { + setTheme("dark"); + } + }); } diff --git a/steps/src/ui.js b/steps/src/ui.js index 18b94ac..5094499 100644 --- a/steps/src/ui.js +++ b/steps/src/ui.js @@ -45,6 +45,9 @@ import { changelogBtn, changelogModal, changelogModalCloseBtn, + startUserManualBtn, + startCodebaseBtn, + startChangelogBtn, } from "./dom.js"; function toggleMenu(show) { @@ -134,6 +137,10 @@ export function initUIEventListeners() { e.preventDefault(); toggleGuideModal(true); }); + startUserManualBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleGuideModal(true); + }); guideModalCloseBtn.addEventListener("click", () => toggleGuideModal(false)); guideModal.addEventListener("click", (e) => { if (e.target === guideModal) { @@ -146,6 +153,10 @@ export function initUIEventListeners() { e.preventDefault(); toggleCodebaseModal(true); }); + startCodebaseBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleCodebaseModal(true); + }); codebaseModalCloseBtn.addEventListener("click", () => toggleCodebaseModal(false)); codebaseModal.addEventListener("click", (e) => { if (e.target === codebaseModal) { @@ -158,6 +169,10 @@ export function initUIEventListeners() { e.preventDefault(); toggleChangelogModal(true); }); + startChangelogBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleChangelogModal(true); + }); changelogModalCloseBtn.addEventListener("click", () => toggleChangelogModal(false)); changelogModal.addEventListener("click", (e) => { if (e.target === changelogModal) {