[fix](trx-frontend): refine spectrum layout behavior

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-02 20:36:22 +01:00
parent 89e44f3fa7
commit 518e10c36a
2 changed files with 75 additions and 27 deletions
@@ -289,6 +289,7 @@ function applyCapabilities(caps) {
if (centerFreqField) centerFreqField.style.display = "none"; if (centerFreqField) centerFreqField.style.display = "none";
stopSpectrumStreaming(); stopSpectrumStreaming();
} }
scheduleSpectrumLayout();
} }
} }
@@ -1653,6 +1654,8 @@ let serverLon = null;
let initialMapZoom = 10; let initialMapZoom = 10;
let spectrumCoverageMarginHz = 50_000; let spectrumCoverageMarginHz = 50_000;
let spectrumUsableSpanRatio = 0.92; let spectrumUsableSpanRatio = 0.92;
const DEFAULT_SPECTRUM_PLOT_HEIGHT_PX = 160;
let spectrumLayoutPending = false;
function updateFooterBuildInfo() { function updateFooterBuildInfo() {
const serverEl = document.getElementById("footer-server-build"); const serverEl = document.getElementById("footer-server-build");
@@ -1662,6 +1665,56 @@ function updateFooterBuildInfo() {
serverEl.textContent = `trx-server v${ver} ${build}`; 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() { function updateTitle() {
const titleEl = document.getElementById("rig-title"); const titleEl = document.getElementById("rig-title");
if (titleEl) { if (titleEl) {
@@ -1716,6 +1769,7 @@ function render(update) {
) { ) {
spectrumUsableSpanRatio = Math.max(0.01, Math.min(1.0, Number(update.spectrum_usable_span_ratio))); spectrumUsableSpanRatio = Math.max(0.01, Math.min(1.0, Number(update.spectrum_usable_span_ratio)));
} }
scheduleSpectrumLayout();
updateTitle(); updateTitle();
updateFooterBuildInfo(); updateFooterBuildInfo();
@@ -2719,12 +2773,14 @@ document.querySelector(".tab-bar").addEventListener("click", (e) => {
btn.classList.add("active"); btn.classList.add("active");
document.querySelectorAll(".tab-panel").forEach((p) => p.style.display = "none"); document.querySelectorAll(".tab-panel").forEach((p) => p.style.display = "none");
document.getElementById(`tab-${btn.dataset.tab}`).style.display = ""; document.getElementById(`tab-${btn.dataset.tab}`).style.display = "";
scheduleSpectrumLayout();
if (btn.dataset.tab === "map") { if (btn.dataset.tab === "map") {
initAprsMap(); initAprsMap();
sizeAprsMapToViewport(); sizeAprsMapToViewport();
if (aprsMap) setTimeout(() => aprsMap.invalidateSize(), 50); if (aprsMap) setTimeout(() => aprsMap.invalidateSize(), 50);
} }
}); });
window.addEventListener("resize", () => { scheduleSpectrumLayout(); });
// --- Auth startup sequence --- // --- Auth startup sequence ---
async function initializeApp() { async function initializeApp() {
@@ -4878,16 +4934,24 @@ function createBookmarkChip(bm, colorMap) {
function updateSideBookmarkStack(container, bookmarks, colorMap) { function updateSideBookmarkStack(container, bookmarks, colorMap) {
if (!container) return; if (!container) return;
container.innerHTML = ""; const nextKey = Array.isArray(bookmarks) ? bookmarks.map((bm) => bm.id).join(",") : "";
if (!Array.isArray(bookmarks) || bookmarks.length === 0) { if (!Array.isArray(bookmarks) || bookmarks.length === 0) {
if (container.dataset.bmKey) {
container.innerHTML = "";
container.dataset.bmKey = "";
}
container.classList.remove("bm-side-visible"); container.classList.remove("bm-side-visible");
return; return;
} }
container.classList.add("bm-side-visible"); if (container.dataset.bmKey !== nextKey) {
for (const bm of bookmarks) { container.dataset.bmKey = nextKey;
container.appendChild(createBookmarkChip(bm, colorMap)); container.innerHTML = "";
for (const bm of bookmarks) {
container.appendChild(createBookmarkChip(bm, colorMap));
}
} }
container.classList.add("bm-side-visible");
} }
function updateBookmarkAxis(range) { function updateBookmarkAxis(range) {
@@ -26,6 +26,7 @@
--filter-border: #385577; --filter-border: #385577;
--wavelength-fg: #8da3be; --wavelength-fg: #8da3be;
--spectrum-bg: #0a0f18; --spectrum-bg: #0a0f18;
--overview-plot-height: 160px;
--spectrum-plot-height: 160px; --spectrum-plot-height: 160px;
--jog-wheel-size: 83.2px; --jog-wheel-size: 83.2px;
--header-waterfall-overlap: 0rem; --header-waterfall-overlap: 0rem;
@@ -525,10 +526,8 @@ small { color: var(--text-muted); }
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 auto;
min-height: 0;
gap: 0; gap: 0;
margin-bottom: 0; margin-bottom: 0.9rem;
} }
#signal-overlay-canvas { #signal-overlay-canvas {
position: absolute; position: absolute;
@@ -833,7 +832,7 @@ small { color: var(--text-muted); }
} }
#overview-canvas { #overview-canvas {
width: 100%; width: 100%;
height: var(--spectrum-plot-height); height: var(--overview-plot-height);
display: block; display: block;
} }
.header-left { .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-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-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; } .meter-value { font-size: 0.95rem; color: var(--text-heading); min-width: 64px; text-align: right; }
#tab-main { #content { display: flex; flex-direction: column; gap: 1.1rem; min-height: 0; overflow: visible; }
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; }
.tab-panel { flex: 1 1 auto; min-height: 0; overflow: visible; } .tab-panel { flex: 1 1 auto; min-height: 0; overflow: visible; }
.tab-bar { .tab-bar {
display: flex; display: flex;
@@ -1514,26 +1508,16 @@ button:focus-visible, input:focus-visible, select:focus-visible {
/* ── Spectrum display ─────────────────────────────────────────────────── */ /* ── Spectrum display ─────────────────────────────────────────────────── */
#spectrum-panel { #spectrum-panel {
margin-bottom: 0; margin-bottom: 0;
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
} }
.spectrum-wrap { .spectrum-wrap {
position: relative; position: relative;
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
width: 100%; width: 100%;
user-select: none; user-select: none;
} }
#spectrum-canvas { #spectrum-canvas {
display: block; display: block;
width: 100%; width: 100%;
flex: 1 1 auto; height: var(--spectrum-plot-height);
min-height: var(--spectrum-plot-height);
height: auto;
background: var(--spectrum-bg); background: var(--spectrum-bg);
border-radius: 6px 6px 0 0; border-radius: 6px 6px 0 0;
cursor: crosshair; cursor: crosshair;
@@ -1541,7 +1525,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
} }
.spectrum-edge-shift { .spectrum-edge-shift {
position: absolute; position: absolute;
top: calc((100% - 18px) / 2); top: calc(var(--spectrum-plot-height) / 2);
transform: translateY(-50%); transform: translateY(-50%);
z-index: 8; z-index: 8;
width: 1.7rem; width: 1.7rem;
@@ -1646,7 +1630,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
} }
.spectrum-bookmark-side { .spectrum-bookmark-side {
position: absolute; position: absolute;
top: calc((100% - 18px) / 2); top: calc(var(--spectrum-plot-height) / 2);
transform: translateY(-50%); transform: translateY(-50%);
z-index: 7; z-index: 7;
width: 7.25rem; width: 7.25rem;