From b3c417ce050b930de16450f056f13b2f0932bb38 Mon Sep 17 00:00:00 2001 From: dirwin31 <83434411+dirwin31@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:59:01 -0700 Subject: [PATCH] Change to an overlay --- .../components/recordings/dashcam_routes.css | 174 +++++++++++++++--- .../components/recordings/dashcam_routes.js | 93 +++++++--- 2 files changed, 219 insertions(+), 48 deletions(-) diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css index 65647709e..029ecc63c 100644 --- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css +++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css @@ -244,45 +244,164 @@ transform: var(--hover-scale-sm); } -.route-logs { - margin-top: var(--padding-base); - max-height: 40vh; - overflow-y: auto; +.dialog-box.route-logs-dialog { + border: var(--border-width-thin) solid var(--sidebar-border-color); + box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.55); + display: flex; + flex-direction: column; + max-height: min(42rem, calc(100vh - 2rem)); + max-width: none; + min-width: 0; + overflow: hidden; + padding: 0; text-align: left; + width: min(36rem, calc(100vw - 2rem)); +} + +.route-logs-toolbar { + align-items: center; + background: var(--input-bg); + border-bottom: var(--border-width-thin) solid var(--sidebar-border-color); + display: flex; + flex-shrink: 0; + justify-content: space-between; + padding: var(--padding-base) var(--padding-lg); +} + +.route-logs-toolbar h2, +.route-logs-eyebrow { + margin: 0; +} + +.route-logs-toolbar h2 { + color: var(--text-color); + font-size: var(--font-size-xl); + line-height: 1.2; +} + +.route-logs-eyebrow { + color: var(--text-muted); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-bold); + letter-spacing: 0.08em; + margin-bottom: var(--margin-xs); + text-transform: uppercase; +} + +.route-logs-close { + align-items: center; + background: transparent; + border: 0; + border-radius: 50%; + color: var(--text-muted); + cursor: pointer; + display: flex; + font-size: 2rem; + height: 2.5rem; + justify-content: center; + line-height: 1; + transition: background-color var(--transition-fast), color var(--transition-fast); + width: 2.5rem; +} + +.route-logs-close:hover, +.route-logs-close:focus-visible { + background: var(--sidebar-active-bg); + color: var(--text-color); + outline: none; +} + +.route-logs-content { + overflow-y: auto; + padding: var(--padding-lg); } .route-logs-message { + color: var(--text-color); + font-size: var(--font-size-base); + line-height: var(--line-height-base); margin: 0; - padding: var(--padding-sm) 0; - font-size: var(--font-size-sm); + padding: var(--padding-lg) var(--padding-sm); + text-align: center; } -.route-logs-header { - display: flex; +.route-logs-error { + color: var(--danger-fg); +} + +.route-logs-summary { align-items: center; + background: var(--input-bg); + border: var(--border-width-thin) solid var(--sidebar-border-color); + border-radius: var(--border-radius-md); + display: flex; justify-content: space-between; - gap: var(--padding-sm); - padding-bottom: var(--padding-sm); - font-size: var(--font-size-sm); + gap: var(--gap-md); + padding: var(--padding-base); +} + +.route-logs-summary > div, +.route-log-details { + display: flex; + flex-direction: column; + gap: var(--gap-xs); + min-width: 0; +} + +.route-logs-summary strong, +.route-log-details strong { + color: var(--text-color); + font-size: var(--font-size-base); +} + +.route-logs-summary div span, +.route-log-details span { + color: var(--text-muted); + font-size: var(--font-size-base); + line-height: 1.4; +} + +.route-logs-download-all, +.route-log-download { + background: var(--main-fg); + border-radius: var(--border-radius-md); + color: var(--text-on-primary); + flex-shrink: 0; + font-size: var(--font-size-base); + font-weight: var(--font-weight-demi-bold); + padding: var(--padding-sm) var(--padding-base); + text-decoration: none; + transition: filter var(--transition-fast), transform var(--transition-fast); +} + +.route-logs-download-all span { + opacity: 0.75; +} + +.route-logs-download-all:hover, +.route-log-download:hover, +.route-logs-download-all:focus-visible, +.route-log-download:focus-visible { + filter: brightness(1.15); + outline: none; + transform: translateY(-1px); } .route-logs-list { + display: grid; + gap: var(--gap-sm); list-style: none; - margin: 0; + margin: var(--margin-base) 0 0; padding: 0; } .route-logs-list li { - display: flex; align-items: center; + border-bottom: var(--border-width-thin) solid var(--sidebar-border-color); + display: flex; justify-content: space-between; - gap: var(--padding-sm); - padding: var(--padding-sm) 0; - font-size: var(--font-size-sm); -} - -.route-logs a { - text-decoration: underline; + gap: var(--gap-md); + padding: var(--padding-sm) var(--padding-xs) var(--padding-base); } @media only screen and (max-width: 768px) and (orientation: portrait) { @@ -308,10 +427,19 @@ width: 100%; } - .route-logs-header, + .route-logs-toolbar, + .route-logs-content { + padding: var(--padding-base); + } + + .route-logs-summary, .route-logs-list li { flex-direction: column; - align-items: flex-start; - gap: 0.25rem; + align-items: stretch; + } + + .route-logs-download-all, + .route-log-download { + text-align: center; } } diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js index 29fc852a1..e19d545b0 100644 --- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js +++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js @@ -301,7 +301,6 @@ async function openOverlay(route) { - `; document.body.appendChild(overlay); @@ -315,7 +314,6 @@ async function openOverlay(route) { const vid = overlay.querySelector("video"); const downloadButton = overlay.querySelector(".action-download"); const logsButton = overlay.querySelector(".action-logs"); - const logsPanel = overlay.querySelector(".route-logs"); const formatBytes = bytes => { if (!bytes) return "0 MB"; @@ -323,39 +321,84 @@ async function openOverlay(route) { return mb >= 1000 ? `${(mb / 1000).toFixed(2)} GB` : `${mb.toFixed(1)} MB`; }; - let logsLoaded = false; + let logsData = null; logsButton.onclick = async () => { - if (logsLoaded) { - logsPanel.hidden = !logsPanel.hidden; - return; - } + const logsDialog = openDialog(` + `); + const logsContent = logsDialog.querySelector(".route-logs-content"); + const logsCloseButton = logsDialog.querySelector(".route-logs-close"); - logsPanel.hidden = false; - logsPanel.innerHTML = `

Looking for full logs...

`; - try { - const response = await fetch(`/api/routes/${route.name}/logs`); - const data = await response.json(); - if (!response.ok) { - logsPanel.innerHTML = `

${data.error || "Could not read logs."}

`; - return; - } + const closeLogsDialog = () => { + document.removeEventListener("keydown", handleLogsKeydown); + closeDialog(logsDialog); + logsButton.focus(); + }; + const handleLogsKeydown = event => { + if (event.key === "Escape") closeLogsDialog(); + }; + logsCloseButton.onclick = closeLogsDialog; + logsDialog.addEventListener("click", event => { + if (event.target === logsDialog) closeLogsDialog(); + }); + document.addEventListener("keydown", handleLogsKeydown); + logsCloseButton.focus(); - // sizes are shown up front so a metered connection is a deliberate choice - logsPanel.innerHTML = ` -
- ${data.segments.length} segment${data.segments.length === 1 ? "" : "s"} · ${formatBytes(data.totalBytes)} total - Download all (.tar) + const renderLogs = data => { + logsContent.innerHTML = ` +
+
+ ${data.segments.length} segment${data.segments.length === 1 ? "" : "s"} + ${formatBytes(data.totalBytes)} total download +
+ + Download all .tar +
`; - logsLoaded = true; + }; + + if (logsData) { + renderLogs(logsData); + return; + } + + try { + const response = await fetch(`/api/routes/${route.name}/logs`); + const data = await response.json(); + if (!response.ok) { + if (logsContent.isConnected) { + logsContent.innerHTML = `

${data.error || "Could not read logs."}

`; + } + return; + } + + // sizes are shown up front so a metered connection is a deliberate choice + logsData = data; + if (logsContent.isConnected) renderLogs(data); } catch (error) { - logsPanel.innerHTML = `

Could not reach the device: ${error.message}

`; + if (logsContent.isConnected) { + logsContent.innerHTML = `

Could not reach the device: ${error.message}

`; + } } };