[feat](trx-frontend): simplify decoder history overlays
Move full-history clear actions into Settings > History. Remove decoder pause controls and pause-only buffering paths. Add close controls to live overlay bars and fix FT4/FT2 overlay naming. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -211,20 +211,21 @@ function applyAuthRestrictions() {
|
|||||||
// Disable plugin enable/disable buttons and decode history clear buttons
|
// Disable plugin enable/disable buttons and decode history clear buttons
|
||||||
// Note: sig-clear-btn is allowed for RX (clears local measurements only)
|
// Note: sig-clear-btn is allowed for RX (clears local measurements only)
|
||||||
const pluginToggleBtns = [
|
const pluginToggleBtns = [
|
||||||
"ais-clear-btn",
|
|
||||||
"vdes-clear-btn",
|
|
||||||
"ft8-decode-toggle-btn",
|
"ft8-decode-toggle-btn",
|
||||||
"ft4-decode-toggle-btn",
|
"ft4-decode-toggle-btn",
|
||||||
"ft2-decode-toggle-btn",
|
"ft2-decode-toggle-btn",
|
||||||
"wspr-decode-toggle-btn",
|
"wspr-decode-toggle-btn",
|
||||||
"hf-aprs-decode-toggle-btn",
|
"hf-aprs-decode-toggle-btn",
|
||||||
"cw-auto",
|
"cw-auto",
|
||||||
"aprs-clear-btn",
|
"settings-clear-ais-history",
|
||||||
"ft8-clear-btn",
|
"settings-clear-vdes-history",
|
||||||
"ft4-clear-btn",
|
"settings-clear-aprs-history",
|
||||||
"ft2-clear-btn",
|
"settings-clear-hf-aprs-history",
|
||||||
"wspr-clear-btn",
|
"settings-clear-cw-history",
|
||||||
"cw-clear-btn"
|
"settings-clear-ft8-history",
|
||||||
|
"settings-clear-ft4-history",
|
||||||
|
"settings-clear-ft2-history",
|
||||||
|
"settings-clear-wspr-history"
|
||||||
];
|
];
|
||||||
pluginToggleBtns.forEach(id => {
|
pluginToggleBtns.forEach(id => {
|
||||||
const btn = document.getElementById(id);
|
const btn = document.getElementById(id);
|
||||||
|
|||||||
@@ -515,8 +515,6 @@
|
|||||||
</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>
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -538,8 +536,6 @@
|
|||||||
</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>
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -561,8 +557,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="subtab-aprs" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-aprs" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls aprs-controls">
|
<div class="ft8-controls aprs-controls">
|
||||||
<button id="aprs-pause-btn" type="button">Pause</button>
|
|
||||||
<button id="aprs-clear-btn" type="button">Clear</button>
|
|
||||||
<input id="aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
|
<input id="aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
|
||||||
<small id="aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -598,8 +592,6 @@
|
|||||||
<div id="subtab-hf-aprs" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-hf-aprs" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls aprs-controls">
|
<div class="ft8-controls aprs-controls">
|
||||||
<button id="hf-aprs-decode-toggle-btn" type="button">Enable HF APRS</button>
|
<button id="hf-aprs-decode-toggle-btn" type="button">Enable HF APRS</button>
|
||||||
<button id="hf-aprs-pause-btn" type="button">Pause</button>
|
|
||||||
<button id="hf-aprs-clear-btn" type="button">Clear</button>
|
|
||||||
<input id="hf-aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
|
<input id="hf-aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
|
||||||
<small id="hf-aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="hf-aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -635,8 +627,6 @@
|
|||||||
<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>
|
|
||||||
<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>
|
||||||
<small id="ft8-period" style="color:var(--text-muted);">Next slot --s</small>
|
<small id="ft8-period" style="color:var(--text-muted);">Next slot --s</small>
|
||||||
@@ -653,8 +643,6 @@
|
|||||||
<div id="subtab-ft4" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-ft4" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls">
|
<div class="ft8-controls">
|
||||||
<button id="ft4-decode-toggle-btn" type="button">Enable FT4</button>
|
<button id="ft4-decode-toggle-btn" type="button">Enable FT4</button>
|
||||||
<button id="ft4-pause-btn" type="button">Pause</button>
|
|
||||||
<button id="ft4-clear-btn" type="button">Clear</button>
|
|
||||||
<input id="ft4-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
<input id="ft4-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
||||||
<small id="ft4-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="ft4-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
<small id="ft4-period" style="color:var(--text-muted);">Next slot --s</small>
|
<small id="ft4-period" style="color:var(--text-muted);">Next slot --s</small>
|
||||||
@@ -671,8 +659,6 @@
|
|||||||
<div id="subtab-ft2" class="sub-tab-panel" style="display:none;">
|
<div id="subtab-ft2" class="sub-tab-panel" style="display:none;">
|
||||||
<div class="ft8-controls">
|
<div class="ft8-controls">
|
||||||
<button id="ft2-decode-toggle-btn" type="button">Enable FT2</button>
|
<button id="ft2-decode-toggle-btn" type="button">Enable FT2</button>
|
||||||
<button id="ft2-pause-btn" type="button">Pause</button>
|
|
||||||
<button id="ft2-clear-btn" type="button">Clear</button>
|
|
||||||
<input id="ft2-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
<input id="ft2-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
|
||||||
<small id="ft2-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
<small id="ft2-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||||
<small id="ft2-period" style="color:var(--text-muted);">Next slot --s</small>
|
<small id="ft2-period" style="color:var(--text-muted);">Next slot --s</small>
|
||||||
@@ -689,8 +675,6 @@
|
|||||||
<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>
|
|
||||||
<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>
|
||||||
<small id="wspr-period" style="color:var(--text-muted);">Next slot --:--</small>
|
<small id="wspr-period" style="color:var(--text-muted);">Next slot --:--</small>
|
||||||
@@ -706,8 +690,6 @@
|
|||||||
</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>
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -783,6 +765,7 @@
|
|||||||
<div class="sub-tab-bar">
|
<div class="sub-tab-bar">
|
||||||
<button class="sub-tab active" data-subtab="settings-scheduler">Scheduler</button>
|
<button class="sub-tab active" data-subtab="settings-scheduler">Scheduler</button>
|
||||||
<button class="sub-tab" data-subtab="settings-background-decode">Background Decode</button>
|
<button class="sub-tab" data-subtab="settings-background-decode">Background Decode</button>
|
||||||
|
<button class="sub-tab" data-subtab="settings-history">History</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="subtab-settings-scheduler" class="sub-tab-panel">
|
<div id="subtab-settings-scheduler" class="sub-tab-panel">
|
||||||
<div id="scheduler-panel" class="sch-panel">
|
<div id="scheduler-panel" class="sch-panel">
|
||||||
@@ -920,6 +903,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="subtab-settings-history" class="sub-tab-panel" style="display:none;">
|
||||||
|
<div class="sch-panel">
|
||||||
|
<div class="sch-section">
|
||||||
|
<div class="sch-section-title">Full Decode History</div>
|
||||||
|
<div class="sch-row">
|
||||||
|
<small style="color:var(--text-muted);">These buttons clear the server-side decode history and reset the corresponding decoder state.</small>
|
||||||
|
</div>
|
||||||
|
<div class="sch-row" style="flex-wrap:wrap;">
|
||||||
|
<button id="settings-clear-ais-history" class="sch-write sch-reset-btn" type="button">Clear full AIS history</button>
|
||||||
|
<button id="settings-clear-vdes-history" class="sch-write sch-reset-btn" type="button">Clear full VDES history</button>
|
||||||
|
<button id="settings-clear-aprs-history" class="sch-write sch-reset-btn" type="button">Clear full APRS history</button>
|
||||||
|
<button id="settings-clear-hf-aprs-history" class="sch-write sch-reset-btn" type="button">Clear full HF APRS history</button>
|
||||||
|
<button id="settings-clear-cw-history" class="sch-write sch-reset-btn" type="button">Clear full CW history</button>
|
||||||
|
<button id="settings-clear-ft8-history" class="sch-write sch-reset-btn" type="button">Clear full FT8 history</button>
|
||||||
|
<button id="settings-clear-ft4-history" class="sch-write sch-reset-btn" type="button">Clear full FT4 history</button>
|
||||||
|
<button id="settings-clear-ft2-history" class="sch-write sch-reset-btn" type="button">Clear full FT2 history</button>
|
||||||
|
<button id="settings-clear-wspr-history" class="sch-write sch-reset-btn" type="button">Clear full WSPR history</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-about" class="tab-panel" style="display:none;">
|
<div id="tab-about" class="tab-panel" style="display:none;">
|
||||||
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
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 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");
|
||||||
const aisVesselCountEl = document.getElementById("ais-vessel-count");
|
const aisVesselCountEl = document.getElementById("ais-vessel-count");
|
||||||
@@ -13,8 +11,6 @@ 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 currentAisHistoryRetentionMs() {
|
function currentAisHistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -161,11 +157,7 @@ function updateAisSummary() {
|
|||||||
const vessels = aisLatestByVessel(aisMessageHistory);
|
const vessels = aisLatestByVessel(aisMessageHistory);
|
||||||
if (aisVesselCountEl) {
|
if (aisVesselCountEl) {
|
||||||
const count = vessels.length;
|
const count = vessels.length;
|
||||||
let text = `${count} vessel${count === 1 ? "" : "s"}`;
|
aisVesselCountEl.textContent = `${count} vessel${count === 1 ? "" : "s"}`;
|
||||||
if (aisPaused && aisBufferedWhilePaused > 0) {
|
|
||||||
text += ` · ${aisBufferedWhilePaused} buffered`;
|
|
||||||
}
|
|
||||||
aisVesselCountEl.textContent = text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aisLatestSeenEl) {
|
if (aisLatestSeenEl) {
|
||||||
@@ -177,10 +169,6 @@ 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) {
|
||||||
@@ -292,13 +280,12 @@ function updateAisBar() {
|
|||||||
}
|
}
|
||||||
window.updateAisBar = updateAisBar;
|
window.updateAisBar = updateAisBar;
|
||||||
window.clearAisBar = function() {
|
window.clearAisBar = function() {
|
||||||
document.getElementById("ais-clear-btn")?.click();
|
window.resetAisHistoryView();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.resetAisHistoryView = function() {
|
window.resetAisHistoryView = function() {
|
||||||
if (aisMessagesEl) aisMessagesEl.innerHTML = "";
|
if (aisMessagesEl) aisMessagesEl.innerHTML = "";
|
||||||
aisMessageHistory = [];
|
aisMessageHistory = [];
|
||||||
aisBufferedWhilePaused = 0;
|
|
||||||
updateAisBar();
|
updateAisBar();
|
||||||
renderAisHistory();
|
renderAisHistory();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ais");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ais");
|
||||||
@@ -306,7 +293,7 @@ window.resetAisHistoryView = function() {
|
|||||||
|
|
||||||
function renderAisHistory() {
|
function renderAisHistory() {
|
||||||
pruneAisMessageHistory();
|
pruneAisMessageHistory();
|
||||||
if (!aisMessagesEl || aisPaused) {
|
if (!aisMessagesEl) {
|
||||||
updateAisSummary();
|
updateAisSummary();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -330,13 +317,7 @@ function addAisMessage(msg) {
|
|||||||
aisMessageHistory.unshift(msg);
|
aisMessageHistory.unshift(msg);
|
||||||
pruneAisMessageHistory();
|
pruneAisMessageHistory();
|
||||||
scheduleAisBarUpdate();
|
scheduleAisBarUpdate();
|
||||||
|
scheduleAisHistoryRender();
|
||||||
if (aisPaused) {
|
|
||||||
aisBufferedWhilePaused += 1;
|
|
||||||
updateAisSummary();
|
|
||||||
} else {
|
|
||||||
scheduleAisHistoryRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.lat != null && msg.lon != null && window.aisMapAddVessel) {
|
if (msg.lat != null && msg.lon != null && window.aisMapAddVessel) {
|
||||||
window.aisMapAddVessel(msg);
|
window.aisMapAddVessel(msg);
|
||||||
@@ -362,7 +343,7 @@ function normalizeServerAisMessage(msg) {
|
|||||||
|
|
||||||
window.onServerAisBatch = function(messages) {
|
window.onServerAisBatch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
if (aisStatus) aisStatus.textContent = aisPaused ? "Paused" : "Receiving";
|
if (aisStatus) aisStatus.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerAisMessage(msg);
|
const next = normalizeServerAisMessage(msg);
|
||||||
@@ -382,11 +363,6 @@ window.onServerAisBatch = function(messages) {
|
|||||||
aisMessageHistory = normalized.concat(aisMessageHistory);
|
aisMessageHistory = normalized.concat(aisMessageHistory);
|
||||||
pruneAisMessageHistory();
|
pruneAisMessageHistory();
|
||||||
scheduleAisBarUpdate();
|
scheduleAisBarUpdate();
|
||||||
if (aisPaused) {
|
|
||||||
aisBufferedWhilePaused += messages.length;
|
|
||||||
updateAisSummary();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleAisHistoryRender();
|
scheduleAisHistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,28 +376,14 @@ window.pruneAisHistoryView = function() {
|
|||||||
renderAisHistory();
|
renderAisHistory();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (aisClearBtn) {
|
document.getElementById("settings-clear-ais-history")?.addEventListener("click", async () => {
|
||||||
aisClearBtn.addEventListener("click", async () => {
|
try {
|
||||||
try {
|
await postPath("/clear_ais_decode");
|
||||||
await postPath("/clear_ais_decode");
|
window.resetAisHistoryView();
|
||||||
window.resetAisHistoryView();
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error("AIS history clear failed", e);
|
||||||
console.error("AIS clear failed", e);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aisPauseBtn) {
|
|
||||||
aisPauseBtn.addEventListener("click", () => {
|
|
||||||
aisPaused = !aisPaused;
|
|
||||||
if (!aisPaused) {
|
|
||||||
aisBufferedWhilePaused = 0;
|
|
||||||
renderAisHistory();
|
|
||||||
} else {
|
|
||||||
updateAisSummary();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aisFilterInput) {
|
if (aisFilterInput) {
|
||||||
aisFilterInput.addEventListener("input", () => {
|
aisFilterInput.addEventListener("input", () => {
|
||||||
@@ -431,7 +393,7 @@ if (aisFilterInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.onServerAis = function(msg) {
|
window.onServerAis = function(msg) {
|
||||||
if (aisStatus) aisStatus.textContent = aisPaused ? "Paused" : "Receiving";
|
if (aisStatus) aisStatus.textContent = "Receiving";
|
||||||
addAisMessage(normalizeServerAisMessage(msg));
|
addAisMessage(normalizeServerAisMessage(msg));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const aprsStatus = document.getElementById("aprs-status");
|
|||||||
const aprsPacketsEl = document.getElementById("aprs-packets");
|
const aprsPacketsEl = document.getElementById("aprs-packets");
|
||||||
const aprsFilterInput = document.getElementById("aprs-filter");
|
const aprsFilterInput = document.getElementById("aprs-filter");
|
||||||
const aprsBarOverlay = document.getElementById("aprs-bar-overlay");
|
const aprsBarOverlay = document.getElementById("aprs-bar-overlay");
|
||||||
const aprsPauseBtn = document.getElementById("aprs-pause-btn");
|
|
||||||
const aprsOnlyPosBtn = document.getElementById("aprs-only-pos-btn");
|
const aprsOnlyPosBtn = document.getElementById("aprs-only-pos-btn");
|
||||||
const aprsHideCrcBtn = document.getElementById("aprs-hide-crc-btn");
|
const aprsHideCrcBtn = document.getElementById("aprs-hide-crc-btn");
|
||||||
const aprsCollapseDupBtn = document.getElementById("aprs-collapse-dup-btn");
|
const aprsCollapseDupBtn = document.getElementById("aprs-collapse-dup-btn");
|
||||||
@@ -13,8 +12,7 @@ const aprsLatestSeenEl = document.getElementById("aprs-latest-seen");
|
|||||||
const APRS_BAR_WINDOW_MS = 15 * 60 * 1000;
|
const APRS_BAR_WINDOW_MS = 15 * 60 * 1000;
|
||||||
let aprsFilterText = "";
|
let aprsFilterText = "";
|
||||||
let aprsPacketHistory = [];
|
let aprsPacketHistory = [];
|
||||||
let aprsPaused = false;
|
let aprsBarDismissedAtMs = 0;
|
||||||
let aprsBufferedWhilePaused = 0;
|
|
||||||
let aprsOnlyPos = false;
|
let aprsOnlyPos = false;
|
||||||
let aprsHideCrc = false;
|
let aprsHideCrc = false;
|
||||||
let aprsCollapseDup = false;
|
let aprsCollapseDup = false;
|
||||||
@@ -187,11 +185,7 @@ function updateAprsSummary() {
|
|||||||
aprsTotalCountEl.textContent = `${aprsPacketHistory.length} total`;
|
aprsTotalCountEl.textContent = `${aprsPacketHistory.length} total`;
|
||||||
}
|
}
|
||||||
if (aprsVisibleCountEl) {
|
if (aprsVisibleCountEl) {
|
||||||
let text = `${visible.length} shown`;
|
aprsVisibleCountEl.textContent = `${visible.length} shown`;
|
||||||
if (aprsPaused && aprsBufferedWhilePaused > 0) {
|
|
||||||
text += ` · ${aprsBufferedWhilePaused} buffered`;
|
|
||||||
}
|
|
||||||
aprsVisibleCountEl.textContent = text;
|
|
||||||
}
|
}
|
||||||
if (aprsLatestSeenEl) {
|
if (aprsLatestSeenEl) {
|
||||||
const latest = aprsPacketHistory[0];
|
const latest = aprsPacketHistory[0];
|
||||||
@@ -210,10 +204,6 @@ function updateAprsChipState() {
|
|||||||
aprsOnlyPosBtn?.classList.toggle("active", aprsOnlyPos);
|
aprsOnlyPosBtn?.classList.toggle("active", aprsOnlyPos);
|
||||||
aprsHideCrcBtn?.classList.toggle("active", aprsHideCrc);
|
aprsHideCrcBtn?.classList.toggle("active", aprsHideCrc);
|
||||||
aprsCollapseDupBtn?.classList.toggle("active", aprsCollapseDup);
|
aprsCollapseDupBtn?.classList.toggle("active", aprsCollapseDup);
|
||||||
if (aprsPauseBtn) {
|
|
||||||
aprsPauseBtn.textContent = aprsPaused ? "Resume" : "Pause";
|
|
||||||
aprsPauseBtn.classList.toggle("active", aprsPaused);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAprsRow(pkt, isFresh) {
|
function renderAprsRow(pkt, isFresh) {
|
||||||
@@ -315,7 +305,7 @@ function renderAprsRow(pkt, isFresh) {
|
|||||||
|
|
||||||
function renderAprsHistory() {
|
function renderAprsHistory() {
|
||||||
pruneAprsPacketHistory();
|
pruneAprsPacketHistory();
|
||||||
if (!aprsPacketsEl || aprsPaused) {
|
if (!aprsPacketsEl) {
|
||||||
updateAprsSummary();
|
updateAprsSummary();
|
||||||
updateAprsChipState();
|
updateAprsChipState();
|
||||||
return;
|
return;
|
||||||
@@ -336,11 +326,13 @@ function updateAprsBar() {
|
|||||||
const cutoffMs = Date.now() - APRS_BAR_WINDOW_MS;
|
const cutoffMs = Date.now() - APRS_BAR_WINDOW_MS;
|
||||||
const okFrames = aprsPacketHistory.filter((p) => p.crcOk && p._tsMs >= cutoffMs);
|
const okFrames = aprsPacketHistory.filter((p) => p.crcOk && p._tsMs >= cutoffMs);
|
||||||
const frames = collapseAprsDuplicates(okFrames).slice(0, 8);
|
const frames = collapseAprsDuplicates(okFrames).slice(0, 8);
|
||||||
if (!isPkt || frames.length === 0) {
|
const newestTsMs = frames.reduce((latest, pkt) => Math.max(latest, Number(pkt._tsMs) || 0), 0);
|
||||||
|
if (!isPkt || frames.length === 0 || newestTsMs <= aprsBarDismissedAtMs) {
|
||||||
aprsBarOverlay.style.display = "none";
|
aprsBarOverlay.style.display = "none";
|
||||||
|
aprsBarOverlay.innerHTML = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = '<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">APRS</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearAprsBar()" onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearAprsBar();}" aria-label="Clear APRS overlay">Clear</span></span><span class="aprs-bar-window">Last 15 minutes</span></div>';
|
let html = '<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">APRS</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-actions"><span class="aprs-bar-window">Last 15 minutes</span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearAprsBar()" onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearAprsBar();}" aria-label="Clear APRS overlay">Clear</span></span><button class="aprs-bar-close" type="button" onclick="window.closeAprsBar()" aria-label="Close APRS overlay">×</button></span></div>';
|
||||||
for (const pkt of frames) {
|
for (const pkt of frames) {
|
||||||
const ts = pkt._ts ? `<span class="aprs-bar-time">${pkt._ts}</span>` : "";
|
const ts = pkt._ts ? `<span class="aprs-bar-time">${pkt._ts}</span>` : "";
|
||||||
const call = `<span class="aprs-bar-call">${escapeMapHtml(pkt.srcCall)}</span>`;
|
const call = `<span class="aprs-bar-call">${escapeMapHtml(pkt.srcCall)}</span>`;
|
||||||
@@ -358,13 +350,19 @@ function updateAprsBar() {
|
|||||||
}
|
}
|
||||||
window.updateAprsBar = updateAprsBar;
|
window.updateAprsBar = updateAprsBar;
|
||||||
window.clearAprsBar = function() {
|
window.clearAprsBar = function() {
|
||||||
document.getElementById("aprs-clear-btn")?.click();
|
window.resetAprsHistoryView();
|
||||||
|
};
|
||||||
|
window.closeAprsBar = function() {
|
||||||
|
aprsBarDismissedAtMs = Date.now();
|
||||||
|
if (aprsBarOverlay) {
|
||||||
|
aprsBarOverlay.style.display = "none";
|
||||||
|
aprsBarOverlay.innerHTML = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.resetAprsHistoryView = function() {
|
window.resetAprsHistoryView = function() {
|
||||||
if (aprsPacketsEl) aprsPacketsEl.innerHTML = "";
|
if (aprsPacketsEl) aprsPacketsEl.innerHTML = "";
|
||||||
aprsPacketHistory = [];
|
aprsPacketHistory = [];
|
||||||
aprsBufferedWhilePaused = 0;
|
|
||||||
updateAprsBar();
|
updateAprsBar();
|
||||||
renderAprsHistory();
|
renderAprsHistory();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("aprs");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("aprs");
|
||||||
@@ -390,13 +388,6 @@ function addAprsPacket(pkt) {
|
|||||||
|
|
||||||
if (pkt.crcOk) scheduleAprsBarUpdate();
|
if (pkt.crcOk) scheduleAprsBarUpdate();
|
||||||
|
|
||||||
if (aprsPaused) {
|
|
||||||
aprsBufferedWhilePaused += 1;
|
|
||||||
updateAprsSummary();
|
|
||||||
updateAprsChipState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleAprsHistoryRender();
|
scheduleAprsHistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +411,7 @@ function normalizeServerAprsPacket(pkt) {
|
|||||||
|
|
||||||
window.onServerAprsBatch = function(packets) {
|
window.onServerAprsBatch = function(packets) {
|
||||||
if (!Array.isArray(packets) || packets.length === 0) return;
|
if (!Array.isArray(packets) || packets.length === 0) return;
|
||||||
aprsStatus.textContent = aprsPaused ? "Paused" : "Receiving";
|
aprsStatus.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
let hasCrcOk = false;
|
let hasCrcOk = false;
|
||||||
for (const pkt of packets) {
|
for (const pkt of packets) {
|
||||||
@@ -438,12 +429,6 @@ window.onServerAprsBatch = function(packets) {
|
|||||||
aprsPacketHistory = normalized.concat(aprsPacketHistory);
|
aprsPacketHistory = normalized.concat(aprsPacketHistory);
|
||||||
pruneAprsPacketHistory();
|
pruneAprsPacketHistory();
|
||||||
if (hasCrcOk) scheduleAprsBarUpdate();
|
if (hasCrcOk) scheduleAprsBarUpdate();
|
||||||
if (aprsPaused) {
|
|
||||||
aprsBufferedWhilePaused += packets.length;
|
|
||||||
updateAprsSummary();
|
|
||||||
updateAprsChipState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleAprsHistoryRender();
|
scheduleAprsHistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -451,28 +436,15 @@ window.restoreAprsHistory = function(packets) {
|
|||||||
window.onServerAprsBatch(packets);
|
window.onServerAprsBatch(packets);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById("aprs-clear-btn").addEventListener("click", async () => {
|
document.getElementById("settings-clear-aprs-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_aprs_decode");
|
await postPath("/clear_aprs_decode");
|
||||||
window.resetAprsHistoryView();
|
window.resetAprsHistoryView();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("APRS clear failed", e);
|
console.error("APRS history clear failed", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (aprsPauseBtn) {
|
|
||||||
aprsPauseBtn.addEventListener("click", () => {
|
|
||||||
aprsPaused = !aprsPaused;
|
|
||||||
if (!aprsPaused) {
|
|
||||||
aprsBufferedWhilePaused = 0;
|
|
||||||
renderAprsHistory();
|
|
||||||
} else {
|
|
||||||
updateAprsSummary();
|
|
||||||
updateAprsChipState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aprsOnlyPosBtn) {
|
if (aprsOnlyPosBtn) {
|
||||||
aprsOnlyPosBtn.addEventListener("click", () => {
|
aprsOnlyPosBtn.addEventListener("click", () => {
|
||||||
aprsOnlyPos = !aprsOnlyPos;
|
aprsOnlyPos = !aprsOnlyPos;
|
||||||
@@ -512,7 +484,7 @@ if (aprsFilterInput) {
|
|||||||
|
|
||||||
// --- Server-side APRS decode handler ---
|
// --- Server-side APRS decode handler ---
|
||||||
window.onServerAprs = function(pkt) {
|
window.onServerAprs = function(pkt) {
|
||||||
aprsStatus.textContent = aprsPaused ? "Paused" : "Receiving";
|
aprsStatus.textContent = "Receiving";
|
||||||
addAprsPacket(normalizeServerAprsPacket(pkt));
|
addAprsPacket(normalizeServerAprsPacket(pkt));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// --- 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");
|
||||||
@@ -22,10 +21,9 @@ const CW_BAR_WINDOW_MS = 15 * 60 * 1000;
|
|||||||
const CW_BAR_LINE_GAP_MS = 5000;
|
const CW_BAR_LINE_GAP_MS = 5000;
|
||||||
let cwLastAppendTime = 0;
|
let cwLastAppendTime = 0;
|
||||||
let cwTonePickerRaf = null;
|
let cwTonePickerRaf = null;
|
||||||
let cwPaused = false;
|
|
||||||
let cwBufferedWhilePaused = 0;
|
|
||||||
let cwBarHistory = []; // [{tsMs, ts, text, wpm, tone_hz}]
|
let cwBarHistory = []; // [{tsMs, ts, text, wpm, tone_hz}]
|
||||||
let cwBarCurrentLine = null; // accumulates chars until gap/newline
|
let cwBarCurrentLine = null; // accumulates chars until gap/newline
|
||||||
|
let cwBarDismissedAtMs = 0;
|
||||||
// Tracks a user-initiated auto toggle that is in-flight (POST not yet
|
// Tracks a user-initiated auto toggle that is in-flight (POST not yet
|
||||||
// acknowledged). While set, server-state updates must not override the
|
// acknowledged). While set, server-state updates must not override the
|
||||||
// checkbox so that a concurrent SSE event carrying the *old* cw_auto value
|
// checkbox so that a concurrent SSE event carrying the *old* cw_auto value
|
||||||
@@ -71,18 +69,23 @@ function updateCwBar() {
|
|||||||
const recent = cwBarHistory.filter((l) => l.tsMs >= cutoffMs);
|
const recent = cwBarHistory.filter((l) => l.tsMs >= cutoffMs);
|
||||||
// Prepend the in-progress line so characters appear immediately
|
// Prepend the in-progress line so characters appear immediately
|
||||||
const liveLines = cwBarCurrentLine && cwBarCurrentLine.text ? [cwBarCurrentLine, ...recent] : recent;
|
const liveLines = cwBarCurrentLine && cwBarCurrentLine.text ? [cwBarCurrentLine, ...recent] : recent;
|
||||||
if (!isCw || liveLines.length === 0) {
|
const newestTsMs = liveLines.reduce((latest, line) => Math.max(latest, Number(line.tsMs) || 0), 0);
|
||||||
|
if (!isCw || liveLines.length === 0 || newestTsMs <= cwBarDismissedAtMs) {
|
||||||
cwBarOverlay.style.display = "none";
|
cwBarOverlay.style.display = "none";
|
||||||
|
cwBarOverlay.innerHTML = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html =
|
let html =
|
||||||
'<div class="aprs-bar-header">' +
|
'<div class="aprs-bar-header">' +
|
||||||
'<span class="aprs-bar-title"><span class="aprs-bar-title-word">CW</span><span class="aprs-bar-title-word">Live</span></span>' +
|
'<span class="aprs-bar-title"><span class="aprs-bar-title-word">CW</span><span class="aprs-bar-title-word">Live</span></span>' +
|
||||||
'<span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0"' +
|
'<span class="aprs-bar-actions">' +
|
||||||
' onclick="window.clearCwBar()"' +
|
'<span class="aprs-bar-window">Last 15 minutes</span>' +
|
||||||
' onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearCwBar();}"' +
|
'<span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0"' +
|
||||||
' aria-label="Clear CW overlay">Clear</span></span>' +
|
' onclick="window.clearCwBar()"' +
|
||||||
'<span class="aprs-bar-window">Last 15 minutes</span>' +
|
' onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearCwBar();}"' +
|
||||||
|
' aria-label="Clear CW overlay">Clear</span></span>' +
|
||||||
|
'<button class="aprs-bar-close" type="button" onclick="window.closeCwBar()" aria-label="Close CW overlay">×</button>' +
|
||||||
|
'</span>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
for (const line of liveLines.slice(0, 8)) {
|
for (const line of liveLines.slice(0, 8)) {
|
||||||
const ts = line.ts ? `<span class="aprs-bar-time">${line.ts}</span>` : "";
|
const ts = line.ts ? `<span class="aprs-bar-time">${line.ts}</span>` : "";
|
||||||
@@ -100,7 +103,14 @@ function updateCwBar() {
|
|||||||
}
|
}
|
||||||
window.updateCwBar = updateCwBar;
|
window.updateCwBar = updateCwBar;
|
||||||
window.clearCwBar = function() {
|
window.clearCwBar = function() {
|
||||||
document.getElementById("cw-clear-btn")?.click();
|
window.resetCwHistoryView();
|
||||||
|
};
|
||||||
|
window.closeCwBar = function() {
|
||||||
|
cwBarDismissedAtMs = Date.now();
|
||||||
|
if (cwBarOverlay) {
|
||||||
|
cwBarOverlay.style.display = "none";
|
||||||
|
cwBarOverlay.innerHTML = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function clampCwWpm(wpm) {
|
function clampCwWpm(wpm) {
|
||||||
@@ -343,33 +353,25 @@ if (cwToneCanvas) {
|
|||||||
window.resetCwHistoryView = function() {
|
window.resetCwHistoryView = function() {
|
||||||
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
||||||
cwLastAppendTime = 0;
|
cwLastAppendTime = 0;
|
||||||
cwBufferedWhilePaused = 0;
|
|
||||||
cwBarHistory = [];
|
cwBarHistory = [];
|
||||||
cwBarCurrentLine = null;
|
cwBarCurrentLine = null;
|
||||||
updateCwPauseUi();
|
|
||||||
updateCwBar();
|
updateCwBar();
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateCwPauseUi() {
|
document.getElementById("settings-clear-cw-history")?.addEventListener("click", async () => {
|
||||||
if (!cwPauseBtn) return;
|
|
||||||
cwPauseBtn.textContent = cwPaused ? "Resume" : "Pause";
|
|
||||||
cwPauseBtn.classList.toggle("active", cwPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("cw-clear-btn").addEventListener("click", async () => {
|
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_cw_decode");
|
await postPath("/clear_cw_decode");
|
||||||
window.resetCwHistoryView();
|
window.resetCwHistoryView();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("CW clear failed", e);
|
console.error("CW history clear failed", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Server-side CW decode handler ---
|
// --- Server-side CW decode handler ---
|
||||||
window.onServerCw = function(evt) {
|
window.onServerCw = function(evt) {
|
||||||
if (cwStatusEl) cwStatusEl.textContent = cwPaused ? "Paused" : "Receiving";
|
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||||
if (evt.text && cwOutputEl && !cwPaused) {
|
if (evt.text && cwOutputEl) {
|
||||||
// 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") {
|
||||||
@@ -408,9 +410,6 @@ 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);
|
||||||
@@ -428,22 +427,12 @@ window.onServerCw = function(evt) {
|
|||||||
|
|
||||||
window.restoreCwHistory = function(events) {
|
window.restoreCwHistory = function(events) {
|
||||||
if (!Array.isArray(events) || events.length === 0) return;
|
if (!Array.isArray(events) || events.length === 0) return;
|
||||||
if (cwStatusEl) cwStatusEl.textContent = cwPaused ? "Paused" : "Receiving";
|
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||||
for (const evt of events) {
|
for (const evt of events) {
|
||||||
window.onServerCw(evt);
|
window.onServerCw(evt);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cwPauseBtn) {
|
|
||||||
cwPauseBtn.addEventListener("click", () => {
|
|
||||||
cwPaused = !cwPaused;
|
|
||||||
if (!cwPaused) {
|
|
||||||
cwBufferedWhilePaused = 0;
|
|
||||||
}
|
|
||||||
updateCwPauseUi();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.refreshCwTonePicker = function refreshCwTonePicker() {
|
window.refreshCwTonePicker = function refreshCwTonePicker() {
|
||||||
ensureCwToneCanvasResolution();
|
ensureCwToneCanvasResolution();
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
@@ -452,7 +441,6 @@ window.addEventListener("resize", () => {
|
|||||||
if (ensureCwToneCanvasResolution()) drawCwTonePicker();
|
if (ensureCwToneCanvasResolution()) drawCwTonePicker();
|
||||||
});
|
});
|
||||||
applyCwAutoUi(!!cwAutoInput?.checked);
|
applyCwAutoUi(!!cwAutoInput?.checked);
|
||||||
updateCwPauseUi();
|
|
||||||
updateCwBar();
|
updateCwBar();
|
||||||
ensureCwToneCanvasResolution();
|
ensureCwToneCanvasResolution();
|
||||||
drawCwTonePicker();
|
drawCwTonePicker();
|
||||||
|
|||||||
@@ -12,12 +12,9 @@ const ft2Status = document.getElementById("ft2-status");
|
|||||||
const ft2PeriodEl = document.getElementById("ft2-period");
|
const ft2PeriodEl = document.getElementById("ft2-period");
|
||||||
const ft2MessagesEl = document.getElementById("ft2-messages");
|
const ft2MessagesEl = document.getElementById("ft2-messages");
|
||||||
const ft2FilterInput = document.getElementById("ft2-filter");
|
const ft2FilterInput = document.getElementById("ft2-filter");
|
||||||
const ft2PauseBtn = document.getElementById("ft2-pause-btn");
|
|
||||||
const FT2_PERIOD_MS = 3750;
|
const FT2_PERIOD_MS = 3750;
|
||||||
let ft2FilterText = "";
|
let ft2FilterText = "";
|
||||||
let ft2MessageHistory = [];
|
let ft2MessageHistory = [];
|
||||||
let ft2Paused = false;
|
|
||||||
let ft2BufferedWhilePaused = 0;
|
|
||||||
|
|
||||||
function currentFt2HistoryRetentionMs() {
|
function currentFt2HistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -78,15 +75,9 @@ function renderFt2Row(msg) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFt2PauseUi() {
|
|
||||||
if (!ft2PauseBtn) return;
|
|
||||||
ft2PauseBtn.textContent = ft2Paused ? "Resume" : "Pause";
|
|
||||||
ft2PauseBtn.classList.toggle("active", ft2Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFt2History() {
|
function renderFt2History() {
|
||||||
pruneFt2MessageHistory();
|
pruneFt2MessageHistory();
|
||||||
if (!ft2MessagesEl || ft2Paused) { updateFt2PauseUi(); return; }
|
if (!ft2MessagesEl) return;
|
||||||
const filter = ft2FilterText;
|
const filter = ft2FilterText;
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < ft2MessageHistory.length; i++) {
|
for (let i = 0; i < ft2MessageHistory.length; i++) {
|
||||||
@@ -95,14 +86,14 @@ function renderFt2History() {
|
|||||||
fragment.appendChild(renderFt2Row(msg));
|
fragment.appendChild(renderFt2Row(msg));
|
||||||
}
|
}
|
||||||
ft2MessagesEl.replaceChildren(fragment);
|
ft2MessagesEl.replaceChildren(fragment);
|
||||||
updateFt2PauseUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFt2Message(msg) {
|
function addFt2Message(msg) {
|
||||||
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
ft2MessageHistory.unshift(msg);
|
ft2MessageHistory.unshift(msg);
|
||||||
pruneFt2MessageHistory();
|
pruneFt2MessageHistory();
|
||||||
if (ft2Paused) { ft2BufferedWhilePaused += 1; updateFt2PauseUi(); return; }
|
window.setFt8FamilyBarDecoder?.("ft2");
|
||||||
|
window.updateFt8Bar?.();
|
||||||
scheduleFt2HistoryRender();
|
scheduleFt2HistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +118,7 @@ function normalizeServerFt2Message(msg) {
|
|||||||
|
|
||||||
window.onServerFt2Batch = function(messages) {
|
window.onServerFt2Batch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
if (ft2Status) ft2Status.textContent = ft2Paused ? "Paused" : "Receiving";
|
if (ft2Status) ft2Status.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerFt2Message(msg);
|
const next = normalizeServerFt2Message(msg);
|
||||||
@@ -140,7 +131,8 @@ window.onServerFt2Batch = function(messages) {
|
|||||||
normalized.reverse();
|
normalized.reverse();
|
||||||
ft2MessageHistory = normalized.concat(ft2MessageHistory);
|
ft2MessageHistory = normalized.concat(ft2MessageHistory);
|
||||||
pruneFt2MessageHistory();
|
pruneFt2MessageHistory();
|
||||||
if (ft2Paused) { ft2BufferedWhilePaused += messages.length; updateFt2PauseUi(); return; }
|
window.setFt8FamilyBarDecoder?.("ft2");
|
||||||
|
window.updateFt8Bar?.();
|
||||||
scheduleFt2HistoryRender();
|
scheduleFt2HistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,10 +142,33 @@ window.pruneFt2HistoryView = function() { pruneFt2MessageHistory(); renderFt2His
|
|||||||
window.resetFt2HistoryView = function() {
|
window.resetFt2HistoryView = function() {
|
||||||
if (ft2MessagesEl) ft2MessagesEl.innerHTML = "";
|
if (ft2MessagesEl) ft2MessagesEl.innerHTML = "";
|
||||||
ft2MessageHistory = [];
|
ft2MessageHistory = [];
|
||||||
ft2BufferedWhilePaused = 0;
|
window.updateFt8Bar?.();
|
||||||
renderFt2History();
|
renderFt2History();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function buildFt2BarFrames() {
|
||||||
|
const cutoffMs = Date.now() - 15 * 60 * 1000;
|
||||||
|
const messages = ft2MessageHistory.filter((msg) => Number(msg._tsMs ?? msg.ts_ms) >= cutoffMs).slice(0, 8);
|
||||||
|
const newestTsMs = messages.reduce((latest, msg) => Math.max(latest, Number(msg._tsMs ?? msg.ts_ms) || 0), 0);
|
||||||
|
if (messages.length === 0) {
|
||||||
|
return { count: 0, newestTsMs: 0, html: "" };
|
||||||
|
}
|
||||||
|
let html = "";
|
||||||
|
for (const msg of messages) {
|
||||||
|
const tsMs = msg._tsMs ?? msg.ts_ms;
|
||||||
|
const ts = tsMs ? `<span class="aprs-bar-time">${fmtTime(tsMs)}</span>` : "";
|
||||||
|
const snr = Number.isFinite(msg.snr_db) ? `${msg.snr_db.toFixed(1)} dB` : "-- dB";
|
||||||
|
const dt = Number.isFinite(msg.dt_s) ? `dt ${msg.dt_s.toFixed(2)}` : null;
|
||||||
|
const displayFreqHz = normalizeFt2DisplayFreqHz(msg.freq_hz);
|
||||||
|
const rf = Number.isFinite(displayFreqHz) ? `${displayFreqHz.toFixed(0)} Hz` : null;
|
||||||
|
const detail = [snr, dt, rf].filter(Boolean).join(" · ");
|
||||||
|
const text = ft8RenderMessageFt2((msg.message || "").toString());
|
||||||
|
html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${ts}<span class="aprs-bar-call">${text}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
|
||||||
|
}
|
||||||
|
return { count: messages.length, newestTsMs, html };
|
||||||
|
}
|
||||||
|
window.registerFt8FamilyBarRenderer?.("ft2", buildFt2BarFrames);
|
||||||
|
|
||||||
if (ft2FilterInput) {
|
if (ft2FilterInput) {
|
||||||
ft2FilterInput.addEventListener("input", () => {
|
ft2FilterInput.addEventListener("input", () => {
|
||||||
ft2FilterText = ft2FilterInput.value.trim().toUpperCase();
|
ft2FilterText = ft2FilterInput.value.trim().toUpperCase();
|
||||||
@@ -161,31 +176,22 @@ if (ft2FilterInput) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ft2PauseBtn) {
|
|
||||||
ft2PauseBtn.addEventListener("click", () => {
|
|
||||||
ft2Paused = !ft2Paused;
|
|
||||||
if (!ft2Paused) { ft2BufferedWhilePaused = 0; renderFt2History(); } else { updateFt2PauseUi(); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("ft2-decode-toggle-btn")?.addEventListener("click", async () => {
|
document.getElementById("ft2-decode-toggle-btn")?.addEventListener("click", async () => {
|
||||||
try { await postPath("/toggle_ft2_decode"); } catch (e) { console.error("FT2 toggle failed", e); }
|
try { await postPath("/toggle_ft2_decode"); } catch (e) { console.error("FT2 toggle failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("ft2-clear-btn")?.addEventListener("click", async () => {
|
document.getElementById("settings-clear-ft2-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_ft2_decode");
|
await postPath("/clear_ft2_decode");
|
||||||
window.resetFt2HistoryView();
|
window.resetFt2HistoryView();
|
||||||
} catch (e) { console.error("FT2 clear failed", e); }
|
} catch (e) { console.error("FT2 history clear failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onServerFt2 = function(msg) {
|
window.onServerFt2 = function(msg) {
|
||||||
if (ft2Status) ft2Status.textContent = ft2Paused ? "Paused" : "Receiving";
|
if (ft2Status) ft2Status.textContent = "Receiving";
|
||||||
const next = normalizeServerFt2Message(msg);
|
const next = normalizeServerFt2Message(msg);
|
||||||
if (next.grids.length > 0 && window.mapAddLocator) {
|
if (next.grids.length > 0 && window.mapAddLocator) {
|
||||||
window.mapAddLocator(next.raw, next.grids, "ft2", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
window.mapAddLocator(next.raw, next.grids, "ft2", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
||||||
}
|
}
|
||||||
addFt2Message(next.history);
|
addFt2Message(next.history);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFt2PauseUi();
|
|
||||||
|
|||||||
@@ -12,12 +12,9 @@ const ft4Status = document.getElementById("ft4-status");
|
|||||||
const ft4PeriodEl = document.getElementById("ft4-period");
|
const ft4PeriodEl = document.getElementById("ft4-period");
|
||||||
const ft4MessagesEl = document.getElementById("ft4-messages");
|
const ft4MessagesEl = document.getElementById("ft4-messages");
|
||||||
const ft4FilterInput = document.getElementById("ft4-filter");
|
const ft4FilterInput = document.getElementById("ft4-filter");
|
||||||
const ft4PauseBtn = document.getElementById("ft4-pause-btn");
|
|
||||||
const FT4_PERIOD_MS = 7500;
|
const FT4_PERIOD_MS = 7500;
|
||||||
let ft4FilterText = "";
|
let ft4FilterText = "";
|
||||||
let ft4MessageHistory = [];
|
let ft4MessageHistory = [];
|
||||||
let ft4Paused = false;
|
|
||||||
let ft4BufferedWhilePaused = 0;
|
|
||||||
|
|
||||||
function currentFt4HistoryRetentionMs() {
|
function currentFt4HistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -78,15 +75,9 @@ function renderFt4Row(msg) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFt4PauseUi() {
|
|
||||||
if (!ft4PauseBtn) return;
|
|
||||||
ft4PauseBtn.textContent = ft4Paused ? "Resume" : "Pause";
|
|
||||||
ft4PauseBtn.classList.toggle("active", ft4Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFt4History() {
|
function renderFt4History() {
|
||||||
pruneFt4MessageHistory();
|
pruneFt4MessageHistory();
|
||||||
if (!ft4MessagesEl || ft4Paused) { updateFt4PauseUi(); return; }
|
if (!ft4MessagesEl) return;
|
||||||
const filter = ft4FilterText;
|
const filter = ft4FilterText;
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < ft4MessageHistory.length; i++) {
|
for (let i = 0; i < ft4MessageHistory.length; i++) {
|
||||||
@@ -95,14 +86,14 @@ function renderFt4History() {
|
|||||||
fragment.appendChild(renderFt4Row(msg));
|
fragment.appendChild(renderFt4Row(msg));
|
||||||
}
|
}
|
||||||
ft4MessagesEl.replaceChildren(fragment);
|
ft4MessagesEl.replaceChildren(fragment);
|
||||||
updateFt4PauseUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFt4Message(msg) {
|
function addFt4Message(msg) {
|
||||||
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
ft4MessageHistory.unshift(msg);
|
ft4MessageHistory.unshift(msg);
|
||||||
pruneFt4MessageHistory();
|
pruneFt4MessageHistory();
|
||||||
if (ft4Paused) { ft4BufferedWhilePaused += 1; updateFt4PauseUi(); return; }
|
window.setFt8FamilyBarDecoder?.("ft4");
|
||||||
|
window.updateFt8Bar?.();
|
||||||
scheduleFt4HistoryRender();
|
scheduleFt4HistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +118,7 @@ function normalizeServerFt4Message(msg) {
|
|||||||
|
|
||||||
window.onServerFt4Batch = function(messages) {
|
window.onServerFt4Batch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
if (ft4Status) ft4Status.textContent = ft4Paused ? "Paused" : "Receiving";
|
if (ft4Status) ft4Status.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerFt4Message(msg);
|
const next = normalizeServerFt4Message(msg);
|
||||||
@@ -140,7 +131,8 @@ window.onServerFt4Batch = function(messages) {
|
|||||||
normalized.reverse();
|
normalized.reverse();
|
||||||
ft4MessageHistory = normalized.concat(ft4MessageHistory);
|
ft4MessageHistory = normalized.concat(ft4MessageHistory);
|
||||||
pruneFt4MessageHistory();
|
pruneFt4MessageHistory();
|
||||||
if (ft4Paused) { ft4BufferedWhilePaused += messages.length; updateFt4PauseUi(); return; }
|
window.setFt8FamilyBarDecoder?.("ft4");
|
||||||
|
window.updateFt8Bar?.();
|
||||||
scheduleFt4HistoryRender();
|
scheduleFt4HistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,10 +142,33 @@ window.pruneFt4HistoryView = function() { pruneFt4MessageHistory(); renderFt4His
|
|||||||
window.resetFt4HistoryView = function() {
|
window.resetFt4HistoryView = function() {
|
||||||
if (ft4MessagesEl) ft4MessagesEl.innerHTML = "";
|
if (ft4MessagesEl) ft4MessagesEl.innerHTML = "";
|
||||||
ft4MessageHistory = [];
|
ft4MessageHistory = [];
|
||||||
ft4BufferedWhilePaused = 0;
|
window.updateFt8Bar?.();
|
||||||
renderFt4History();
|
renderFt4History();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function buildFt4BarFrames() {
|
||||||
|
const cutoffMs = Date.now() - 15 * 60 * 1000;
|
||||||
|
const messages = ft4MessageHistory.filter((msg) => Number(msg._tsMs ?? msg.ts_ms) >= cutoffMs).slice(0, 8);
|
||||||
|
const newestTsMs = messages.reduce((latest, msg) => Math.max(latest, Number(msg._tsMs ?? msg.ts_ms) || 0), 0);
|
||||||
|
if (messages.length === 0) {
|
||||||
|
return { count: 0, newestTsMs: 0, html: "" };
|
||||||
|
}
|
||||||
|
let html = "";
|
||||||
|
for (const msg of messages) {
|
||||||
|
const tsMs = msg._tsMs ?? msg.ts_ms;
|
||||||
|
const ts = tsMs ? `<span class="aprs-bar-time">${fmtTime(tsMs)}</span>` : "";
|
||||||
|
const snr = Number.isFinite(msg.snr_db) ? `${msg.snr_db.toFixed(1)} dB` : "-- dB";
|
||||||
|
const dt = Number.isFinite(msg.dt_s) ? `dt ${msg.dt_s.toFixed(2)}` : null;
|
||||||
|
const displayFreqHz = normalizeFt4DisplayFreqHz(msg.freq_hz);
|
||||||
|
const rf = Number.isFinite(displayFreqHz) ? `${displayFreqHz.toFixed(0)} Hz` : null;
|
||||||
|
const detail = [snr, dt, rf].filter(Boolean).join(" · ");
|
||||||
|
const text = ft8RenderMessage((msg.message || "").toString());
|
||||||
|
html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${ts}<span class="aprs-bar-call">${text}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
|
||||||
|
}
|
||||||
|
return { count: messages.length, newestTsMs, html };
|
||||||
|
}
|
||||||
|
window.registerFt8FamilyBarRenderer?.("ft4", buildFt4BarFrames);
|
||||||
|
|
||||||
if (ft4FilterInput) {
|
if (ft4FilterInput) {
|
||||||
ft4FilterInput.addEventListener("input", () => {
|
ft4FilterInput.addEventListener("input", () => {
|
||||||
ft4FilterText = ft4FilterInput.value.trim().toUpperCase();
|
ft4FilterText = ft4FilterInput.value.trim().toUpperCase();
|
||||||
@@ -161,31 +176,22 @@ if (ft4FilterInput) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ft4PauseBtn) {
|
|
||||||
ft4PauseBtn.addEventListener("click", () => {
|
|
||||||
ft4Paused = !ft4Paused;
|
|
||||||
if (!ft4Paused) { ft4BufferedWhilePaused = 0; renderFt4History(); } else { updateFt4PauseUi(); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("ft4-decode-toggle-btn")?.addEventListener("click", async () => {
|
document.getElementById("ft4-decode-toggle-btn")?.addEventListener("click", async () => {
|
||||||
try { await postPath("/toggle_ft4_decode"); } catch (e) { console.error("FT4 toggle failed", e); }
|
try { await postPath("/toggle_ft4_decode"); } catch (e) { console.error("FT4 toggle failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("ft4-clear-btn")?.addEventListener("click", async () => {
|
document.getElementById("settings-clear-ft4-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_ft4_decode");
|
await postPath("/clear_ft4_decode");
|
||||||
window.resetFt4HistoryView();
|
window.resetFt4HistoryView();
|
||||||
} catch (e) { console.error("FT4 clear failed", e); }
|
} catch (e) { console.error("FT4 history clear failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onServerFt4 = function(msg) {
|
window.onServerFt4 = function(msg) {
|
||||||
if (ft4Status) ft4Status.textContent = ft4Paused ? "Paused" : "Receiving";
|
if (ft4Status) ft4Status.textContent = "Receiving";
|
||||||
const next = normalizeServerFt4Message(msg);
|
const next = normalizeServerFt4Message(msg);
|
||||||
if (next.grids.length > 0 && window.mapAddLocator) {
|
if (next.grids.length > 0 && window.mapAddLocator) {
|
||||||
window.mapAddLocator(next.raw, next.grids, "ft4", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
window.mapAddLocator(next.raw, next.grids, "ft4", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
||||||
}
|
}
|
||||||
addFt4Message(next.history);
|
addFt4Message(next.history);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFt4PauseUi();
|
|
||||||
|
|||||||
@@ -3,14 +3,23 @@ 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_BAR_WINDOW_MS = 15 * 60 * 1000;
|
const FT8_BAR_WINDOW_MS = 15 * 60 * 1000;
|
||||||
const FT8_PERIOD_SECONDS = 15;
|
const FT8_PERIOD_SECONDS = 15;
|
||||||
|
const FT8_BAR_DECODER_LABELS = {
|
||||||
|
ft8: "FT8",
|
||||||
|
ft4: "FT4",
|
||||||
|
ft2: "FT2",
|
||||||
|
};
|
||||||
let ft8FilterText = "";
|
let ft8FilterText = "";
|
||||||
let ft8MessageHistory = [];
|
let ft8MessageHistory = [];
|
||||||
let ft8Paused = false;
|
let ft8BarActiveDecoder = "ft8";
|
||||||
let ft8BufferedWhilePaused = 0;
|
const ft8BarBuilders = {};
|
||||||
|
const ft8BarDismissedAtMsByDecoder = {
|
||||||
|
ft8: 0,
|
||||||
|
ft4: 0,
|
||||||
|
ft2: 0,
|
||||||
|
};
|
||||||
|
|
||||||
function currentFt8HistoryRetentionMs() {
|
function currentFt8HistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -39,6 +48,17 @@ function scheduleFt8BarUpdate() {
|
|||||||
scheduleFt8Ui("ft8-bar", () => updateFt8Bar());
|
scheduleFt8Ui("ft8-bar", () => updateFt8Bar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.registerFt8FamilyBarRenderer = function(decoder, builder) {
|
||||||
|
if (!FT8_BAR_DECODER_LABELS[decoder] || typeof builder !== "function") return;
|
||||||
|
ft8BarBuilders[decoder] = builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.setFt8FamilyBarDecoder = function(decoder) {
|
||||||
|
if (!FT8_BAR_DECODER_LABELS[decoder]) return;
|
||||||
|
ft8BarActiveDecoder = decoder;
|
||||||
|
scheduleFt8BarUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeFt8DisplayFreqHz(freqHz) {
|
function normalizeFt8DisplayFreqHz(freqHz) {
|
||||||
const rawHz = Number(freqHz);
|
const rawHz = Number(freqHz);
|
||||||
if (!Number.isFinite(rawHz)) return null;
|
if (!Number.isFinite(rawHz)) return null;
|
||||||
@@ -81,36 +101,22 @@ function renderFt8Row(msg) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFt8PauseUi() {
|
|
||||||
if (!ft8PauseBtn) return;
|
|
||||||
ft8PauseBtn.textContent = ft8Paused ? "Resume" : "Pause";
|
|
||||||
ft8PauseBtn.classList.toggle("active", ft8Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFt8History() {
|
function renderFt8History() {
|
||||||
pruneFt8MessageHistory();
|
pruneFt8MessageHistory();
|
||||||
if (!ft8MessagesEl || ft8Paused) {
|
if (!ft8MessagesEl) return;
|
||||||
updateFt8PauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < ft8MessageHistory.length; i += 1) {
|
for (let i = 0; i < ft8MessageHistory.length; i += 1) {
|
||||||
fragment.appendChild(renderFt8Row(ft8MessageHistory[i]));
|
fragment.appendChild(renderFt8Row(ft8MessageHistory[i]));
|
||||||
}
|
}
|
||||||
ft8MessagesEl.replaceChildren(fragment);
|
ft8MessagesEl.replaceChildren(fragment);
|
||||||
updateFt8PauseUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFt8Message(msg) {
|
function addFt8Message(msg) {
|
||||||
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
ft8MessageHistory.unshift(msg);
|
ft8MessageHistory.unshift(msg);
|
||||||
pruneFt8MessageHistory();
|
pruneFt8MessageHistory();
|
||||||
|
ft8BarActiveDecoder = "ft8";
|
||||||
scheduleFt8BarUpdate();
|
scheduleFt8BarUpdate();
|
||||||
if (ft8Paused) {
|
|
||||||
ft8BufferedWhilePaused += 1;
|
|
||||||
updateFt8PauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleFt8HistoryRender();
|
scheduleFt8HistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +147,7 @@ function normalizeServerFt8Message(msg) {
|
|||||||
|
|
||||||
window.onServerFt8Batch = function(messages) {
|
window.onServerFt8Batch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
ft8Status.textContent = ft8Paused ? "Paused" : "Receiving";
|
ft8Status.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerFt8Message(msg);
|
const next = normalizeServerFt8Message(msg);
|
||||||
@@ -158,12 +164,8 @@ window.onServerFt8Batch = function(messages) {
|
|||||||
normalized.reverse();
|
normalized.reverse();
|
||||||
ft8MessageHistory = normalized.concat(ft8MessageHistory);
|
ft8MessageHistory = normalized.concat(ft8MessageHistory);
|
||||||
pruneFt8MessageHistory();
|
pruneFt8MessageHistory();
|
||||||
|
ft8BarActiveDecoder = "ft8";
|
||||||
scheduleFt8BarUpdate();
|
scheduleFt8BarUpdate();
|
||||||
if (ft8Paused) {
|
|
||||||
ft8BufferedWhilePaused += messages.length;
|
|
||||||
updateFt8PauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleFt8HistoryRender();
|
scheduleFt8HistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,19 +185,14 @@ function ft8BarRfText(msg) {
|
|||||||
return `${displayFreqHz.toFixed(0)} Hz`;
|
return `${displayFreqHz.toFixed(0)} Hz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFt8Bar() {
|
function buildFt8BarFrames() {
|
||||||
if (!ft8BarOverlay) return;
|
|
||||||
const modeUpper = (document.getElementById("mode")?.value || "").toUpperCase();
|
|
||||||
const isFt8Mode = modeUpper === "DIG" || modeUpper === "USB";
|
|
||||||
const cutoffMs = Date.now() - FT8_BAR_WINDOW_MS;
|
const cutoffMs = Date.now() - FT8_BAR_WINDOW_MS;
|
||||||
const messages = ft8MessageHistory.filter((msg) => Number(msg.ts_ms) >= cutoffMs).slice(0, 8);
|
const messages = ft8MessageHistory.filter((msg) => Number(msg.ts_ms) >= cutoffMs).slice(0, 8);
|
||||||
if (!isFt8Mode || messages.length === 0) {
|
const newestTsMs = messages.reduce((latest, msg) => Math.max(latest, Number(msg.ts_ms) || 0), 0);
|
||||||
ft8BarOverlay.style.display = "none";
|
if (messages.length === 0) {
|
||||||
ft8BarOverlay.innerHTML = "";
|
return { count: 0, newestTsMs: 0, html: "" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
let html = "";
|
||||||
let html = '<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">FT8</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearFt8Bar()" onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearFt8Bar();}" aria-label="Clear FT8 overlay">Clear</span></span><span class="aprs-bar-window">Last 15 minutes</span></div>';
|
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const ts = msg.ts_ms ? `<span class="aprs-bar-time">${fmtTime(msg.ts_ms)}</span>` : "";
|
const ts = msg.ts_ms ? `<span class="aprs-bar-time">${fmtTime(msg.ts_ms)}</span>` : "";
|
||||||
const snr = Number.isFinite(msg.snr_db) ? `${msg.snr_db.toFixed(1)} dB` : "-- dB";
|
const snr = Number.isFinite(msg.snr_db) ? `${msg.snr_db.toFixed(1)} dB` : "-- dB";
|
||||||
@@ -205,13 +202,48 @@ function updateFt8Bar() {
|
|||||||
const text = ft8EscapeHtml((msg.message || "").toString());
|
const text = ft8EscapeHtml((msg.message || "").toString());
|
||||||
html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${ts}<span class="aprs-bar-call">${text}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
|
html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${ts}<span class="aprs-bar-call">${text}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
|
||||||
}
|
}
|
||||||
ft8BarOverlay.innerHTML = html;
|
return { count: messages.length, newestTsMs, html };
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFt8Bar() {
|
||||||
|
if (!ft8BarOverlay) return;
|
||||||
|
const modeUpper = (document.getElementById("mode")?.value || "").toUpperCase();
|
||||||
|
const isFt8Mode = modeUpper === "DIG" || modeUpper === "USB";
|
||||||
|
const decoder = ft8BarActiveDecoder;
|
||||||
|
const builder = ft8BarBuilders[decoder];
|
||||||
|
const label = FT8_BAR_DECODER_LABELS[decoder] || "FT8";
|
||||||
|
const result = typeof builder === "function" ? builder() : null;
|
||||||
|
const newestTsMs = Number(result?.newestTsMs) || 0;
|
||||||
|
if (!isFt8Mode || !result || result.count === 0 || newestTsMs <= (ft8BarDismissedAtMsByDecoder[decoder] || 0)) {
|
||||||
|
ft8BarOverlay.style.display = "none";
|
||||||
|
ft8BarOverlay.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ft8BarOverlay.innerHTML = `<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">${label}</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-actions"><span class="aprs-bar-window">Last 15 minutes</span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearFt8Bar()" onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();window.clearFt8Bar();}" aria-label="Clear ${label} overlay">Clear</span></span><button class="aprs-bar-close" type="button" onclick="window.closeFt8Bar()" aria-label="Close ${label} overlay">×</button></span></div>${result.html}`;
|
||||||
ft8BarOverlay.style.display = "flex";
|
ft8BarOverlay.style.display = "flex";
|
||||||
}
|
}
|
||||||
window.updateFt8Bar = updateFt8Bar;
|
window.updateFt8Bar = updateFt8Bar;
|
||||||
window.clearFt8Bar = function() {
|
window.clearFt8Bar = function() {
|
||||||
document.getElementById("ft8-clear-btn")?.click();
|
const decoder = ft8BarActiveDecoder;
|
||||||
|
if (decoder === "ft4") {
|
||||||
|
window.resetFt4HistoryView?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (decoder === "ft2") {
|
||||||
|
window.resetFt2HistoryView?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.resetFt8HistoryView?.();
|
||||||
};
|
};
|
||||||
|
window.closeFt8Bar = function() {
|
||||||
|
ft8BarDismissedAtMsByDecoder[ft8BarActiveDecoder] = Date.now();
|
||||||
|
if (ft8BarOverlay) {
|
||||||
|
ft8BarOverlay.style.display = "none";
|
||||||
|
ft8BarOverlay.innerHTML = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.registerFt8FamilyBarRenderer("ft8", buildFt8BarFrames);
|
||||||
|
|
||||||
function renderFt8Message(message) {
|
function renderFt8Message(message) {
|
||||||
let out = "";
|
let out = "";
|
||||||
@@ -387,7 +419,6 @@ window.updateFt8RfDisplay = function() {
|
|||||||
window.resetFt8HistoryView = function() {
|
window.resetFt8HistoryView = function() {
|
||||||
ft8MessagesEl.innerHTML = "";
|
ft8MessagesEl.innerHTML = "";
|
||||||
ft8MessageHistory = [];
|
ft8MessageHistory = [];
|
||||||
ft8BufferedWhilePaused = 0;
|
|
||||||
updateFt8Bar();
|
updateFt8Bar();
|
||||||
renderFt8History();
|
renderFt8History();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ft8");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("ft8");
|
||||||
@@ -414,34 +445,22 @@ if (ft8MessagesEl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ft8PauseBtn) {
|
|
||||||
ft8PauseBtn.addEventListener("click", () => {
|
|
||||||
ft8Paused = !ft8Paused;
|
|
||||||
if (!ft8Paused) {
|
|
||||||
ft8BufferedWhilePaused = 0;
|
|
||||||
renderFt8History();
|
|
||||||
} else {
|
|
||||||
updateFt8PauseUi();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("ft8-decode-toggle-btn").addEventListener("click", async () => {
|
document.getElementById("ft8-decode-toggle-btn").addEventListener("click", async () => {
|
||||||
try { await postPath("/toggle_ft8_decode"); } catch (e) { console.error("FT8 toggle failed", e); }
|
try { await postPath("/toggle_ft8_decode"); } catch (e) { console.error("FT8 toggle failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("ft8-clear-btn").addEventListener("click", async () => {
|
document.getElementById("settings-clear-ft8-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_ft8_decode");
|
await postPath("/clear_ft8_decode");
|
||||||
window.resetFt8HistoryView();
|
window.resetFt8HistoryView();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("FT8 clear failed", e);
|
console.error("FT8 history clear failed", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Server-side FT8 decode handler ---
|
// --- Server-side FT8 decode handler ---
|
||||||
window.onServerFt8 = function(msg) {
|
window.onServerFt8 = function(msg) {
|
||||||
ft8Status.textContent = ft8Paused ? "Paused" : "Receiving";
|
ft8Status.textContent = "Receiving";
|
||||||
const next = normalizeServerFt8Message(msg);
|
const next = normalizeServerFt8Message(msg);
|
||||||
if (next.grids.length > 0 && window.mapAddLocator) {
|
if (next.grids.length > 0 && window.mapAddLocator) {
|
||||||
window.mapAddLocator(next.raw, next.grids, "ft8", next.station, {
|
window.mapAddLocator(next.raw, next.grids, "ft8", next.station, {
|
||||||
@@ -452,5 +471,3 @@ window.onServerFt8 = function(msg) {
|
|||||||
}
|
}
|
||||||
addFt8Message(next.history);
|
addFt8Message(next.history);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFt8PauseUi();
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
const hfAprsStatus = document.getElementById("hf-aprs-status");
|
const hfAprsStatus = document.getElementById("hf-aprs-status");
|
||||||
const hfAprsPacketsEl = document.getElementById("hf-aprs-packets");
|
const hfAprsPacketsEl = document.getElementById("hf-aprs-packets");
|
||||||
const hfAprsFilterInput = document.getElementById("hf-aprs-filter");
|
const hfAprsFilterInput = document.getElementById("hf-aprs-filter");
|
||||||
const hfAprsPauseBtn = document.getElementById("hf-aprs-pause-btn");
|
|
||||||
const hfAprsOnlyPosBtn = document.getElementById("hf-aprs-only-pos-btn");
|
const hfAprsOnlyPosBtn = document.getElementById("hf-aprs-only-pos-btn");
|
||||||
const hfAprsHideCrcBtn = document.getElementById("hf-aprs-hide-crc-btn");
|
const hfAprsHideCrcBtn = document.getElementById("hf-aprs-hide-crc-btn");
|
||||||
const hfAprsCollapseDupBtn = document.getElementById("hf-aprs-collapse-dup-btn");
|
const hfAprsCollapseDupBtn = document.getElementById("hf-aprs-collapse-dup-btn");
|
||||||
@@ -11,8 +10,6 @@ const hfAprsVisibleCountEl = document.getElementById("hf-aprs-visible-count");
|
|||||||
const hfAprsLatestSeenEl = document.getElementById("hf-aprs-latest-seen");
|
const hfAprsLatestSeenEl = document.getElementById("hf-aprs-latest-seen");
|
||||||
let hfAprsFilterText = "";
|
let hfAprsFilterText = "";
|
||||||
let hfAprsPacketHistory = [];
|
let hfAprsPacketHistory = [];
|
||||||
let hfAprsPaused = false;
|
|
||||||
let hfAprsBufferedWhilePaused = 0;
|
|
||||||
let hfAprsOnlyPos = false;
|
let hfAprsOnlyPos = false;
|
||||||
let hfAprsHideCrc = false;
|
let hfAprsHideCrc = false;
|
||||||
let hfAprsCollapseDup = false;
|
let hfAprsCollapseDup = false;
|
||||||
@@ -138,11 +135,7 @@ function updateHfAprsSummary() {
|
|||||||
hfAprsTotalCountEl.textContent = `${hfAprsPacketHistory.length} total`;
|
hfAprsTotalCountEl.textContent = `${hfAprsPacketHistory.length} total`;
|
||||||
}
|
}
|
||||||
if (hfAprsVisibleCountEl) {
|
if (hfAprsVisibleCountEl) {
|
||||||
let text = `${visible.length} shown`;
|
hfAprsVisibleCountEl.textContent = `${visible.length} shown`;
|
||||||
if (hfAprsPaused && hfAprsBufferedWhilePaused > 0) {
|
|
||||||
text += ` · ${hfAprsBufferedWhilePaused} buffered`;
|
|
||||||
}
|
|
||||||
hfAprsVisibleCountEl.textContent = text;
|
|
||||||
}
|
}
|
||||||
if (hfAprsLatestSeenEl) {
|
if (hfAprsLatestSeenEl) {
|
||||||
const latest = hfAprsPacketHistory[0];
|
const latest = hfAprsPacketHistory[0];
|
||||||
@@ -161,10 +154,6 @@ function updateHfAprsChipState() {
|
|||||||
hfAprsOnlyPosBtn?.classList.toggle("active", hfAprsOnlyPos);
|
hfAprsOnlyPosBtn?.classList.toggle("active", hfAprsOnlyPos);
|
||||||
hfAprsHideCrcBtn?.classList.toggle("active", hfAprsHideCrc);
|
hfAprsHideCrcBtn?.classList.toggle("active", hfAprsHideCrc);
|
||||||
hfAprsCollapseDupBtn?.classList.toggle("active", hfAprsCollapseDup);
|
hfAprsCollapseDupBtn?.classList.toggle("active", hfAprsCollapseDup);
|
||||||
if (hfAprsPauseBtn) {
|
|
||||||
hfAprsPauseBtn.textContent = hfAprsPaused ? "Resume" : "Pause";
|
|
||||||
hfAprsPauseBtn.classList.toggle("active", hfAprsPaused);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHfAprsInfo(pkt) {
|
function renderHfAprsInfo(pkt) {
|
||||||
@@ -307,7 +296,7 @@ function renderHfAprsRow(pkt, isFresh) {
|
|||||||
|
|
||||||
function renderHfAprsHistory() {
|
function renderHfAprsHistory() {
|
||||||
pruneHfAprsPacketHistory();
|
pruneHfAprsPacketHistory();
|
||||||
if (!hfAprsPacketsEl || hfAprsPaused) {
|
if (!hfAprsPacketsEl) {
|
||||||
updateHfAprsSummary();
|
updateHfAprsSummary();
|
||||||
updateHfAprsChipState();
|
updateHfAprsChipState();
|
||||||
return;
|
return;
|
||||||
@@ -325,7 +314,6 @@ function renderHfAprsHistory() {
|
|||||||
window.resetHfAprsHistoryView = function() {
|
window.resetHfAprsHistoryView = function() {
|
||||||
if (hfAprsPacketsEl) hfAprsPacketsEl.innerHTML = "";
|
if (hfAprsPacketsEl) hfAprsPacketsEl.innerHTML = "";
|
||||||
hfAprsPacketHistory = [];
|
hfAprsPacketHistory = [];
|
||||||
hfAprsBufferedWhilePaused = 0;
|
|
||||||
renderHfAprsHistory();
|
renderHfAprsHistory();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -342,13 +330,6 @@ function addHfAprsPacket(pkt) {
|
|||||||
hfAprsPacketHistory.unshift(pkt);
|
hfAprsPacketHistory.unshift(pkt);
|
||||||
pruneHfAprsPacketHistory();
|
pruneHfAprsPacketHistory();
|
||||||
|
|
||||||
if (hfAprsPaused) {
|
|
||||||
hfAprsBufferedWhilePaused += 1;
|
|
||||||
updateHfAprsSummary();
|
|
||||||
updateHfAprsChipState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleHfAprsHistoryRender();
|
scheduleHfAprsHistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +353,7 @@ function normalizeServerHfAprsPacket(pkt) {
|
|||||||
|
|
||||||
window.onServerHfAprsBatch = function(packets) {
|
window.onServerHfAprsBatch = function(packets) {
|
||||||
if (!Array.isArray(packets) || packets.length === 0) return;
|
if (!Array.isArray(packets) || packets.length === 0) return;
|
||||||
if (hfAprsStatus) hfAprsStatus.textContent = hfAprsPaused ? "Paused" : "Receiving";
|
if (hfAprsStatus) hfAprsStatus.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const pkt of packets) {
|
for (const pkt of packets) {
|
||||||
const next = normalizeServerHfAprsPacket(pkt);
|
const next = normalizeServerHfAprsPacket(pkt);
|
||||||
@@ -384,12 +365,6 @@ window.onServerHfAprsBatch = function(packets) {
|
|||||||
normalized.reverse();
|
normalized.reverse();
|
||||||
hfAprsPacketHistory = normalized.concat(hfAprsPacketHistory);
|
hfAprsPacketHistory = normalized.concat(hfAprsPacketHistory);
|
||||||
pruneHfAprsPacketHistory();
|
pruneHfAprsPacketHistory();
|
||||||
if (hfAprsPaused) {
|
|
||||||
hfAprsBufferedWhilePaused += packets.length;
|
|
||||||
updateHfAprsSummary();
|
|
||||||
updateHfAprsChipState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleHfAprsHistoryRender();
|
scheduleHfAprsHistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -401,28 +376,15 @@ document.getElementById("hf-aprs-decode-toggle-btn")?.addEventListener("click",
|
|||||||
try { await postPath("/toggle_hf_aprs_decode"); } catch (e) { console.error("HF APRS toggle failed", e); }
|
try { await postPath("/toggle_hf_aprs_decode"); } catch (e) { console.error("HF APRS toggle failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("hf-aprs-clear-btn")?.addEventListener("click", async () => {
|
document.getElementById("settings-clear-hf-aprs-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_hf_aprs_decode");
|
await postPath("/clear_hf_aprs_decode");
|
||||||
window.resetHfAprsHistoryView();
|
window.resetHfAprsHistoryView();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("HF APRS clear failed", e);
|
console.error("HF APRS history clear failed", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hfAprsPauseBtn) {
|
|
||||||
hfAprsPauseBtn.addEventListener("click", () => {
|
|
||||||
hfAprsPaused = !hfAprsPaused;
|
|
||||||
if (!hfAprsPaused) {
|
|
||||||
hfAprsBufferedWhilePaused = 0;
|
|
||||||
renderHfAprsHistory();
|
|
||||||
} else {
|
|
||||||
updateHfAprsSummary();
|
|
||||||
updateHfAprsChipState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hfAprsOnlyPosBtn) {
|
if (hfAprsOnlyPosBtn) {
|
||||||
hfAprsOnlyPosBtn.addEventListener("click", () => {
|
hfAprsOnlyPosBtn.addEventListener("click", () => {
|
||||||
hfAprsOnlyPos = !hfAprsOnlyPos;
|
hfAprsOnlyPos = !hfAprsOnlyPos;
|
||||||
@@ -462,7 +424,7 @@ if (hfAprsFilterInput) {
|
|||||||
|
|
||||||
// --- Server-side HF APRS decode handler ---
|
// --- Server-side HF APRS decode handler ---
|
||||||
window.onServerHfAprs = function(pkt) {
|
window.onServerHfAprs = function(pkt) {
|
||||||
if (hfAprsStatus) hfAprsStatus.textContent = hfAprsPaused ? "Paused" : "Receiving";
|
if (hfAprsStatus) hfAprsStatus.textContent = "Receiving";
|
||||||
addHfAprsPacket(normalizeServerHfAprsPacket(pkt));
|
addHfAprsPacket(normalizeServerHfAprsPacket(pkt));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
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 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");
|
||||||
const vdesFrameCountEl = document.getElementById("vdes-frame-count");
|
const vdesFrameCountEl = document.getElementById("vdes-frame-count");
|
||||||
@@ -11,8 +9,6 @@ const vdesLatestSeenEl = document.getElementById("vdes-latest-seen");
|
|||||||
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 currentVdesHistoryRetentionMs() {
|
function currentVdesHistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -76,20 +72,12 @@ function updateVdesSummary() {
|
|||||||
}
|
}
|
||||||
if (vdesFrameCountEl) {
|
if (vdesFrameCountEl) {
|
||||||
const count = vdesMessageHistory.length;
|
const count = vdesMessageHistory.length;
|
||||||
let text = `${count} burst${count === 1 ? "" : "s"}`;
|
vdesFrameCountEl.textContent = `${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) {
|
||||||
@@ -227,20 +215,19 @@ function updateVdesBar() {
|
|||||||
}
|
}
|
||||||
window.updateVdesBar = updateVdesBar;
|
window.updateVdesBar = updateVdesBar;
|
||||||
window.clearVdesBar = function() {
|
window.clearVdesBar = function() {
|
||||||
document.getElementById("vdes-clear-btn")?.click();
|
window.resetVdesHistoryView();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.resetVdesHistoryView = function() {
|
window.resetVdesHistoryView = function() {
|
||||||
if (vdesMessagesEl) vdesMessagesEl.innerHTML = "";
|
if (vdesMessagesEl) vdesMessagesEl.innerHTML = "";
|
||||||
vdesMessageHistory = [];
|
vdesMessageHistory = [];
|
||||||
vdesBufferedWhilePaused = 0;
|
|
||||||
updateVdesBar();
|
updateVdesBar();
|
||||||
renderVdesHistory();
|
renderVdesHistory();
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderVdesHistory() {
|
function renderVdesHistory() {
|
||||||
pruneVdesMessageHistory();
|
pruneVdesMessageHistory();
|
||||||
if (!vdesMessagesEl || vdesPaused) {
|
if (!vdesMessagesEl) {
|
||||||
updateVdesSummary();
|
updateVdesSummary();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -264,13 +251,7 @@ function addVdesMessage(msg) {
|
|||||||
vdesMessageHistory.unshift(msg);
|
vdesMessageHistory.unshift(msg);
|
||||||
pruneVdesMessageHistory();
|
pruneVdesMessageHistory();
|
||||||
scheduleVdesBarUpdate();
|
scheduleVdesBarUpdate();
|
||||||
|
scheduleVdesHistoryRender();
|
||||||
if (vdesPaused) {
|
|
||||||
vdesBufferedWhilePaused += 1;
|
|
||||||
updateVdesSummary();
|
|
||||||
} else {
|
|
||||||
scheduleVdesHistoryRender();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeServerVdesMessage(msg) {
|
function normalizeServerVdesMessage(msg) {
|
||||||
@@ -303,7 +284,7 @@ function normalizeServerVdesMessage(msg) {
|
|||||||
|
|
||||||
window.onServerVdesBatch = function(messages) {
|
window.onServerVdesBatch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
if (vdesStatus) vdesStatus.textContent = vdesPaused ? "Paused" : "Receiving";
|
if (vdesStatus) vdesStatus.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerVdesMessage(msg);
|
const next = normalizeServerVdesMessage(msg);
|
||||||
@@ -323,11 +304,6 @@ window.onServerVdesBatch = function(messages) {
|
|||||||
vdesMessageHistory = normalized.concat(vdesMessageHistory);
|
vdesMessageHistory = normalized.concat(vdesMessageHistory);
|
||||||
pruneVdesMessageHistory();
|
pruneVdesMessageHistory();
|
||||||
scheduleVdesBarUpdate();
|
scheduleVdesBarUpdate();
|
||||||
if (vdesPaused) {
|
|
||||||
vdesBufferedWhilePaused += messages.length;
|
|
||||||
updateVdesSummary();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleVdesHistoryRender();
|
scheduleVdesHistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -335,28 +311,14 @@ window.restoreVdesHistory = function(messages) {
|
|||||||
window.onServerVdesBatch(messages);
|
window.onServerVdesBatch(messages);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (vdesClearBtn) {
|
document.getElementById("settings-clear-vdes-history")?.addEventListener("click", async () => {
|
||||||
vdesClearBtn.addEventListener("click", async () => {
|
try {
|
||||||
try {
|
await postPath("/clear_vdes_decode");
|
||||||
await postPath("/clear_vdes_decode");
|
window.resetVdesHistoryView();
|
||||||
window.resetVdesHistoryView();
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error("VDES history clear failed", e);
|
||||||
console.error("VDES clear failed", e);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vdesPauseBtn) {
|
|
||||||
vdesPauseBtn.addEventListener("click", () => {
|
|
||||||
vdesPaused = !vdesPaused;
|
|
||||||
if (!vdesPaused) {
|
|
||||||
vdesBufferedWhilePaused = 0;
|
|
||||||
renderVdesHistory();
|
|
||||||
} else {
|
|
||||||
updateVdesSummary();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vdesFilterInput) {
|
if (vdesFilterInput) {
|
||||||
vdesFilterInput.addEventListener("input", () => {
|
vdesFilterInput.addEventListener("input", () => {
|
||||||
@@ -366,7 +328,7 @@ if (vdesFilterInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.onServerVdes = function(msg) {
|
window.onServerVdes = function(msg) {
|
||||||
if (vdesStatus) vdesStatus.textContent = vdesPaused ? "Paused" : "Receiving";
|
if (vdesStatus) vdesStatus.textContent = "Receiving";
|
||||||
const next = normalizeServerVdesMessage(msg);
|
const next = normalizeServerVdesMessage(msg);
|
||||||
addVdesMessage(next);
|
addVdesMessage(next);
|
||||||
if (next.lat != null && next.lon != null && window.vdesMapAddPoint) {
|
if (next.lat != null && next.lon != null && window.vdesMapAddPoint) {
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ 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_PERIOD_SECONDS = 120;
|
const WSPR_PERIOD_SECONDS = 120;
|
||||||
let wsprFilterText = "";
|
let wsprFilterText = "";
|
||||||
let wsprMessageHistory = [];
|
let wsprMessageHistory = [];
|
||||||
let wsprPaused = false;
|
|
||||||
let wsprBufferedWhilePaused = 0;
|
|
||||||
|
|
||||||
function currentWsprHistoryRetentionMs() {
|
function currentWsprHistoryRetentionMs() {
|
||||||
return typeof window.getDecodeHistoryRetentionMs === "function"
|
return typeof window.getDecodeHistoryRetentionMs === "function"
|
||||||
@@ -62,35 +59,20 @@ function renderWsprRow(msg) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWsprPauseUi() {
|
|
||||||
if (!wsprPauseBtn) return;
|
|
||||||
wsprPauseBtn.textContent = wsprPaused ? "Resume" : "Pause";
|
|
||||||
wsprPauseBtn.classList.toggle("active", wsprPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderWsprHistory() {
|
function renderWsprHistory() {
|
||||||
pruneWsprMessageHistory();
|
pruneWsprMessageHistory();
|
||||||
if (!wsprMessagesEl || wsprPaused) {
|
if (!wsprMessagesEl) return;
|
||||||
updateWsprPauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < wsprMessageHistory.length; i += 1) {
|
for (let i = 0; i < wsprMessageHistory.length; i += 1) {
|
||||||
fragment.appendChild(renderWsprRow(wsprMessageHistory[i]));
|
fragment.appendChild(renderWsprRow(wsprMessageHistory[i]));
|
||||||
}
|
}
|
||||||
wsprMessagesEl.replaceChildren(fragment);
|
wsprMessagesEl.replaceChildren(fragment);
|
||||||
updateWsprPauseUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWsprMessage(msg) {
|
function addWsprMessage(msg) {
|
||||||
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
msg._tsMs = Number.isFinite(msg?.ts_ms) ? Number(msg.ts_ms) : Date.now();
|
||||||
wsprMessageHistory.unshift(msg);
|
wsprMessageHistory.unshift(msg);
|
||||||
pruneWsprMessageHistory();
|
pruneWsprMessageHistory();
|
||||||
if (wsprPaused) {
|
|
||||||
wsprBufferedWhilePaused += 1;
|
|
||||||
updateWsprPauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleWsprHistoryRender();
|
scheduleWsprHistoryRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +102,7 @@ function normalizeServerWsprMessage(msg) {
|
|||||||
|
|
||||||
window.onServerWsprBatch = function(messages) {
|
window.onServerWsprBatch = function(messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
wsprStatus.textContent = wsprPaused ? "Paused" : "Receiving";
|
wsprStatus.textContent = "Receiving";
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerWsprMessage(msg);
|
const next = normalizeServerWsprMessage(msg);
|
||||||
@@ -136,11 +118,6 @@ window.onServerWsprBatch = function(messages) {
|
|||||||
normalized.reverse();
|
normalized.reverse();
|
||||||
wsprMessageHistory = normalized.concat(wsprMessageHistory);
|
wsprMessageHistory = normalized.concat(wsprMessageHistory);
|
||||||
pruneWsprMessageHistory();
|
pruneWsprMessageHistory();
|
||||||
if (wsprPaused) {
|
|
||||||
wsprBufferedWhilePaused += messages.length;
|
|
||||||
updateWsprPauseUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scheduleWsprHistoryRender();
|
scheduleWsprHistoryRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,7 +230,6 @@ function applyWsprFilterToAll() {
|
|||||||
window.resetWsprHistoryView = function() {
|
window.resetWsprHistoryView = function() {
|
||||||
wsprMessagesEl.innerHTML = "";
|
wsprMessagesEl.innerHTML = "";
|
||||||
wsprMessageHistory = [];
|
wsprMessageHistory = [];
|
||||||
wsprBufferedWhilePaused = 0;
|
|
||||||
renderWsprHistory();
|
renderWsprHistory();
|
||||||
if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr");
|
if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr");
|
||||||
};
|
};
|
||||||
@@ -279,33 +255,21 @@ if (wsprMessagesEl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wsprPauseBtn) {
|
|
||||||
wsprPauseBtn.addEventListener("click", () => {
|
|
||||||
wsprPaused = !wsprPaused;
|
|
||||||
if (!wsprPaused) {
|
|
||||||
wsprBufferedWhilePaused = 0;
|
|
||||||
renderWsprHistory();
|
|
||||||
} else {
|
|
||||||
updateWsprPauseUi();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("wspr-decode-toggle-btn").addEventListener("click", async () => {
|
document.getElementById("wspr-decode-toggle-btn").addEventListener("click", async () => {
|
||||||
try { await postPath("/toggle_wspr_decode"); } catch (e) { console.error("WSPR toggle failed", e); }
|
try { await postPath("/toggle_wspr_decode"); } catch (e) { console.error("WSPR toggle failed", e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("wspr-clear-btn").addEventListener("click", async () => {
|
document.getElementById("settings-clear-wspr-history")?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await postPath("/clear_wspr_decode");
|
await postPath("/clear_wspr_decode");
|
||||||
window.resetWsprHistoryView();
|
window.resetWsprHistoryView();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("WSPR clear failed", e);
|
console.error("WSPR history clear failed", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onServerWspr = function(msg) {
|
window.onServerWspr = function(msg) {
|
||||||
wsprStatus.textContent = wsprPaused ? "Paused" : "Receiving";
|
wsprStatus.textContent = "Receiving";
|
||||||
const next = normalizeServerWsprMessage(msg);
|
const next = normalizeServerWsprMessage(msg);
|
||||||
if (next.grids.length > 0 && window.mapAddLocator) {
|
if (next.grids.length > 0 && window.mapAddLocator) {
|
||||||
window.mapAddLocator(next.raw, next.grids, "wspr", next.station, {
|
window.mapAddLocator(next.raw, next.grids, "wspr", next.station, {
|
||||||
@@ -315,5 +279,3 @@ window.onServerWspr = function(msg) {
|
|||||||
}
|
}
|
||||||
addWsprMessage(next.history);
|
addWsprMessage(next.history);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateWsprPauseUi();
|
|
||||||
|
|||||||
@@ -821,7 +821,6 @@ small { color: var(--text-muted); }
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.aprs-bar-window {
|
.aprs-bar-window {
|
||||||
margin-left: auto;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
@@ -831,6 +830,13 @@ small { color: var(--text-muted); }
|
|||||||
text-transform: none;
|
text-transform: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.aprs-bar-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.38rem;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
.aprs-bar-clear-wrap {
|
.aprs-bar-clear-wrap {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -864,6 +870,30 @@ small { color: var(--text-muted); }
|
|||||||
.aprs-bar-clear-wrap:hover {
|
.aprs-bar-clear-wrap:hover {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
.aprs-bar-close {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.05rem;
|
||||||
|
height: 1.05rem;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.95em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.82;
|
||||||
|
transition: opacity 120ms, color 120ms, background-color 120ms;
|
||||||
|
}
|
||||||
|
.aprs-bar-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--accent-green);
|
||||||
|
background: color-mix(in srgb, var(--btn-bg) 70%, transparent);
|
||||||
|
}
|
||||||
.aprs-bar-frame {
|
.aprs-bar-frame {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
font-size: clamp(0.56rem, 0.92vw, 0.7rem);
|
font-size: clamp(0.56rem, 0.92vw, 0.7rem);
|
||||||
|
|||||||
Reference in New Issue
Block a user