diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wxsat.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wxsat.js
index 83384df..d79989b 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wxsat.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wxsat.js
@@ -1,10 +1,27 @@
// --- Weather Satellite Decoder Plugin ---
+// Live view: decoder state, latest image card
+// History view: filterable table of all decoded images
+
+// ── DOM references ──────────────────────────────────────────────────
const wxsatStatus = document.getElementById("wxsat-status");
-const wxsatImagesEl = document.getElementById("wxsat-images");
+const wxsatLiveView = document.getElementById("wxsat-live-view");
+const wxsatHistoryView = document.getElementById("wxsat-history-view");
+const wxsatLiveLatest = document.getElementById("wxsat-live-latest");
+const wxsatHistoryList = document.getElementById("wxsat-history-list");
+const wxsatHistoryCount = document.getElementById("wxsat-history-count");
+const wxsatFilterInput = document.getElementById("wxsat-filter");
+const wxsatSortSelect = document.getElementById("wxsat-sort");
+const wxsatTypeFilter = document.getElementById("wxsat-type-filter");
+const wxsatAptState = document.getElementById("wxsat-apt-state");
+const wxsatLrptState = document.getElementById("wxsat-lrpt-state");
+// ── State ───────────────────────────────────────────────────────────
let wxsatImageHistory = [];
-const WXSAT_MAX_IMAGES = 20;
+const WXSAT_MAX_IMAGES = 100;
+let wxsatFilterText = "";
+let wxsatActiveView = "live"; // "live" | "history"
+// ── UI scheduler helper ─────────────────────────────────────────────
function scheduleWxsatUi(key, job) {
if (typeof window.trxScheduleUiFrameJob === "function") {
window.trxScheduleUiFrameJob(key, job);
@@ -13,48 +30,160 @@ function scheduleWxsatUi(key, job) {
job();
}
-function renderWxsatImage(img) {
- const card = document.createElement("div");
- card.className = "wxsat-image-card";
- card.style.cssText =
- "border:1px solid var(--border-color);border-radius:0.5rem;padding:0.5rem;margin-bottom:0.75rem;background:var(--bg-secondary);";
+// ── View switching ──────────────────────────────────────────────────
+const wxsatViewLiveBtn = document.getElementById("wxsat-view-live");
+const wxsatViewHistoryBtn = document.getElementById("wxsat-view-history");
- const ts = img._ts || new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
+function switchWxsatView(view) {
+ wxsatActiveView = view;
+ if (wxsatLiveView) wxsatLiveView.style.display = view === "live" ? "" : "none";
+ if (wxsatHistoryView) wxsatHistoryView.style.display = view === "history" ? "" : "none";
+ if (wxsatViewLiveBtn) {
+ wxsatViewLiveBtn.classList.toggle("wxsat-view-active", view === "live");
+ }
+ if (wxsatViewHistoryBtn) {
+ wxsatViewHistoryBtn.classList.toggle("wxsat-view-active", view === "history");
+ }
+ if (view === "history") {
+ renderWxsatHistoryTable();
+ }
+}
+
+wxsatViewLiveBtn?.addEventListener("click", () => switchWxsatView("live"));
+wxsatViewHistoryBtn?.addEventListener("click", () => switchWxsatView("history"));
+
+// ── Live view: decoder state ────────────────────────────────────────
+// Updated from app.js render() via window.updateWxsatLiveState
+window.updateWxsatLiveState = function (update) {
+ if (!wxsatAptState || !wxsatLrptState) return;
+ const aptOn = !!update.wxsat_decode_enabled;
+ const lrptOn = !!update.lrpt_decode_enabled;
+
+ wxsatAptState.textContent = aptOn ? "Listening" : "Idle";
+ wxsatAptState.className = "wxsat-live-value " + (aptOn ? "wxsat-state-listening" : "wxsat-state-idle");
+ wxsatLrptState.textContent = lrptOn ? "Listening" : "Idle";
+ wxsatLrptState.className = "wxsat-live-value " + (lrptOn ? "wxsat-state-listening" : "wxsat-state-idle");
+};
+
+function renderWxsatLatestCard() {
+ if (!wxsatLiveLatest) return;
+ if (wxsatImageHistory.length === 0) {
+ wxsatLiveLatest.innerHTML =
+ '
No images decoded yet. Enable a decoder and wait for a satellite pass.
';
+ return;
+ }
+
+ const img = wxsatImageHistory[0];
const decoder = img._decoder || "unknown";
+ const typeName = decoder === "lrpt" ? "Meteor LRPT" : "NOAA APT";
const satellite = img.satellite || "";
- const channels = img.channels || "";
+ const channels = img.channels || img.channel_a || "";
const lines = img.line_count || img.mcu_count || 0;
+ const unit = decoder === "lrpt" ? "MCU rows" : "lines";
+ const ts = img._ts || "--";
+ const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : "";
- let metaParts = [`
${decoder === "lrpt" ? "Meteor LRPT" : "NOAA APT"}`];
- if (satellite) metaParts.push(satellite);
- if (channels) metaParts.push("ch " + channels);
- metaParts.push(lines + (decoder === "lrpt" ? " MCU rows" : " lines"));
- metaParts.push(ts);
-
- card.innerHTML =
- `
${metaParts.join(" · ")}
`;
+ let meta = [typeName];
+ if (satellite) meta.push(satellite);
+ if (channels) meta.push(channels);
+ meta.push(`${lines} ${unit}`);
+ meta.push(`${date} ${ts}`);
+ let html = `
`;
+ html += `
Latest decoded image
`;
+ html += `
${meta.join(" · ")}
`;
if (img.path) {
- const link = document.createElement("a");
- link.href = img.path;
- link.target = "_blank";
- link.textContent = "Download image";
- link.style.cssText = "font-size:0.8rem;color:var(--accent);";
- card.appendChild(link);
+ html += `
Download PNG`;
}
-
- return card;
+ html += `
`;
+ wxsatLiveLatest.innerHTML = html;
}
-function renderWxsatHistory() {
- if (!wxsatImagesEl) return;
+// ── History view: table ─────────────────────────────────────────────
+function getFilteredHistory() {
+ let items = wxsatImageHistory;
+
+ // Type filter
+ const typeVal = wxsatTypeFilter ? wxsatTypeFilter.value : "all";
+ if (typeVal === "apt") items = items.filter((i) => i._decoder === "apt");
+ else if (typeVal === "lrpt") items = items.filter((i) => i._decoder === "lrpt");
+
+ // Text filter
+ if (wxsatFilterText) {
+ items = items.filter((i) => {
+ const haystack = [
+ i._decoder === "lrpt" ? "meteor lrpt" : "noaa apt",
+ i.satellite || "",
+ i.channels || "",
+ i.channel_a || "",
+ i.channel_b || "",
+ ]
+ .join(" ")
+ .toUpperCase();
+ return haystack.includes(wxsatFilterText);
+ });
+ }
+
+ // Sort
+ const sortVal = wxsatSortSelect ? wxsatSortSelect.value : "newest";
+ if (sortVal === "oldest") {
+ items = items.slice().reverse();
+ }
+
+ return items;
+}
+
+function renderWxsatHistoryRow(img) {
+ const row = document.createElement("div");
+ row.className = "wxsat-history-row";
+
+ const decoder = img._decoder || "unknown";
+ const typeName = decoder === "lrpt" ? "Meteor LRPT" : "NOAA APT";
+ const typeClass = decoder === "lrpt" ? "wxsat-type-lrpt" : "wxsat-type-apt";
+ const ts = img._ts || "--";
+ const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: "short", day: "numeric" }) : "";
+ const satellite = img.satellite || "--";
+ const channels = decoder === "lrpt" ? (img.channels || "--") : (img.channel_a && img.channel_b ? `A:${img.channel_a} B:${img.channel_b}` : img.channel_a || "--");
+ const lines = img.line_count || img.mcu_count || 0;
+ const unit = decoder === "lrpt" ? "MCU" : "ln";
+ const link = img.path
+ ? `
PNG`
+ : "--";
+
+ row.innerHTML = [
+ `
${date} ${ts}`,
+ `
${typeName}`,
+ `
${satellite}`,
+ `
${channels}`,
+ `
${lines} ${unit}`,
+ `
${link}`,
+ ].join("");
+
+ return row;
+}
+
+function renderWxsatHistoryTable() {
+ if (!wxsatHistoryList) return;
+ const items = getFilteredHistory();
const fragment = document.createDocumentFragment();
- for (let i = 0; i < wxsatImageHistory.length; i += 1) {
- fragment.appendChild(renderWxsatImage(wxsatImageHistory[i]));
+ for (let i = 0; i < items.length; i += 1) {
+ fragment.appendChild(renderWxsatHistoryRow(items[i]));
+ }
+ wxsatHistoryList.replaceChildren(fragment);
+
+ if (wxsatHistoryCount) {
+ const total = wxsatImageHistory.length;
+ const shown = items.length;
+ wxsatHistoryCount.textContent =
+ total === 0
+ ? "No images yet"
+ : shown === total
+ ? `${total} image${total === 1 ? "" : "s"}`
+ : `${shown} of ${total} images`;
}
- wxsatImagesEl.replaceChildren(fragment);
}
+// ── Add image to history ────────────────────────────────────────────
function addWxsatImage(img, decoder) {
const tsMs = Number.isFinite(img.ts_ms) ? Number(img.ts_ms) : Date.now();
img._tsMs = tsMs;
@@ -69,10 +198,14 @@ function addWxsatImage(img, decoder) {
if (wxsatImageHistory.length > WXSAT_MAX_IMAGES) {
wxsatImageHistory = wxsatImageHistory.slice(0, WXSAT_MAX_IMAGES);
}
- scheduleWxsatUi("wxsat-history", () => renderWxsatHistory());
+
+ scheduleWxsatUi("wxsat-latest", () => renderWxsatLatestCard());
+ if (wxsatActiveView === "history") {
+ scheduleWxsatUi("wxsat-history", () => renderWxsatHistoryTable());
+ }
}
-// Server-dispatched callbacks
+// ── Server callbacks ────────────────────────────────────────────────
window.onServerWxsatImage = function (msg) {
if (wxsatStatus) wxsatStatus.textContent = "Image received (NOAA APT)";
addWxsatImage(msg, "apt");
@@ -85,10 +218,17 @@ window.onServerLrptImage = function (msg) {
window.resetWxsatHistoryView = function () {
wxsatImageHistory = [];
- if (wxsatImagesEl) wxsatImagesEl.innerHTML = "";
+ if (wxsatHistoryList) wxsatHistoryList.innerHTML = "";
+ renderWxsatLatestCard();
+ renderWxsatHistoryTable();
};
-// Toggle buttons
+window.pruneWxsatHistoryView = function () {
+ renderWxsatHistoryTable();
+ renderWxsatLatestCard();
+};
+
+// ── Toggle buttons ──────────────────────────────────────────────────
const wxsatDecodeToggleBtn = document.getElementById("wxsat-decode-toggle-btn");
wxsatDecodeToggleBtn?.addEventListener("click", async () => {
try {
@@ -109,7 +249,16 @@ lrptDecodeToggleBtn?.addEventListener("click", async () => {
}
});
-// Clear history button
+// ── Filter / sort event listeners ───────────────────────────────────
+wxsatFilterInput?.addEventListener("input", () => {
+ wxsatFilterText = wxsatFilterInput.value.trim().toUpperCase();
+ renderWxsatHistoryTable();
+});
+
+wxsatSortSelect?.addEventListener("change", () => renderWxsatHistoryTable());
+wxsatTypeFilter?.addEventListener("change", () => renderWxsatHistoryTable());
+
+// ── Settings: clear history ─────────────────────────────────────────
document
.getElementById("settings-clear-wxsat-history")
?.addEventListener("click", async () => {
@@ -122,5 +271,6 @@ document
}
});
-// Initial render
-renderWxsatHistory();
+// ── Initial render ──────────────────────────────────────────────────
+renderWxsatLatestCard();
+renderWxsatHistoryTable();
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
index 119dae6..4a54e1e 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
@@ -4538,3 +4538,30 @@ button:focus-visible, input:focus-visible, select:focus-visible {
width: 100%;
}
}
+/* ── Weather Satellite panel ────────────────────────────────────────── */
+.wxsat-view-bar { display: flex; gap: 0; margin-bottom: 0.75rem; border-bottom: 1px solid var(--border); }
+.wxsat-view-btn { flex-shrink: 0; background: transparent; border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: 0.3rem 0.9rem; color: var(--text-muted); cursor: pointer; font-size: 0.82rem; }
+.wxsat-view-active { border-bottom-color: var(--accent-green); color: var(--accent-green); font-weight: 600; }
+.wxsat-view-btn:hover:not(.wxsat-view-active) { color: var(--text); }
+.wxsat-live-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.5rem; }
+.wxsat-live-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 0.35rem; padding: 0.5rem 0.75rem; display: flex; flex-direction: column; gap: 0.15rem; }
+.wxsat-live-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
+.wxsat-live-value { font-size: 0.9rem; font-weight: 600; }
+.wxsat-state-idle { color: var(--text-muted); }
+.wxsat-state-listening { color: var(--accent-green); }
+.wxsat-state-decoding { color: #f0a020; }
+.wxsat-history-controls { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; }
+.wxsat-sort-select { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 0.25rem; color: var(--text); padding: 0.25rem 0.4rem; font-size: 0.82rem; }
+.wxsat-history-header { display: grid; grid-template-columns: 7rem 5.5rem 9rem 6rem 4.5rem 1fr; gap: 0.25rem; padding: 0.25rem 0.4rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; border-bottom: 1px solid var(--border); }
+.wxsat-history-row { display: grid; grid-template-columns: 7rem 5.5rem 9rem 6rem 4.5rem 1fr; gap: 0.25rem; padding: 0.35rem 0.4rem; font-size: 0.82rem; border-bottom: 1px solid var(--border-faint, rgba(255,255,255,0.04)); }
+.wxsat-history-row:hover { background: var(--bg-hover, rgba(255,255,255,0.02)); }
+.wxsat-col-type { font-weight: 500; }
+.wxsat-type-apt { color: #6ec6ff; }
+.wxsat-type-lrpt { color: #b39ddb; }
+.wxsat-latest-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 0.4rem; padding: 0.6rem 0.75rem; }
+.wxsat-latest-card .wxsat-latest-title { font-size: 0.82rem; font-weight: 600; margin-bottom: 0.25rem; }
+.wxsat-latest-card .wxsat-latest-meta { font-size: 0.78rem; color: var(--text-muted); }
+@media (max-width: 600px) {
+ .wxsat-live-grid { grid-template-columns: 1fr; }
+ .wxsat-history-header, .wxsat-history-row { grid-template-columns: 5rem 4rem 6rem 4rem 3.5rem 1fr; font-size: 0.75rem; }
+}