+
Waiting for server decode
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js
index 142c199..40d7179 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js
@@ -2,6 +2,7 @@
const aisStatus = document.getElementById("ais-status");
const aisMessagesEl = document.getElementById("ais-messages");
const aisFilterInput = document.getElementById("ais-filter");
+const aisPauseBtn = document.getElementById("ais-pause-btn");
const aisClearBtn = document.getElementById("ais-clear-btn");
const aisBarOverlay = document.getElementById("ais-bar-overlay");
const aisChannelSummaryEl = document.getElementById("ais-channel-summary");
@@ -13,6 +14,8 @@ const AIS_DEFAULT_A_HZ = 161_975_000;
const AIS_CHANNEL_SPACING_HZ = 50_000;
let aisFilterText = "";
let aisMessageHistory = [];
+let aisPaused = false;
+let aisBufferedWhilePaused = 0;
function formatAisMhz(freqHz) {
return `${(freqHz / 1_000_000).toFixed(3)} MHz`;
@@ -132,7 +135,11 @@ function updateAisSummary() {
const vessels = aisLatestByVessel(aisMessageHistory);
if (aisVesselCountEl) {
const count = vessels.length;
- aisVesselCountEl.textContent = `${count} vessel${count === 1 ? "" : "s"}`;
+ let text = `${count} vessel${count === 1 ? "" : "s"}`;
+ if (aisPaused && aisBufferedWhilePaused > 0) {
+ text += ` · ${aisBufferedWhilePaused} buffered`;
+ }
+ aisVesselCountEl.textContent = text;
}
if (aisLatestSeenEl) {
@@ -144,6 +151,10 @@ function updateAisSummary() {
aisLatestSeenEl.textContent = `${channel.label} ${aisAgeText(latest._tsMs)}`;
}
}
+ if (aisPauseBtn) {
+ aisPauseBtn.textContent = aisPaused ? "Resume" : "Pause";
+ aisPauseBtn.classList.toggle("active", aisPaused);
+ }
}
function renderAisRow(msg) {
@@ -261,10 +272,24 @@ window.clearAisBar = function() {
window.resetAisHistoryView = function() {
if (aisMessagesEl) aisMessagesEl.innerHTML = "";
aisMessageHistory = [];
+ aisBufferedWhilePaused = 0;
updateAisBar();
+ renderAisHistory();
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ais");
};
+function renderAisHistory() {
+ if (!aisMessagesEl || aisPaused) {
+ updateAisSummary();
+ return;
+ }
+ aisMessagesEl.innerHTML = "";
+ for (let i = 0; i < aisMessageHistory.length; i += 1) {
+ aisMessagesEl.appendChild(renderAisRow(aisMessageHistory[i]));
+ }
+ updateAisSummary();
+}
+
function addAisMessage(msg) {
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
msg._tsMs = tsMs;
@@ -278,12 +303,11 @@ function addAisMessage(msg) {
if (aisMessageHistory.length > AIS_MAX_MESSAGES) aisMessageHistory.length = AIS_MAX_MESSAGES;
updateAisBar();
- if (aisMessagesEl) {
- const row = renderAisRow(msg);
- aisMessagesEl.prepend(row);
- while (aisMessagesEl.children.length > AIS_MAX_MESSAGES) {
- aisMessagesEl.removeChild(aisMessagesEl.lastChild);
- }
+ if (aisPaused) {
+ aisBufferedWhilePaused += 1;
+ updateAisSummary();
+ } else {
+ renderAisHistory();
}
if (msg.lat != null && msg.lon != null && window.aisMapAddVessel) {
@@ -302,15 +326,27 @@ if (aisClearBtn) {
});
}
+if (aisPauseBtn) {
+ aisPauseBtn.addEventListener("click", () => {
+ aisPaused = !aisPaused;
+ if (!aisPaused) {
+ aisBufferedWhilePaused = 0;
+ renderAisHistory();
+ } else {
+ updateAisSummary();
+ }
+ });
+}
+
if (aisFilterInput) {
aisFilterInput.addEventListener("input", () => {
aisFilterText = aisFilterInput.value.trim().toUpperCase();
- applyAisFilterToAll();
+ renderAisHistory();
});
}
window.onServerAis = function(msg) {
- if (aisStatus) aisStatus.textContent = "Receiving";
+ if (aisStatus) aisStatus.textContent = aisPaused ? "Paused" : "Receiving";
addAisMessage({
channel: msg.channel,
message_type: msg.message_type,
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js
index 47bac8b..b012d15 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/cw.js
@@ -1,6 +1,7 @@
// --- CW (Morse) Decoder Plugin (server-side decode) ---
const cwStatusEl = document.getElementById("cw-status");
const cwOutputEl = document.getElementById("cw-output");
+const cwPauseBtn = document.getElementById("cw-pause-btn");
const cwAutoInput = document.getElementById("cw-auto");
const cwWpmInput = document.getElementById("cw-wpm");
const cwToneInput = document.getElementById("cw-tone");
@@ -15,6 +16,8 @@ const CW_WPM_MIN = 5;
const CW_WPM_MAX = 40;
let cwLastAppendTime = 0;
let cwTonePickerRaf = null;
+let cwPaused = false;
+let cwBufferedWhilePaused = 0;
function applyCwAutoUi(enabled) {
if (cwAutoInput) cwAutoInput.checked = enabled;
@@ -242,9 +245,17 @@ if (cwToneCanvas) {
window.resetCwHistoryView = function() {
if (cwOutputEl) cwOutputEl.innerHTML = "";
cwLastAppendTime = 0;
+ cwBufferedWhilePaused = 0;
+ updateCwPauseUi();
drawCwTonePicker();
};
+function updateCwPauseUi() {
+ if (!cwPauseBtn) return;
+ cwPauseBtn.textContent = cwPaused ? "Resume" : "Pause";
+ cwPauseBtn.classList.toggle("active", cwPaused);
+}
+
document.getElementById("cw-clear-btn").addEventListener("click", async () => {
try {
await postPath("/clear_cw_decode");
@@ -256,8 +267,8 @@ document.getElementById("cw-clear-btn").addEventListener("click", async () => {
// --- Server-side CW decode handler ---
window.onServerCw = function(evt) {
- if (cwStatusEl) cwStatusEl.textContent = "Receiving";
- if (evt.text && cwOutputEl) {
+ if (cwStatusEl) cwStatusEl.textContent = cwPaused ? "Paused" : "Receiving";
+ if (evt.text && cwOutputEl && !cwPaused) {
// Append decoded text to output
const now = Date.now();
if (!cwOutputEl.lastElementChild || now - cwLastAppendTime > 10000 || evt.text === "\n") {
@@ -278,6 +289,9 @@ window.onServerCw = function(evt) {
if (cwSignalIndicator) {
cwSignalIndicator.className = evt.signal_on ? "cw-signal-on" : "cw-signal-off";
}
+ if (cwPaused && evt.text) {
+ cwBufferedWhilePaused += 1;
+ }
if (!cwAutoInput || cwAutoInput.checked) {
if (cwWpmInput && Number.isFinite(Number(evt.wpm))) {
cwWpmInput.value = clampCwWpm(evt.wpm);
@@ -293,10 +307,21 @@ window.onServerCw = function(evt) {
});
};
+if (cwPauseBtn) {
+ cwPauseBtn.addEventListener("click", () => {
+ cwPaused = !cwPaused;
+ if (!cwPaused) {
+ cwBufferedWhilePaused = 0;
+ }
+ updateCwPauseUi();
+ });
+}
+
window.refreshCwTonePicker = drawCwTonePicker;
window.addEventListener("resize", () => {
if (ensureCwToneCanvasResolution()) drawCwTonePicker();
});
applyCwAutoUi(!!cwAutoInput?.checked);
+updateCwPauseUi();
ensureCwToneCanvasResolution();
drawCwTonePicker();
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js
index 3f4223b..d7f14de 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js
@@ -3,12 +3,15 @@ const ft8Status = document.getElementById("ft8-status");
const ft8PeriodEl = document.getElementById("ft8-period");
const ft8MessagesEl = document.getElementById("ft8-messages");
const ft8FilterInput = document.getElementById("ft8-filter");
+const ft8PauseBtn = document.getElementById("ft8-pause-btn");
const ft8BarOverlay = document.getElementById("ft8-bar-overlay");
const FT8_MAX_MESSAGES = 200;
const FT8_BAR_WINDOW_MS = 15 * 60 * 1000;
const FT8_PERIOD_SECONDS = 15;
let ft8FilterText = "";
let ft8MessageHistory = [];
+let ft8Paused = false;
+let ft8BufferedWhilePaused = 0;
function normalizeFt8DisplayFreqHz(freqHz) {
const rawHz = Number(freqHz);
@@ -51,14 +54,34 @@ function renderFt8Row(msg) {
return row;
}
+function updateFt8PauseUi() {
+ if (!ft8PauseBtn) return;
+ ft8PauseBtn.textContent = ft8Paused ? "Resume" : "Pause";
+ ft8PauseBtn.classList.toggle("active", ft8Paused);
+}
+
+function renderFt8History() {
+ if (!ft8MessagesEl || ft8Paused) {
+ updateFt8PauseUi();
+ return;
+ }
+ ft8MessagesEl.innerHTML = "";
+ for (let i = 0; i < ft8MessageHistory.length; i += 1) {
+ ft8MessagesEl.appendChild(renderFt8Row(ft8MessageHistory[i]));
+ }
+ updateFt8PauseUi();
+}
+
function addFt8Message(msg) {
ft8MessageHistory.unshift(msg);
if (ft8MessageHistory.length > FT8_MAX_MESSAGES) ft8MessageHistory.length = FT8_MAX_MESSAGES;
updateFt8Bar();
- ft8MessagesEl.prepend(renderFt8Row(msg));
- while (ft8MessagesEl.children.length > FT8_MAX_MESSAGES) {
- ft8MessagesEl.removeChild(ft8MessagesEl.lastChild);
+ if (ft8Paused) {
+ ft8BufferedWhilePaused += 1;
+ updateFt8PauseUi();
+ return;
}
+ renderFt8History();
}
function ft8BarRfText(msg) {
@@ -202,14 +225,28 @@ window.updateFt8RfDisplay = function() {
window.resetFt8HistoryView = function() {
ft8MessagesEl.innerHTML = "";
ft8MessageHistory = [];
+ ft8BufferedWhilePaused = 0;
updateFt8Bar();
+ renderFt8History();
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ft8");
};
if (ft8FilterInput) {
ft8FilterInput.addEventListener("input", () => {
ft8FilterText = ft8FilterInput.value.trim().toUpperCase();
- applyFt8FilterToAll();
+ renderFt8History();
+ });
+}
+
+if (ft8PauseBtn) {
+ ft8PauseBtn.addEventListener("click", () => {
+ ft8Paused = !ft8Paused;
+ if (!ft8Paused) {
+ ft8BufferedWhilePaused = 0;
+ renderFt8History();
+ } else {
+ updateFt8PauseUi();
+ }
});
}
@@ -228,7 +265,7 @@ document.getElementById("ft8-clear-btn").addEventListener("click", async () => {
// --- Server-side FT8 decode handler ---
window.onServerFt8 = function(msg) {
- ft8Status.textContent = "Receiving";
+ ft8Status.textContent = ft8Paused ? "Paused" : "Receiving";
const raw = (msg.message || "").toString();
const grids = extractAllGrids(raw);
const station = extractLikelyCallsign(raw);
@@ -251,3 +288,5 @@ window.onServerFt8 = function(msg) {
message: msg.message,
});
};
+
+updateFt8PauseUi();
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js
index 2f64b31..ff86af0 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js
@@ -2,6 +2,7 @@
const vdesStatus = document.getElementById("vdes-status");
const vdesMessagesEl = document.getElementById("vdes-messages");
const vdesFilterInput = document.getElementById("vdes-filter");
+const vdesPauseBtn = document.getElementById("vdes-pause-btn");
const vdesClearBtn = document.getElementById("vdes-clear-btn");
const vdesBarOverlay = document.getElementById("vdes-bar-overlay");
const vdesChannelSummaryEl = document.getElementById("vdes-channel-summary");
@@ -11,6 +12,8 @@ const VDES_MAX_MESSAGES = 200;
const VDES_BAR_WINDOW_MS = 15 * 60 * 1000;
let vdesFilterText = "";
let vdesMessageHistory = [];
+let vdesPaused = false;
+let vdesBufferedWhilePaused = 0;
function currentVdesCenterText() {
const raw = (document.getElementById("freq")?.value || "").replace(/[^\d]/g, "");
@@ -46,12 +49,20 @@ function updateVdesSummary() {
}
if (vdesFrameCountEl) {
const count = vdesMessageHistory.length;
- vdesFrameCountEl.textContent = `${count} burst${count === 1 ? "" : "s"}`;
+ let text = `${count} burst${count === 1 ? "" : "s"}`;
+ if (vdesPaused && vdesBufferedWhilePaused > 0) {
+ text += ` · ${vdesBufferedWhilePaused} buffered`;
+ }
+ vdesFrameCountEl.textContent = text;
}
if (vdesLatestSeenEl) {
const latest = vdesMessageHistory[0];
vdesLatestSeenEl.textContent = latest ? vdesAgeText(latest._tsMs) : "No traffic yet";
}
+ if (vdesPauseBtn) {
+ vdesPauseBtn.textContent = vdesPaused ? "Resume" : "Pause";
+ vdesPauseBtn.classList.toggle("active", vdesPaused);
+ }
}
function applyVdesFilterToRow(row) {
@@ -195,9 +206,23 @@ window.clearVdesBar = function() {
window.resetVdesHistoryView = function() {
if (vdesMessagesEl) vdesMessagesEl.innerHTML = "";
vdesMessageHistory = [];
+ vdesBufferedWhilePaused = 0;
updateVdesBar();
+ renderVdesHistory();
};
+function renderVdesHistory() {
+ if (!vdesMessagesEl || vdesPaused) {
+ updateVdesSummary();
+ return;
+ }
+ vdesMessagesEl.innerHTML = "";
+ for (let i = 0; i < vdesMessageHistory.length; i += 1) {
+ vdesMessagesEl.appendChild(renderVdesRow(vdesMessageHistory[i]));
+ }
+ updateVdesSummary();
+}
+
function addVdesMessage(msg) {
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
msg._tsMs = tsMs;
@@ -211,12 +236,11 @@ function addVdesMessage(msg) {
if (vdesMessageHistory.length > VDES_MAX_MESSAGES) vdesMessageHistory.length = VDES_MAX_MESSAGES;
updateVdesBar();
- if (vdesMessagesEl) {
- const row = renderVdesRow(msg);
- vdesMessagesEl.prepend(row);
- while (vdesMessagesEl.children.length > VDES_MAX_MESSAGES) {
- vdesMessagesEl.removeChild(vdesMessagesEl.lastChild);
- }
+ if (vdesPaused) {
+ vdesBufferedWhilePaused += 1;
+ updateVdesSummary();
+ } else {
+ renderVdesHistory();
}
}
@@ -231,15 +255,27 @@ if (vdesClearBtn) {
});
}
+if (vdesPauseBtn) {
+ vdesPauseBtn.addEventListener("click", () => {
+ vdesPaused = !vdesPaused;
+ if (!vdesPaused) {
+ vdesBufferedWhilePaused = 0;
+ renderVdesHistory();
+ } else {
+ updateVdesSummary();
+ }
+ });
+}
+
if (vdesFilterInput) {
vdesFilterInput.addEventListener("input", () => {
vdesFilterText = vdesFilterInput.value.trim().toUpperCase();
- applyVdesFilterToAll();
+ renderVdesHistory();
});
}
window.onServerVdes = function(msg) {
- if (vdesStatus) vdesStatus.textContent = "Receiving";
+ if (vdesStatus) vdesStatus.textContent = vdesPaused ? "Paused" : "Receiving";
addVdesMessage({
message_type: msg.message_type,
bit_len: msg.bit_len,
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js
index a18a691..1dcbfb0 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js
@@ -3,9 +3,13 @@ const wsprStatus = document.getElementById("wspr-status");
const wsprPeriodEl = document.getElementById("wspr-period");
const wsprMessagesEl = document.getElementById("wspr-messages");
const wsprFilterInput = document.getElementById("wspr-filter");
+const wsprPauseBtn = document.getElementById("wspr-pause-btn");
const WSPR_MAX_MESSAGES = 200;
const WSPR_PERIOD_SECONDS = 120;
let wsprFilterText = "";
+let wsprMessageHistory = [];
+let wsprPaused = false;
+let wsprBufferedWhilePaused = 0;
function fmtWsprTime(tsMs) {
if (!tsMs) return "--:--:--";
@@ -39,11 +43,33 @@ function renderWsprRow(msg) {
return row;
}
-function addWsprMessage(msg) {
- wsprMessagesEl.prepend(renderWsprRow(msg));
- while (wsprMessagesEl.children.length > WSPR_MAX_MESSAGES) {
- wsprMessagesEl.removeChild(wsprMessagesEl.lastChild);
+function updateWsprPauseUi() {
+ if (!wsprPauseBtn) return;
+ wsprPauseBtn.textContent = wsprPaused ? "Resume" : "Pause";
+ wsprPauseBtn.classList.toggle("active", wsprPaused);
+}
+
+function renderWsprHistory() {
+ if (!wsprMessagesEl || wsprPaused) {
+ updateWsprPauseUi();
+ return;
}
+ wsprMessagesEl.innerHTML = "";
+ for (let i = 0; i < wsprMessageHistory.length; i += 1) {
+ wsprMessagesEl.appendChild(renderWsprRow(wsprMessageHistory[i]));
+ }
+ updateWsprPauseUi();
+}
+
+function addWsprMessage(msg) {
+ wsprMessageHistory.unshift(msg);
+ if (wsprMessageHistory.length > WSPR_MAX_MESSAGES) wsprMessageHistory.length = WSPR_MAX_MESSAGES;
+ if (wsprPaused) {
+ wsprBufferedWhilePaused += 1;
+ updateWsprPauseUi();
+ return;
+ }
+ renderWsprHistory();
}
function escapeWsprHtml(input) {
@@ -96,13 +122,28 @@ function applyWsprFilterToAll() {
window.resetWsprHistoryView = function() {
wsprMessagesEl.innerHTML = "";
+ wsprMessageHistory = [];
+ wsprBufferedWhilePaused = 0;
+ renderWsprHistory();
if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr");
};
if (wsprFilterInput) {
wsprFilterInput.addEventListener("input", () => {
wsprFilterText = wsprFilterInput.value.trim().toUpperCase();
- applyWsprFilterToAll();
+ renderWsprHistory();
+ });
+}
+
+if (wsprPauseBtn) {
+ wsprPauseBtn.addEventListener("click", () => {
+ wsprPaused = !wsprPaused;
+ if (!wsprPaused) {
+ wsprBufferedWhilePaused = 0;
+ renderWsprHistory();
+ } else {
+ updateWsprPauseUi();
+ }
});
}
@@ -120,7 +161,7 @@ document.getElementById("wspr-clear-btn").addEventListener("click", async () =>
});
window.onServerWspr = function(msg) {
- wsprStatus.textContent = "Receiving";
+ wsprStatus.textContent = wsprPaused ? "Paused" : "Receiving";
const raw = (msg.message || "").toString();
const grids = extractAllGrids(raw);
const station = extractLikelyCallsign(raw);
@@ -143,3 +184,5 @@ window.onServerWspr = function(msg) {
message: raw,
});
};
+
+updateWsprPauseUi();
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 57463ef..2eaca42 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
@@ -1142,6 +1142,13 @@ small { color: var(--text-muted); }
border-radius: 0;
}
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
+.aprs-controls > button.active,
+.ft8-controls > button.active,
+.cw-controls > button.active {
+ border-color: color-mix(in srgb, var(--accent-green) 58%, var(--border-light));
+ color: var(--accent-green);
+ background: color-mix(in srgb, var(--accent-green) 10%, var(--btn-bg));
+}
.aprs-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -1895,7 +1902,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
min-width: 0;
}
#tab-main,
- #tab-plugins,
+ #tab-decoders,
#tab-map,
#tab-about {
scroll-margin-top: 0.75rem;