[fix](trx-frontend): add decoder pause controls and rename tab
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -4978,7 +4978,7 @@ window.ft8MapAddLocator = function(message, grids, type = "ft8", station = null,
|
|||||||
applyMapFilter();
|
applyMapFilter();
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Sub-tab navigation (Plugins tab) ---
|
// --- Sub-tab navigation (Decoders tab) ---
|
||||||
document.querySelectorAll(".sub-tab-bar").forEach((bar) => {
|
document.querySelectorAll(".sub-tab-bar").forEach((bar) => {
|
||||||
bar.addEventListener("click", (e) => {
|
bar.addEventListener("click", (e) => {
|
||||||
const btn = e.target.closest(".sub-tab[data-subtab]");
|
const btn = e.target.closest(".sub-tab[data-subtab]");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="tab-bar-nav">
|
<div class="tab-bar-nav">
|
||||||
<button class="tab active" data-tab="main">Main</button>
|
<button class="tab active" data-tab="main">Main</button>
|
||||||
<button class="tab" data-tab="bookmarks">Bookmarks</button>
|
<button class="tab" data-tab="bookmarks">Bookmarks</button>
|
||||||
<button class="tab" data-tab="plugins">Plugins</button>
|
<button class="tab" data-tab="decoders">Decoders</button>
|
||||||
<button class="tab" data-tab="map">Map</button>
|
<button class="tab" data-tab="map">Map</button>
|
||||||
<button class="tab" data-tab="about">About</button>
|
<button class="tab" data-tab="about">About</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
<div id="bm-empty" class="bm-empty" style="display:none;">No bookmarks yet. Click <strong>+ Add Bookmark</strong> to save a frequency.</div>
|
<div id="bm-empty" class="bm-empty" style="display:none;">No bookmarks yet. Click <strong>+ Add Bookmark</strong> to save a frequency.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-plugins" class="tab-panel" style="display:none;">
|
<div id="tab-decoders" class="tab-panel" style="display:none;">
|
||||||
<div class="sub-tab-bar">
|
<div class="sub-tab-bar">
|
||||||
<button class="sub-tab active" data-subtab="overview">Overview</button>
|
<button class="sub-tab active" data-subtab="overview">Overview</button>
|
||||||
<button class="sub-tab" data-subtab="ais">AIS</button>
|
<button class="sub-tab" data-subtab="ais">AIS</button>
|
||||||
@@ -435,6 +435,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="subtab-ais" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-ais" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="aprs-controls">
|
<div class="aprs-controls">
|
||||||
|
<button id="ais-pause-btn" type="button">Pause</button>
|
||||||
<button id="ais-clear-btn" type="button">Clear</button>
|
<button id="ais-clear-btn" type="button">Clear</button>
|
||||||
<input id="ais-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. MMSI, vessel, A)" />
|
<input id="ais-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. MMSI, vessel, A)" />
|
||||||
<small id="ais-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="ais-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
@@ -457,6 +458,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="subtab-vdes" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-vdes" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="aprs-controls">
|
<div class="aprs-controls">
|
||||||
|
<button id="vdes-pause-btn" type="button">Pause</button>
|
||||||
<button id="vdes-clear-btn" type="button">Clear</button>
|
<button id="vdes-clear-btn" type="button">Clear</button>
|
||||||
<input id="vdes-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. frame, RMS, payload)" />
|
<input id="vdes-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. frame, RMS, payload)" />
|
||||||
<small id="vdes-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="vdes-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
@@ -516,6 +518,7 @@
|
|||||||
<div id="subtab-ft8" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-ft8" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls">
|
<div class="ft8-controls">
|
||||||
<button id="ft8-decode-toggle-btn" type="button">Enable FT8</button>
|
<button id="ft8-decode-toggle-btn" type="button">Enable FT8</button>
|
||||||
|
<button id="ft8-pause-btn" type="button">Pause</button>
|
||||||
<button id="ft8-clear-btn" type="button">Clear</button>
|
<button id="ft8-clear-btn" type="button">Clear</button>
|
||||||
<input id="ft8-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
<input id="ft8-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
||||||
<small id="ft8-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="ft8-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
@@ -533,6 +536,7 @@
|
|||||||
<div id="subtab-wspr" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-wspr" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls">
|
<div class="ft8-controls">
|
||||||
<button id="wspr-decode-toggle-btn" type="button">Enable WSPR</button>
|
<button id="wspr-decode-toggle-btn" type="button">Enable WSPR</button>
|
||||||
|
<button id="wspr-pause-btn" type="button">Pause</button>
|
||||||
<button id="wspr-clear-btn" type="button">Clear</button>
|
<button id="wspr-clear-btn" type="button">Clear</button>
|
||||||
<input id="wspr-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. K1ABC, FN31)" />
|
<input id="wspr-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. K1ABC, FN31)" />
|
||||||
<small id="wspr-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="wspr-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
@@ -549,6 +553,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="subtab-cw" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-cw" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="cw-controls">
|
<div class="cw-controls">
|
||||||
|
<button id="cw-pause-btn" type="button">Pause</button>
|
||||||
<button id="cw-clear-btn" type="button">Clear</button>
|
<button id="cw-clear-btn" type="button">Clear</button>
|
||||||
<small id="cw-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="cw-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
<div id="cw-signal-indicator" class="cw-signal-off"></div>
|
<div id="cw-signal-indicator" class="cw-signal-off"></div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const aisStatus = document.getElementById("ais-status");
|
const aisStatus = document.getElementById("ais-status");
|
||||||
const aisMessagesEl = document.getElementById("ais-messages");
|
const aisMessagesEl = document.getElementById("ais-messages");
|
||||||
const aisFilterInput = document.getElementById("ais-filter");
|
const aisFilterInput = document.getElementById("ais-filter");
|
||||||
|
const aisPauseBtn = document.getElementById("ais-pause-btn");
|
||||||
const aisClearBtn = document.getElementById("ais-clear-btn");
|
const aisClearBtn = document.getElementById("ais-clear-btn");
|
||||||
const aisBarOverlay = document.getElementById("ais-bar-overlay");
|
const aisBarOverlay = document.getElementById("ais-bar-overlay");
|
||||||
const aisChannelSummaryEl = document.getElementById("ais-channel-summary");
|
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;
|
const AIS_CHANNEL_SPACING_HZ = 50_000;
|
||||||
let aisFilterText = "";
|
let aisFilterText = "";
|
||||||
let aisMessageHistory = [];
|
let aisMessageHistory = [];
|
||||||
|
let aisPaused = false;
|
||||||
|
let aisBufferedWhilePaused = 0;
|
||||||
|
|
||||||
function formatAisMhz(freqHz) {
|
function formatAisMhz(freqHz) {
|
||||||
return `${(freqHz / 1_000_000).toFixed(3)} MHz`;
|
return `${(freqHz / 1_000_000).toFixed(3)} MHz`;
|
||||||
@@ -132,7 +135,11 @@ function updateAisSummary() {
|
|||||||
const vessels = aisLatestByVessel(aisMessageHistory);
|
const vessels = aisLatestByVessel(aisMessageHistory);
|
||||||
if (aisVesselCountEl) {
|
if (aisVesselCountEl) {
|
||||||
const count = vessels.length;
|
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) {
|
if (aisLatestSeenEl) {
|
||||||
@@ -144,6 +151,10 @@ function updateAisSummary() {
|
|||||||
aisLatestSeenEl.textContent = `${channel.label} ${aisAgeText(latest._tsMs)}`;
|
aisLatestSeenEl.textContent = `${channel.label} ${aisAgeText(latest._tsMs)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (aisPauseBtn) {
|
||||||
|
aisPauseBtn.textContent = aisPaused ? "Resume" : "Pause";
|
||||||
|
aisPauseBtn.classList.toggle("active", aisPaused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAisRow(msg) {
|
function renderAisRow(msg) {
|
||||||
@@ -261,10 +272,24 @@ window.clearAisBar = function() {
|
|||||||
window.resetAisHistoryView = function() {
|
window.resetAisHistoryView = function() {
|
||||||
if (aisMessagesEl) aisMessagesEl.innerHTML = "";
|
if (aisMessagesEl) aisMessagesEl.innerHTML = "";
|
||||||
aisMessageHistory = [];
|
aisMessageHistory = [];
|
||||||
|
aisBufferedWhilePaused = 0;
|
||||||
updateAisBar();
|
updateAisBar();
|
||||||
|
renderAisHistory();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ais");
|
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) {
|
function addAisMessage(msg) {
|
||||||
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
msg._tsMs = tsMs;
|
msg._tsMs = tsMs;
|
||||||
@@ -278,12 +303,11 @@ function addAisMessage(msg) {
|
|||||||
if (aisMessageHistory.length > AIS_MAX_MESSAGES) aisMessageHistory.length = AIS_MAX_MESSAGES;
|
if (aisMessageHistory.length > AIS_MAX_MESSAGES) aisMessageHistory.length = AIS_MAX_MESSAGES;
|
||||||
updateAisBar();
|
updateAisBar();
|
||||||
|
|
||||||
if (aisMessagesEl) {
|
if (aisPaused) {
|
||||||
const row = renderAisRow(msg);
|
aisBufferedWhilePaused += 1;
|
||||||
aisMessagesEl.prepend(row);
|
updateAisSummary();
|
||||||
while (aisMessagesEl.children.length > AIS_MAX_MESSAGES) {
|
} else {
|
||||||
aisMessagesEl.removeChild(aisMessagesEl.lastChild);
|
renderAisHistory();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.lat != null && msg.lon != null && window.aisMapAddVessel) {
|
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) {
|
if (aisFilterInput) {
|
||||||
aisFilterInput.addEventListener("input", () => {
|
aisFilterInput.addEventListener("input", () => {
|
||||||
aisFilterText = aisFilterInput.value.trim().toUpperCase();
|
aisFilterText = aisFilterInput.value.trim().toUpperCase();
|
||||||
applyAisFilterToAll();
|
renderAisHistory();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onServerAis = function(msg) {
|
window.onServerAis = function(msg) {
|
||||||
if (aisStatus) aisStatus.textContent = "Receiving";
|
if (aisStatus) aisStatus.textContent = aisPaused ? "Paused" : "Receiving";
|
||||||
addAisMessage({
|
addAisMessage({
|
||||||
channel: msg.channel,
|
channel: msg.channel,
|
||||||
message_type: msg.message_type,
|
message_type: msg.message_type,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// --- CW (Morse) Decoder Plugin (server-side decode) ---
|
// --- CW (Morse) Decoder Plugin (server-side decode) ---
|
||||||
const cwStatusEl = document.getElementById("cw-status");
|
const cwStatusEl = document.getElementById("cw-status");
|
||||||
const cwOutputEl = document.getElementById("cw-output");
|
const cwOutputEl = document.getElementById("cw-output");
|
||||||
|
const cwPauseBtn = document.getElementById("cw-pause-btn");
|
||||||
const cwAutoInput = document.getElementById("cw-auto");
|
const cwAutoInput = document.getElementById("cw-auto");
|
||||||
const cwWpmInput = document.getElementById("cw-wpm");
|
const cwWpmInput = document.getElementById("cw-wpm");
|
||||||
const cwToneInput = document.getElementById("cw-tone");
|
const cwToneInput = document.getElementById("cw-tone");
|
||||||
@@ -15,6 +16,8 @@ const CW_WPM_MIN = 5;
|
|||||||
const CW_WPM_MAX = 40;
|
const CW_WPM_MAX = 40;
|
||||||
let cwLastAppendTime = 0;
|
let cwLastAppendTime = 0;
|
||||||
let cwTonePickerRaf = null;
|
let cwTonePickerRaf = null;
|
||||||
|
let cwPaused = false;
|
||||||
|
let cwBufferedWhilePaused = 0;
|
||||||
|
|
||||||
function applyCwAutoUi(enabled) {
|
function applyCwAutoUi(enabled) {
|
||||||
if (cwAutoInput) cwAutoInput.checked = enabled;
|
if (cwAutoInput) cwAutoInput.checked = enabled;
|
||||||
@@ -242,9 +245,17 @@ if (cwToneCanvas) {
|
|||||||
window.resetCwHistoryView = function() {
|
window.resetCwHistoryView = function() {
|
||||||
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
||||||
cwLastAppendTime = 0;
|
cwLastAppendTime = 0;
|
||||||
|
cwBufferedWhilePaused = 0;
|
||||||
|
updateCwPauseUi();
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateCwPauseUi() {
|
||||||
|
if (!cwPauseBtn) return;
|
||||||
|
cwPauseBtn.textContent = cwPaused ? "Resume" : "Pause";
|
||||||
|
cwPauseBtn.classList.toggle("active", cwPaused);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("cw-clear-btn").addEventListener("click", async () => {
|
document.getElementById("cw-clear-btn").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_cw_decode");
|
await postPath("/clear_cw_decode");
|
||||||
@@ -256,8 +267,8 @@ document.getElementById("cw-clear-btn").addEventListener("click", async () => {
|
|||||||
|
|
||||||
// --- Server-side CW decode handler ---
|
// --- Server-side CW decode handler ---
|
||||||
window.onServerCw = function(evt) {
|
window.onServerCw = function(evt) {
|
||||||
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
if (cwStatusEl) cwStatusEl.textContent = cwPaused ? "Paused" : "Receiving";
|
||||||
if (evt.text && cwOutputEl) {
|
if (evt.text && cwOutputEl && !cwPaused) {
|
||||||
// Append decoded text to output
|
// Append decoded text to output
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!cwOutputEl.lastElementChild || now - cwLastAppendTime > 10000 || evt.text === "\n") {
|
if (!cwOutputEl.lastElementChild || now - cwLastAppendTime > 10000 || evt.text === "\n") {
|
||||||
@@ -278,6 +289,9 @@ window.onServerCw = function(evt) {
|
|||||||
if (cwSignalIndicator) {
|
if (cwSignalIndicator) {
|
||||||
cwSignalIndicator.className = evt.signal_on ? "cw-signal-on" : "cw-signal-off";
|
cwSignalIndicator.className = evt.signal_on ? "cw-signal-on" : "cw-signal-off";
|
||||||
}
|
}
|
||||||
|
if (cwPaused && evt.text) {
|
||||||
|
cwBufferedWhilePaused += 1;
|
||||||
|
}
|
||||||
if (!cwAutoInput || cwAutoInput.checked) {
|
if (!cwAutoInput || cwAutoInput.checked) {
|
||||||
if (cwWpmInput && Number.isFinite(Number(evt.wpm))) {
|
if (cwWpmInput && Number.isFinite(Number(evt.wpm))) {
|
||||||
cwWpmInput.value = clampCwWpm(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.refreshCwTonePicker = drawCwTonePicker;
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
if (ensureCwToneCanvasResolution()) drawCwTonePicker();
|
if (ensureCwToneCanvasResolution()) drawCwTonePicker();
|
||||||
});
|
});
|
||||||
applyCwAutoUi(!!cwAutoInput?.checked);
|
applyCwAutoUi(!!cwAutoInput?.checked);
|
||||||
|
updateCwPauseUi();
|
||||||
ensureCwToneCanvasResolution();
|
ensureCwToneCanvasResolution();
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ const ft8Status = document.getElementById("ft8-status");
|
|||||||
const ft8PeriodEl = document.getElementById("ft8-period");
|
const ft8PeriodEl = document.getElementById("ft8-period");
|
||||||
const ft8MessagesEl = document.getElementById("ft8-messages");
|
const ft8MessagesEl = document.getElementById("ft8-messages");
|
||||||
const ft8FilterInput = document.getElementById("ft8-filter");
|
const ft8FilterInput = document.getElementById("ft8-filter");
|
||||||
|
const ft8PauseBtn = document.getElementById("ft8-pause-btn");
|
||||||
const ft8BarOverlay = document.getElementById("ft8-bar-overlay");
|
const ft8BarOverlay = document.getElementById("ft8-bar-overlay");
|
||||||
const FT8_MAX_MESSAGES = 200;
|
const FT8_MAX_MESSAGES = 200;
|
||||||
const FT8_BAR_WINDOW_MS = 15 * 60 * 1000;
|
const FT8_BAR_WINDOW_MS = 15 * 60 * 1000;
|
||||||
const FT8_PERIOD_SECONDS = 15;
|
const FT8_PERIOD_SECONDS = 15;
|
||||||
let ft8FilterText = "";
|
let ft8FilterText = "";
|
||||||
let ft8MessageHistory = [];
|
let ft8MessageHistory = [];
|
||||||
|
let ft8Paused = false;
|
||||||
|
let ft8BufferedWhilePaused = 0;
|
||||||
|
|
||||||
function normalizeFt8DisplayFreqHz(freqHz) {
|
function normalizeFt8DisplayFreqHz(freqHz) {
|
||||||
const rawHz = Number(freqHz);
|
const rawHz = Number(freqHz);
|
||||||
@@ -51,14 +54,34 @@ function renderFt8Row(msg) {
|
|||||||
return row;
|
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) {
|
function addFt8Message(msg) {
|
||||||
ft8MessageHistory.unshift(msg);
|
ft8MessageHistory.unshift(msg);
|
||||||
if (ft8MessageHistory.length > FT8_MAX_MESSAGES) ft8MessageHistory.length = FT8_MAX_MESSAGES;
|
if (ft8MessageHistory.length > FT8_MAX_MESSAGES) ft8MessageHistory.length = FT8_MAX_MESSAGES;
|
||||||
updateFt8Bar();
|
updateFt8Bar();
|
||||||
ft8MessagesEl.prepend(renderFt8Row(msg));
|
if (ft8Paused) {
|
||||||
while (ft8MessagesEl.children.length > FT8_MAX_MESSAGES) {
|
ft8BufferedWhilePaused += 1;
|
||||||
ft8MessagesEl.removeChild(ft8MessagesEl.lastChild);
|
updateFt8PauseUi();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
renderFt8History();
|
||||||
}
|
}
|
||||||
|
|
||||||
function ft8BarRfText(msg) {
|
function ft8BarRfText(msg) {
|
||||||
@@ -202,14 +225,28 @@ window.updateFt8RfDisplay = function() {
|
|||||||
window.resetFt8HistoryView = function() {
|
window.resetFt8HistoryView = function() {
|
||||||
ft8MessagesEl.innerHTML = "";
|
ft8MessagesEl.innerHTML = "";
|
||||||
ft8MessageHistory = [];
|
ft8MessageHistory = [];
|
||||||
|
ft8BufferedWhilePaused = 0;
|
||||||
updateFt8Bar();
|
updateFt8Bar();
|
||||||
|
renderFt8History();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ft8");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ft8");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ft8FilterInput) {
|
if (ft8FilterInput) {
|
||||||
ft8FilterInput.addEventListener("input", () => {
|
ft8FilterInput.addEventListener("input", () => {
|
||||||
ft8FilterText = ft8FilterInput.value.trim().toUpperCase();
|
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 ---
|
// --- Server-side FT8 decode handler ---
|
||||||
window.onServerFt8 = function(msg) {
|
window.onServerFt8 = function(msg) {
|
||||||
ft8Status.textContent = "Receiving";
|
ft8Status.textContent = ft8Paused ? "Paused" : "Receiving";
|
||||||
const raw = (msg.message || "").toString();
|
const raw = (msg.message || "").toString();
|
||||||
const grids = extractAllGrids(raw);
|
const grids = extractAllGrids(raw);
|
||||||
const station = extractLikelyCallsign(raw);
|
const station = extractLikelyCallsign(raw);
|
||||||
@@ -251,3 +288,5 @@ window.onServerFt8 = function(msg) {
|
|||||||
message: msg.message,
|
message: msg.message,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateFt8PauseUi();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const vdesStatus = document.getElementById("vdes-status");
|
const vdesStatus = document.getElementById("vdes-status");
|
||||||
const vdesMessagesEl = document.getElementById("vdes-messages");
|
const vdesMessagesEl = document.getElementById("vdes-messages");
|
||||||
const vdesFilterInput = document.getElementById("vdes-filter");
|
const vdesFilterInput = document.getElementById("vdes-filter");
|
||||||
|
const vdesPauseBtn = document.getElementById("vdes-pause-btn");
|
||||||
const vdesClearBtn = document.getElementById("vdes-clear-btn");
|
const vdesClearBtn = document.getElementById("vdes-clear-btn");
|
||||||
const vdesBarOverlay = document.getElementById("vdes-bar-overlay");
|
const vdesBarOverlay = document.getElementById("vdes-bar-overlay");
|
||||||
const vdesChannelSummaryEl = document.getElementById("vdes-channel-summary");
|
const vdesChannelSummaryEl = document.getElementById("vdes-channel-summary");
|
||||||
@@ -11,6 +12,8 @@ const VDES_MAX_MESSAGES = 200;
|
|||||||
const VDES_BAR_WINDOW_MS = 15 * 60 * 1000;
|
const VDES_BAR_WINDOW_MS = 15 * 60 * 1000;
|
||||||
let vdesFilterText = "";
|
let vdesFilterText = "";
|
||||||
let vdesMessageHistory = [];
|
let vdesMessageHistory = [];
|
||||||
|
let vdesPaused = false;
|
||||||
|
let vdesBufferedWhilePaused = 0;
|
||||||
|
|
||||||
function currentVdesCenterText() {
|
function currentVdesCenterText() {
|
||||||
const raw = (document.getElementById("freq")?.value || "").replace(/[^\d]/g, "");
|
const raw = (document.getElementById("freq")?.value || "").replace(/[^\d]/g, "");
|
||||||
@@ -46,12 +49,20 @@ function updateVdesSummary() {
|
|||||||
}
|
}
|
||||||
if (vdesFrameCountEl) {
|
if (vdesFrameCountEl) {
|
||||||
const count = vdesMessageHistory.length;
|
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) {
|
if (vdesLatestSeenEl) {
|
||||||
const latest = vdesMessageHistory[0];
|
const latest = vdesMessageHistory[0];
|
||||||
vdesLatestSeenEl.textContent = latest ? vdesAgeText(latest._tsMs) : "No traffic yet";
|
vdesLatestSeenEl.textContent = latest ? vdesAgeText(latest._tsMs) : "No traffic yet";
|
||||||
}
|
}
|
||||||
|
if (vdesPauseBtn) {
|
||||||
|
vdesPauseBtn.textContent = vdesPaused ? "Resume" : "Pause";
|
||||||
|
vdesPauseBtn.classList.toggle("active", vdesPaused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyVdesFilterToRow(row) {
|
function applyVdesFilterToRow(row) {
|
||||||
@@ -195,9 +206,23 @@ window.clearVdesBar = function() {
|
|||||||
window.resetVdesHistoryView = function() {
|
window.resetVdesHistoryView = function() {
|
||||||
if (vdesMessagesEl) vdesMessagesEl.innerHTML = "";
|
if (vdesMessagesEl) vdesMessagesEl.innerHTML = "";
|
||||||
vdesMessageHistory = [];
|
vdesMessageHistory = [];
|
||||||
|
vdesBufferedWhilePaused = 0;
|
||||||
updateVdesBar();
|
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) {
|
function addVdesMessage(msg) {
|
||||||
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
msg._tsMs = tsMs;
|
msg._tsMs = tsMs;
|
||||||
@@ -211,12 +236,11 @@ function addVdesMessage(msg) {
|
|||||||
if (vdesMessageHistory.length > VDES_MAX_MESSAGES) vdesMessageHistory.length = VDES_MAX_MESSAGES;
|
if (vdesMessageHistory.length > VDES_MAX_MESSAGES) vdesMessageHistory.length = VDES_MAX_MESSAGES;
|
||||||
updateVdesBar();
|
updateVdesBar();
|
||||||
|
|
||||||
if (vdesMessagesEl) {
|
if (vdesPaused) {
|
||||||
const row = renderVdesRow(msg);
|
vdesBufferedWhilePaused += 1;
|
||||||
vdesMessagesEl.prepend(row);
|
updateVdesSummary();
|
||||||
while (vdesMessagesEl.children.length > VDES_MAX_MESSAGES) {
|
} else {
|
||||||
vdesMessagesEl.removeChild(vdesMessagesEl.lastChild);
|
renderVdesHistory();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,15 +255,27 @@ if (vdesClearBtn) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vdesPauseBtn) {
|
||||||
|
vdesPauseBtn.addEventListener("click", () => {
|
||||||
|
vdesPaused = !vdesPaused;
|
||||||
|
if (!vdesPaused) {
|
||||||
|
vdesBufferedWhilePaused = 0;
|
||||||
|
renderVdesHistory();
|
||||||
|
} else {
|
||||||
|
updateVdesSummary();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (vdesFilterInput) {
|
if (vdesFilterInput) {
|
||||||
vdesFilterInput.addEventListener("input", () => {
|
vdesFilterInput.addEventListener("input", () => {
|
||||||
vdesFilterText = vdesFilterInput.value.trim().toUpperCase();
|
vdesFilterText = vdesFilterInput.value.trim().toUpperCase();
|
||||||
applyVdesFilterToAll();
|
renderVdesHistory();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onServerVdes = function(msg) {
|
window.onServerVdes = function(msg) {
|
||||||
if (vdesStatus) vdesStatus.textContent = "Receiving";
|
if (vdesStatus) vdesStatus.textContent = vdesPaused ? "Paused" : "Receiving";
|
||||||
addVdesMessage({
|
addVdesMessage({
|
||||||
message_type: msg.message_type,
|
message_type: msg.message_type,
|
||||||
bit_len: msg.bit_len,
|
bit_len: msg.bit_len,
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ const wsprStatus = document.getElementById("wspr-status");
|
|||||||
const wsprPeriodEl = document.getElementById("wspr-period");
|
const wsprPeriodEl = document.getElementById("wspr-period");
|
||||||
const wsprMessagesEl = document.getElementById("wspr-messages");
|
const wsprMessagesEl = document.getElementById("wspr-messages");
|
||||||
const wsprFilterInput = document.getElementById("wspr-filter");
|
const wsprFilterInput = document.getElementById("wspr-filter");
|
||||||
|
const wsprPauseBtn = document.getElementById("wspr-pause-btn");
|
||||||
const WSPR_MAX_MESSAGES = 200;
|
const WSPR_MAX_MESSAGES = 200;
|
||||||
const WSPR_PERIOD_SECONDS = 120;
|
const WSPR_PERIOD_SECONDS = 120;
|
||||||
let wsprFilterText = "";
|
let wsprFilterText = "";
|
||||||
|
let wsprMessageHistory = [];
|
||||||
|
let wsprPaused = false;
|
||||||
|
let wsprBufferedWhilePaused = 0;
|
||||||
|
|
||||||
function fmtWsprTime(tsMs) {
|
function fmtWsprTime(tsMs) {
|
||||||
if (!tsMs) return "--:--:--";
|
if (!tsMs) return "--:--:--";
|
||||||
@@ -39,11 +43,33 @@ function renderWsprRow(msg) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWsprMessage(msg) {
|
function updateWsprPauseUi() {
|
||||||
wsprMessagesEl.prepend(renderWsprRow(msg));
|
if (!wsprPauseBtn) return;
|
||||||
while (wsprMessagesEl.children.length > WSPR_MAX_MESSAGES) {
|
wsprPauseBtn.textContent = wsprPaused ? "Resume" : "Pause";
|
||||||
wsprMessagesEl.removeChild(wsprMessagesEl.lastChild);
|
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) {
|
function escapeWsprHtml(input) {
|
||||||
@@ -96,13 +122,28 @@ function applyWsprFilterToAll() {
|
|||||||
|
|
||||||
window.resetWsprHistoryView = function() {
|
window.resetWsprHistoryView = function() {
|
||||||
wsprMessagesEl.innerHTML = "";
|
wsprMessagesEl.innerHTML = "";
|
||||||
|
wsprMessageHistory = [];
|
||||||
|
wsprBufferedWhilePaused = 0;
|
||||||
|
renderWsprHistory();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (wsprFilterInput) {
|
if (wsprFilterInput) {
|
||||||
wsprFilterInput.addEventListener("input", () => {
|
wsprFilterInput.addEventListener("input", () => {
|
||||||
wsprFilterText = wsprFilterInput.value.trim().toUpperCase();
|
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) {
|
window.onServerWspr = function(msg) {
|
||||||
wsprStatus.textContent = "Receiving";
|
wsprStatus.textContent = wsprPaused ? "Paused" : "Receiving";
|
||||||
const raw = (msg.message || "").toString();
|
const raw = (msg.message || "").toString();
|
||||||
const grids = extractAllGrids(raw);
|
const grids = extractAllGrids(raw);
|
||||||
const station = extractLikelyCallsign(raw);
|
const station = extractLikelyCallsign(raw);
|
||||||
@@ -143,3 +184,5 @@ window.onServerWspr = function(msg) {
|
|||||||
message: raw,
|
message: raw,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateWsprPauseUi();
|
||||||
|
|||||||
@@ -1142,6 +1142,13 @@ small { color: var(--text-muted); }
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
.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 {
|
.aprs-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
@@ -1895,7 +1902,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
#tab-main,
|
#tab-main,
|
||||||
#tab-plugins,
|
#tab-decoders,
|
||||||
#tab-map,
|
#tab-map,
|
||||||
#tab-about {
|
#tab-about {
|
||||||
scroll-margin-top: 0.75rem;
|
scroll-margin-top: 0.75rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user