From 518e10c36a796f925add0d09142103ab818e1b82 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Mon, 2 Mar 2026 20:36:22 +0100 Subject: [PATCH] [fix](trx-frontend): refine spectrum layout behavior Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 72 +++++++++++++++++-- .../trx-frontend-http/assets/web/style.css | 30 ++------ 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 5949b84..2112983 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -289,6 +289,7 @@ function applyCapabilities(caps) { if (centerFreqField) centerFreqField.style.display = "none"; stopSpectrumStreaming(); } + scheduleSpectrumLayout(); } } @@ -1653,6 +1654,8 @@ let serverLon = null; let initialMapZoom = 10; let spectrumCoverageMarginHz = 50_000; let spectrumUsableSpanRatio = 0.92; +const DEFAULT_SPECTRUM_PLOT_HEIGHT_PX = 160; +let spectrumLayoutPending = false; function updateFooterBuildInfo() { const serverEl = document.getElementById("footer-server-build"); @@ -1662,6 +1665,56 @@ function updateFooterBuildInfo() { serverEl.textContent = `trx-server v${ver} ${build}`; } +function scheduleSpectrumLayout() { + if (spectrumLayoutPending) return; + spectrumLayoutPending = true; + requestAnimationFrame(() => { + spectrumLayoutPending = false; + updateSpectrumAutoHeight(); + }); +} + +function updateSpectrumAutoHeight() { + const root = document.documentElement; + const tabMainEl = document.getElementById("tab-main"); + const contentEl = document.getElementById("content"); + const spectrumPanelEl = document.getElementById("spectrum-panel"); + const spectrumCanvasEl = document.getElementById("spectrum-canvas"); + if (!root || !tabMainEl || !contentEl || !spectrumPanelEl || !spectrumCanvasEl) return; + + const mainVisible = getComputedStyle(tabMainEl).display !== "none"; + const contentVisible = getComputedStyle(contentEl).display !== "none"; + const spectrumVisible = getComputedStyle(spectrumPanelEl).display !== "none"; + const currentHeight = Math.max( + DEFAULT_SPECTRUM_PLOT_HEIGHT_PX, + Math.round(spectrumCanvasEl.clientHeight || DEFAULT_SPECTRUM_PLOT_HEIGHT_PX), + ); + + if (!mainVisible || !contentVisible || !spectrumVisible) { + root.style.setProperty("--spectrum-plot-height", `${DEFAULT_SPECTRUM_PLOT_HEIGHT_PX}px`); + if (currentHeight !== DEFAULT_SPECTRUM_PLOT_HEIGHT_PX && lastSpectrumData) { + scheduleSpectrumDraw(); + scheduleOverviewDraw(); + } + return; + } + + const tabBottom = tabMainEl.getBoundingClientRect().bottom; + const contentBottom = contentEl.getBoundingClientRect().bottom; + const slackPx = Math.floor(tabBottom - contentBottom); + const nextHeight = Math.max( + DEFAULT_SPECTRUM_PLOT_HEIGHT_PX, + currentHeight + slackPx - 2, + ); + if (Math.abs(nextHeight - currentHeight) < 2) return; + + root.style.setProperty("--spectrum-plot-height", `${nextHeight}px`); + if (lastSpectrumData) { + scheduleSpectrumDraw(); + scheduleOverviewDraw(); + } +} + function updateTitle() { const titleEl = document.getElementById("rig-title"); if (titleEl) { @@ -1716,6 +1769,7 @@ function render(update) { ) { spectrumUsableSpanRatio = Math.max(0.01, Math.min(1.0, Number(update.spectrum_usable_span_ratio))); } + scheduleSpectrumLayout(); updateTitle(); updateFooterBuildInfo(); @@ -2719,12 +2773,14 @@ document.querySelector(".tab-bar").addEventListener("click", (e) => { btn.classList.add("active"); document.querySelectorAll(".tab-panel").forEach((p) => p.style.display = "none"); document.getElementById(`tab-${btn.dataset.tab}`).style.display = ""; + scheduleSpectrumLayout(); if (btn.dataset.tab === "map") { initAprsMap(); sizeAprsMapToViewport(); if (aprsMap) setTimeout(() => aprsMap.invalidateSize(), 50); } }); +window.addEventListener("resize", () => { scheduleSpectrumLayout(); }); // --- Auth startup sequence --- async function initializeApp() { @@ -4878,16 +4934,24 @@ function createBookmarkChip(bm, colorMap) { function updateSideBookmarkStack(container, bookmarks, colorMap) { if (!container) return; - container.innerHTML = ""; + const nextKey = Array.isArray(bookmarks) ? bookmarks.map((bm) => bm.id).join(",") : ""; if (!Array.isArray(bookmarks) || bookmarks.length === 0) { + if (container.dataset.bmKey) { + container.innerHTML = ""; + container.dataset.bmKey = ""; + } container.classList.remove("bm-side-visible"); return; } - container.classList.add("bm-side-visible"); - for (const bm of bookmarks) { - container.appendChild(createBookmarkChip(bm, colorMap)); + if (container.dataset.bmKey !== nextKey) { + container.dataset.bmKey = nextKey; + container.innerHTML = ""; + for (const bm of bookmarks) { + container.appendChild(createBookmarkChip(bm, colorMap)); + } } + container.classList.add("bm-side-visible"); } function updateBookmarkAxis(range) { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index 560d44e..f6036a3 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -26,6 +26,7 @@ --filter-border: #385577; --wavelength-fg: #8da3be; --spectrum-bg: #0a0f18; + --overview-plot-height: 160px; --spectrum-plot-height: 160px; --jog-wheel-size: 83.2px; --header-waterfall-overlap: 0rem; @@ -525,10 +526,8 @@ small { color: var(--text-muted); } position: relative; display: flex; flex-direction: column; - flex: 1 1 auto; - min-height: 0; gap: 0; - margin-bottom: 0; + margin-bottom: 0.9rem; } #signal-overlay-canvas { position: absolute; @@ -833,7 +832,7 @@ small { color: var(--text-muted); } } #overview-canvas { width: 100%; - height: var(--spectrum-plot-height); + height: var(--overview-plot-height); display: block; } .header-left { @@ -914,12 +913,7 @@ small { color: var(--text-muted); } .meter-bar { flex: 1 1 auto; height: 12px; border-radius: 999px; background: var(--btn-bg); border: 1px solid var(--border-light); overflow: hidden; } .meter-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent-green), var(--accent-yellow), var(--accent-red)); transition: width 150ms ease; } .meter-value { font-size: 0.95rem; color: var(--text-heading); min-width: 64px; text-align: right; } -#tab-main { - display: flex; - flex-direction: column; - min-height: 0; -} -#content { display: flex; flex: 1 1 auto; flex-direction: column; gap: 1.1rem; min-height: 0; overflow: visible; } +#content { display: flex; flex-direction: column; gap: 1.1rem; min-height: 0; overflow: visible; } .tab-panel { flex: 1 1 auto; min-height: 0; overflow: visible; } .tab-bar { display: flex; @@ -1514,26 +1508,16 @@ button:focus-visible, input:focus-visible, select:focus-visible { /* ── Spectrum display ─────────────────────────────────────────────────── */ #spectrum-panel { margin-bottom: 0; - flex: 1 1 auto; - min-height: 0; - display: flex; - flex-direction: column; } .spectrum-wrap { position: relative; - display: flex; - flex: 1 1 auto; - flex-direction: column; - min-height: 0; width: 100%; user-select: none; } #spectrum-canvas { display: block; width: 100%; - flex: 1 1 auto; - min-height: var(--spectrum-plot-height); - height: auto; + height: var(--spectrum-plot-height); background: var(--spectrum-bg); border-radius: 6px 6px 0 0; cursor: crosshair; @@ -1541,7 +1525,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { } .spectrum-edge-shift { position: absolute; - top: calc((100% - 18px) / 2); + top: calc(var(--spectrum-plot-height) / 2); transform: translateY(-50%); z-index: 8; width: 1.7rem; @@ -1646,7 +1630,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { } .spectrum-bookmark-side { position: absolute; - top: calc((100% - 18px) / 2); + top: calc(var(--spectrum-plot-height) / 2); transform: translateY(-50%); z-index: 7; width: 7.25rem;