From 088a683c62875e928aa7ae92346a53c6247bdef9 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Fri, 13 Feb 2026 01:38:51 +0100 Subject: [PATCH] [feat](trx-frontend-http): streamline main control layout Refine main control interactions and presentation in the HTTP frontend.\n\n- remove frequency and mode Set buttons\n- apply mode changes immediately on picker change\n- place Mode/Tune/Transmit-Power controls in one horizontal row\n- align control labels vertically across that row\n- move and enlarge MHz/kHz/Hz selector beside frequency input\n- keep Enter-to-set frequency behavior\n- switch signal measurement to elapsed-time averaging\n- enlarge header logo 2x\n\nCo-authored-by: OpenAI Codex Signed-off-by: Stanislaw Grams --- .../trx-frontend-http/assets/web/app.js | 92 +++++++++++++------ .../trx-frontend-http/assets/web/index.html | 22 +++-- .../trx-frontend-http/assets/web/style.css | 13 ++- 3 files changed, 87 insertions(+), 40 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 cc47099..e4a579c 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 @@ -19,7 +19,6 @@ const vfoPicker = document.getElementById("vfo-picker"); const signalBar = document.getElementById("signal-bar"); const signalValue = document.getElementById("signal-value"); const pttBtn = document.getElementById("ptt-btn"); -const modeBtn = document.getElementById("mode-apply"); const txLimitInput = document.getElementById("tx-limit"); const txLimitBtn = document.getElementById("tx-limit-btn"); const txLimitRow = document.getElementById("tx-limit-row"); @@ -41,7 +40,12 @@ let lastRendered = null; let rigName = "Rig"; let hintTimer = null; let sigMeasuring = false; -let sigSamples = []; +let sigLastSUnits = null; +let sigMeasureTimer = null; +let sigMeasureLastTickMs = 0; +let sigMeasureAccumMs = 0; +let sigMeasureWeighted = 0; +let sigMeasurePeak = null; let lastFreqHz = null; let jogStep = loadSetting("jogStep", 1000); let minFreqStepHz = 1; @@ -69,7 +73,6 @@ function showHint(msg, duration) { let supportedModes = []; let supportedBands = []; let freqDirty = false; -let modeDirty = false; let initialized = false; let lastEventAt = Date.now(); let es; @@ -228,7 +231,7 @@ function formatSignal(sUnits) { } function setDisabled(disabled) { - [freqEl, modeEl, modeBtn, pttBtn, powerBtn, txLimitInput, txLimitBtn, lockBtn].forEach((el) => { + [freqEl, modeEl, pttBtn, powerBtn, txLimitInput, txLimitBtn, lockBtn].forEach((el) => { if (el) el.disabled = disabled; }); } @@ -317,7 +320,7 @@ function render(update) { window.updateFt8RfDisplay(); } } - if (!modeDirty && update.status && update.status.mode) { + if (update.status && update.status.mode) { const mode = normalizeMode(update.status.mode); modeEl.value = mode ? mode.toUpperCase() : ""; } @@ -424,14 +427,12 @@ function render(update) { } if (update.status && update.status.rx && typeof update.status.rx.sig === "number") { const sUnits = dbmToSUnits(update.status.rx.sig); + sigLastSUnits = sUnits; const pct = sUnits <= 9 ? Math.max(0, Math.min(100, (sUnits / 9) * 100)) : 100; signalBar.style.width = `${pct}%`; signalValue.textContent = formatSignal(sUnits); - if (sigMeasuring) { - sigSamples.push(sUnits); - sigMeasureBtn.textContent = `Stop (${sigSamples.length})`; - } } else { + sigLastSUnits = null; signalBar.style.width = "0%"; signalValue.textContent = "--"; } @@ -756,14 +757,13 @@ jogStepEl.addEventListener("click", (e) => { } } -modeBtn.addEventListener("click", async () => { +async function applyModeFromPicker() { const mode = modeEl.value || ""; if (!mode) { showHint("Mode missing", 1500); return; } - modeDirty = false; - modeBtn.disabled = true; + modeEl.disabled = true; showHint("Setting mode…"); try { await postPath(`/set_mode?mode=${encodeURIComponent(mode)}`); @@ -772,13 +772,11 @@ modeBtn.addEventListener("click", async () => { showHint("Set mode failed", 2000); console.error(err); } finally { - modeBtn.disabled = false; + modeEl.disabled = false; } -}); +} -modeEl.addEventListener("input", () => { - modeDirty = true; -}); +modeEl.addEventListener("change", applyModeFromPicker); txLimitInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { @@ -997,27 +995,67 @@ const sigMeasureBtn = document.getElementById("sig-measure-btn"); const sigClearBtn = document.getElementById("sig-clear-btn"); const sigResult = document.getElementById("sig-result"); +function resetSignalMeasurementState() { + sigMeasureLastTickMs = 0; + sigMeasureAccumMs = 0; + sigMeasureWeighted = 0; + sigMeasurePeak = null; +} + +function updateSignalMeasurement(nowMs) { + if (!sigMeasuring) return; + if (sigMeasureLastTickMs === 0) { + sigMeasureLastTickMs = nowMs; + return; + } + const dt = Math.max(0, nowMs - sigMeasureLastTickMs); + sigMeasureLastTickMs = nowMs; + if (!Number.isFinite(sigLastSUnits)) return; + + sigMeasureAccumMs += dt; + sigMeasureWeighted += sigLastSUnits * dt; + if (sigMeasurePeak === null || sigLastSUnits > sigMeasurePeak) { + sigMeasurePeak = sigLastSUnits; + } +} + +function stopSignalMeasurement() { + if (sigMeasureTimer) { + clearInterval(sigMeasureTimer); + sigMeasureTimer = null; + } + sigMeasuring = false; + sigMeasureBtn.textContent = "Measure"; + sigMeasureBtn.style.borderColor = ""; + sigMeasureBtn.style.color = ""; +} + sigMeasureBtn.addEventListener("click", () => { if (!sigMeasuring) { - sigSamples = []; + resetSignalMeasurementState(); sigMeasuring = true; - sigMeasureBtn.textContent = "Stop (0)"; + sigMeasureBtn.textContent = "Stop (0.0s)"; sigMeasureBtn.style.borderColor = "#00d17f"; sigMeasureBtn.style.color = "#00d17f"; + sigMeasureTimer = setInterval(() => { + const now = Date.now(); + updateSignalMeasurement(now); + sigMeasureBtn.textContent = `Stop (${(sigMeasureAccumMs / 1000).toFixed(1)}s)`; + }, 200); } else { - sigMeasuring = false; - sigMeasureBtn.textContent = "Measure"; - sigMeasureBtn.style.borderColor = ""; - sigMeasureBtn.style.color = ""; - if (sigSamples.length > 0) { - const avg = sigSamples.reduce((a, b) => a + b, 0) / sigSamples.length; - const peak = Math.max(...sigSamples); - sigResult.textContent = `Avg ${formatSignal(avg)} / Peak ${formatSignal(peak)} (${sigSamples.length} samples)`; + updateSignalMeasurement(Date.now()); + stopSignalMeasurement(); + if (sigMeasureAccumMs > 0) { + const avg = sigMeasureWeighted / sigMeasureAccumMs; + const peak = sigMeasurePeak; + sigResult.textContent = `Avg ${formatSignal(avg)} / Peak ${formatSignal(peak)} (${(sigMeasureAccumMs / 1000).toFixed(1)}s)`; } } }); sigClearBtn.addEventListener("click", () => { + stopSignalMeasurement(); + resetSignalMeasurementState(); sigResult.textContent = ""; }); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 6f1a3d1..331a467 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -41,25 +41,27 @@ -
- -
-
-
- -
-
+
Mode
-
-
+
+
Tune
+
+ +
+
+
+ +
+
+
Transmit / Power
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 999017c..3ca88cc 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 @@ -22,10 +22,18 @@ input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; #freq { font-family: 'DSEG14 Classic', monospace; font-size: 2rem; padding: 0.5rem 0.6rem; letter-spacing: 0.05em; text-align: center; } .controls-row { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr auto 1fr; gap: 1rem; align-items: start; } +.controls-col { min-width: 0; } +.controls-col-center { justify-self: center; width: auto; } +.controls-row .label { + margin-bottom: 6px; + min-height: 1.2rem; + display: flex; + align-items: center; +} .btn-grid { display: grid; grid-template-columns: repeat(3, 1fr); @@ -37,7 +45,6 @@ input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; align-items: center; justify-content: center; gap: 0.5rem; - margin-top: 0.6rem; } .jog-wheel { width: 52px; @@ -137,7 +144,7 @@ button:disabled { opacity: 0.6; cursor: not-allowed; } small { color: var(--text-muted); } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.25rem; } .title { font-size: 1.4rem; font-weight: 700; display: inline-flex; align-items: center; gap: 0.35rem; } -.header-logo { height: 5em; width: auto; flex-shrink: 0; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); } +.header-logo { height: 10em; width: auto; flex-shrink: 0; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); } .subtitle { color: var(--text-muted); font-size: 0.95rem; } .subtitle a { color: var(--accent-green); text-decoration: none; } .subtitle a:hover { text-decoration: underline; }