[refactor](trx-frontend-http): extract satellite scheduling UI into dedicated module
Move ~230 lines of satellite pass scheduling code from scheduler.js into a new sat-scheduler.js plugin with cached DOM refs, createElement- based rendering, and a clean bridge API. Refactor sat.js predictions view to deduplicate row builders, extract countdown timer lifecycle management, and cache all DOM references. https://claude.ai/code/session_0144nUfHAKs7yRnYTsozNagw Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1393,6 +1393,7 @@
|
|||||||
<script src="/sat.js"></script>
|
<script src="/sat.js"></script>
|
||||||
<script src="/bookmarks.js"></script>
|
<script src="/bookmarks.js"></script>
|
||||||
<script src="/scheduler.js"></script>
|
<script src="/scheduler.js"></script>
|
||||||
|
<script src="/sat-scheduler.js"></script>
|
||||||
<script src="/background-decode.js"></script>
|
<script src="/background-decode.js"></script>
|
||||||
<script src="/vchan.js"></script>
|
<script src="/vchan.js"></script>
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// Satellite Pass Scheduling UI
|
||||||
|
// Manages the satellite overlay section within the background decoding scheduler.
|
||||||
|
// Communicates with scheduler.js via a thin window API for shared state access.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// ── DOM references (cached once) ──────────────────────────────────
|
||||||
|
const dom = {
|
||||||
|
enabled: document.getElementById("scheduler-sat-enabled"),
|
||||||
|
pretune: document.getElementById("scheduler-sat-pretune"),
|
||||||
|
body: document.getElementById("scheduler-sat-body"),
|
||||||
|
tbody: document.getElementById("scheduler-sat-tbody"),
|
||||||
|
addBtn: document.getElementById("scheduler-sat-add-btn"),
|
||||||
|
passStatus: document.getElementById("scheduler-sat-pass-status"),
|
||||||
|
formWrap: document.getElementById("sch-sat-form-wrap"),
|
||||||
|
formTitle: document.getElementById("sch-sat-form-title"),
|
||||||
|
form: document.getElementById("sch-sat-form"),
|
||||||
|
formCancel: document.getElementById("sch-sat-form-cancel"),
|
||||||
|
preset: document.getElementById("scheduler-sat-preset"),
|
||||||
|
name: document.getElementById("scheduler-sat-name"),
|
||||||
|
norad: document.getElementById("scheduler-sat-norad"),
|
||||||
|
bookmark: document.getElementById("scheduler-sat-bookmark"),
|
||||||
|
minEl: document.getElementById("scheduler-sat-min-el"),
|
||||||
|
priority: document.getElementById("scheduler-sat-priority"),
|
||||||
|
centerHz: document.getElementById("scheduler-sat-center-hz"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Local state ───────────────────────────────────────────────────
|
||||||
|
let editIdx = null; // null = adding, number = editing
|
||||||
|
|
||||||
|
// ── Scheduler bridge ──────────────────────────────────────────────
|
||||||
|
// These accessors call into scheduler.js via window.schedulerBridge,
|
||||||
|
// which is set up by scheduler.js after it initializes.
|
||||||
|
function getBridge() {
|
||||||
|
return window.schedulerBridge || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig() {
|
||||||
|
const b = getBridge();
|
||||||
|
return typeof b.getConfig === "function" ? b.getConfig() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus() {
|
||||||
|
const b = getBridge();
|
||||||
|
return typeof b.getStatus === "function" ? b.getStatus() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBookmarks() {
|
||||||
|
const b = getBridge();
|
||||||
|
return typeof b.getBookmarks === "function" ? b.getBookmarks() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function bmName(id) {
|
||||||
|
const bm = getBookmarks().find(function (b) { return b.id === id; });
|
||||||
|
return bm ? bm.name : String(id || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(s) {
|
||||||
|
return String(s)
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFreq(hz) {
|
||||||
|
if (hz >= 1e6) return (hz / 1e6).toFixed(3) + " MHz";
|
||||||
|
if (hz >= 1e3) return (hz / 1e3).toFixed(1) + " kHz";
|
||||||
|
return hz + " Hz";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Satellite config helpers ──────────────────────────────────────
|
||||||
|
function getSatelliteEntries() {
|
||||||
|
var config = getConfig();
|
||||||
|
return (config && config.satellites && Array.isArray(config.satellites.entries))
|
||||||
|
? config.satellites.entries
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSatelliteConfig() {
|
||||||
|
var config = getConfig();
|
||||||
|
if (!config) return { enabled: false, pretune_secs: 60, entries: [] };
|
||||||
|
if (!config.satellites) config.satellites = { enabled: false, pretune_secs: 60, entries: [] };
|
||||||
|
if (!config.satellites.entries) config.satellites.entries = [];
|
||||||
|
return config.satellites;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectSatelliteConfig() {
|
||||||
|
var enabled = dom.enabled ? dom.enabled.checked : false;
|
||||||
|
var pretune = dom.pretune ? parseInt(dom.pretune.value, 10) : 60;
|
||||||
|
return {
|
||||||
|
enabled: enabled,
|
||||||
|
pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune,
|
||||||
|
entries: getSatelliteEntries(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render: section ───────────────────────────────────────────────
|
||||||
|
function renderSection() {
|
||||||
|
var config = getConfig();
|
||||||
|
var satCfg = (config && config.satellites) || {};
|
||||||
|
var enabled = !!satCfg.enabled;
|
||||||
|
|
||||||
|
if (dom.enabled) dom.enabled.checked = enabled;
|
||||||
|
if (dom.pretune) dom.pretune.value = satCfg.pretune_secs != null ? satCfg.pretune_secs : 60;
|
||||||
|
if (dom.body) dom.body.style.display = enabled ? "" : "none";
|
||||||
|
|
||||||
|
renderEntries();
|
||||||
|
renderPassStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render: entries table ─────────────────────────────────────────
|
||||||
|
function renderEntries() {
|
||||||
|
if (!dom.tbody) return;
|
||||||
|
var entries = getSatelliteEntries();
|
||||||
|
var frag = document.createDocumentFragment();
|
||||||
|
|
||||||
|
entries.forEach(function (entry, idx) {
|
||||||
|
var tr = document.createElement("tr");
|
||||||
|
|
||||||
|
var tdSat = document.createElement("td");
|
||||||
|
tdSat.textContent = entry.satellite || "";
|
||||||
|
tr.appendChild(tdSat);
|
||||||
|
|
||||||
|
var tdNorad = document.createElement("td");
|
||||||
|
tdNorad.textContent = entry.norad_id || "";
|
||||||
|
tr.appendChild(tdNorad);
|
||||||
|
|
||||||
|
var tdBm = document.createElement("td");
|
||||||
|
tdBm.textContent = bmName(entry.bookmark_id);
|
||||||
|
tr.appendChild(tdBm);
|
||||||
|
|
||||||
|
var tdEl = document.createElement("td");
|
||||||
|
tdEl.textContent = (entry.min_elevation_deg != null ? entry.min_elevation_deg + "\u00B0" : "5\u00B0");
|
||||||
|
tr.appendChild(tdEl);
|
||||||
|
|
||||||
|
var tdPrio = document.createElement("td");
|
||||||
|
tdPrio.textContent = entry.priority || 0;
|
||||||
|
tr.appendChild(tdPrio);
|
||||||
|
|
||||||
|
var tdActions = document.createElement("td");
|
||||||
|
|
||||||
|
var editBtn = document.createElement("button");
|
||||||
|
editBtn.className = "sch-write";
|
||||||
|
editBtn.type = "button";
|
||||||
|
editBtn.textContent = "Edit";
|
||||||
|
editBtn.addEventListener("click", function () {
|
||||||
|
openForm(entry, idx);
|
||||||
|
});
|
||||||
|
tdActions.appendChild(editBtn);
|
||||||
|
|
||||||
|
var removeBtn = document.createElement("button");
|
||||||
|
removeBtn.className = "sch-write";
|
||||||
|
removeBtn.type = "button";
|
||||||
|
removeBtn.textContent = "Remove";
|
||||||
|
removeBtn.addEventListener("click", function () {
|
||||||
|
removeEntry(idx);
|
||||||
|
});
|
||||||
|
tdActions.appendChild(removeBtn);
|
||||||
|
|
||||||
|
tr.appendChild(tdActions);
|
||||||
|
frag.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.tbody.replaceChildren(frag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render: pass status ───────────────────────────────────────────
|
||||||
|
function renderPassStatus() {
|
||||||
|
if (!dom.passStatus) return;
|
||||||
|
var entries = getSatelliteEntries();
|
||||||
|
if (entries.length === 0) {
|
||||||
|
dom.passStatus.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var status = getStatus();
|
||||||
|
if (status && status.active_satellite) {
|
||||||
|
dom.passStatus.innerHTML =
|
||||||
|
'<span class="sch-sat-active-badge">PASS ACTIVE: ' +
|
||||||
|
escHtml(status.active_satellite) +
|
||||||
|
'</span>';
|
||||||
|
} else {
|
||||||
|
dom.passStatus.innerHTML =
|
||||||
|
'<span style="color:var(--text-muted);font-size:0.8rem;">No satellite pass active. Predictions available in the SAT tab.</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render: bookmark dropdown ─────────────────────────────────────
|
||||||
|
function renderBookmarkSelect(selectedId) {
|
||||||
|
if (!dom.bookmark) return;
|
||||||
|
dom.bookmark.innerHTML = '<option value="">— none —</option>';
|
||||||
|
getBookmarks().forEach(function (bm) {
|
||||||
|
var opt = document.createElement("option");
|
||||||
|
opt.value = bm.id;
|
||||||
|
opt.textContent = bm.name + " (" + formatFreq(bm.freq_hz) + " " + bm.mode + ")";
|
||||||
|
if (bm.id === selectedId) opt.selected = true;
|
||||||
|
dom.bookmark.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Entry management ──────────────────────────────────────────────
|
||||||
|
function removeEntry(idx) {
|
||||||
|
var sat = ensureSatelliteConfig();
|
||||||
|
sat.entries.splice(idx, 1);
|
||||||
|
renderEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Form: open ────────────────────────────────────────────────────
|
||||||
|
function openForm(entry, idx) {
|
||||||
|
editIdx = (idx != null) ? idx : null;
|
||||||
|
|
||||||
|
if (dom.formTitle) dom.formTitle.textContent = entry ? "Edit Satellite" : "Add Satellite";
|
||||||
|
if (dom.preset) dom.preset.value = "";
|
||||||
|
if (dom.name) dom.name.value = entry ? (entry.satellite || "") : "";
|
||||||
|
if (dom.norad) dom.norad.value = entry ? (entry.norad_id || "") : "";
|
||||||
|
if (dom.minEl) dom.minEl.value = entry && entry.min_elevation_deg != null ? entry.min_elevation_deg : 5;
|
||||||
|
if (dom.priority) dom.priority.value = entry && entry.priority != null ? entry.priority : 0;
|
||||||
|
if (dom.centerHz) dom.centerHz.value = entry && entry.center_hz ? entry.center_hz : "";
|
||||||
|
|
||||||
|
renderBookmarkSelect(entry ? entry.bookmark_id : null);
|
||||||
|
|
||||||
|
if (dom.formWrap) {
|
||||||
|
dom.formWrap.style.display = "flex";
|
||||||
|
if (dom.name) dom.name.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Form: close ───────────────────────────────────────────────────
|
||||||
|
function closeForm() {
|
||||||
|
if (dom.formWrap) dom.formWrap.style.display = "none";
|
||||||
|
editIdx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Form: submit ──────────────────────────────────────────────────
|
||||||
|
function onFormSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var satellite = dom.name ? dom.name.value.trim() : "";
|
||||||
|
var noradId = dom.norad ? parseInt(dom.norad.value, 10) : NaN;
|
||||||
|
var bmId = dom.bookmark ? dom.bookmark.value : "";
|
||||||
|
|
||||||
|
if (!satellite) { alert("Please enter a satellite name."); return; }
|
||||||
|
if (isNaN(noradId) || noradId <= 0) { alert("Please enter a valid NORAD catalog number."); return; }
|
||||||
|
if (!bmId) { alert("Please select a bookmark."); return; }
|
||||||
|
|
||||||
|
var minEl = dom.minEl ? parseFloat(dom.minEl.value) : 5;
|
||||||
|
var prio = dom.priority ? parseInt(dom.priority.value, 10) : 0;
|
||||||
|
var centerHzRaw = dom.centerHz ? parseInt(dom.centerHz.value, 10) : NaN;
|
||||||
|
|
||||||
|
var sat = ensureSatelliteConfig();
|
||||||
|
|
||||||
|
var entryData = {
|
||||||
|
satellite: satellite,
|
||||||
|
norad_id: noradId,
|
||||||
|
bookmark_id: bmId,
|
||||||
|
min_elevation_deg: isNaN(minEl) ? 5 : minEl,
|
||||||
|
priority: isNaN(prio) ? 0 : prio,
|
||||||
|
center_hz: !isNaN(centerHzRaw) && centerHzRaw > 0 ? centerHzRaw : null,
|
||||||
|
bookmark_ids: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editIdx !== null) {
|
||||||
|
var existing = sat.entries[editIdx];
|
||||||
|
entryData.id = existing ? existing.id : ("sat_" + Date.now().toString(36));
|
||||||
|
sat.entries[editIdx] = entryData;
|
||||||
|
} else {
|
||||||
|
entryData.id = "sat_" + Date.now().toString(36);
|
||||||
|
sat.entries.push(entryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeForm();
|
||||||
|
renderEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Preset change handler ─────────────────────────────────────────
|
||||||
|
function onPresetChange() {
|
||||||
|
if (!dom.preset || !dom.preset.value) return;
|
||||||
|
var parts = dom.preset.value.split("|");
|
||||||
|
if (dom.name) dom.name.value = parts[0] || "";
|
||||||
|
if (dom.norad) dom.norad.value = parts[1] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Wire all events ───────────────────────────────────────────────
|
||||||
|
function wireEvents() {
|
||||||
|
if (dom.enabled) {
|
||||||
|
dom.enabled.addEventListener("change", function () {
|
||||||
|
if (dom.body) dom.body.style.display = dom.enabled.checked ? "" : "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dom.addBtn) dom.addBtn.addEventListener("click", function () { openForm(null, null); });
|
||||||
|
if (dom.form) dom.form.addEventListener("submit", onFormSubmit);
|
||||||
|
if (dom.formCancel) dom.formCancel.addEventListener("click", closeForm);
|
||||||
|
if (dom.preset) dom.preset.addEventListener("change", onPresetChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public API ────────────────────────────────────────────────────
|
||||||
|
window.satScheduler = {
|
||||||
|
wireEvents: wireEvents,
|
||||||
|
renderSection: renderSection,
|
||||||
|
renderPassStatus: renderPassStatus,
|
||||||
|
collectSatelliteConfig: collectSatelliteConfig,
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -3,27 +3,46 @@
|
|||||||
// History view: filterable table of all decoded images
|
// History view: filterable table of all decoded images
|
||||||
// Predictions view: next 24 h passes for ham satellites
|
// Predictions view: next 24 h passes for ham satellites
|
||||||
|
|
||||||
// ── DOM references ──────────────────────────────────────────────────
|
// ── DOM references (cached once) ───────────────────────────────────
|
||||||
const satStatus = document.getElementById("sat-status");
|
const satDom = {
|
||||||
const satLiveView = document.getElementById("sat-live-view");
|
status: document.getElementById("sat-status"),
|
||||||
const satHistoryView = document.getElementById("sat-history-view");
|
liveView: document.getElementById("sat-live-view"),
|
||||||
const satPredictionsView = document.getElementById("sat-predictions-view");
|
historyView: document.getElementById("sat-history-view"),
|
||||||
const satLiveLatest = document.getElementById("sat-live-latest");
|
predictionsView: document.getElementById("sat-predictions-view"),
|
||||||
const satHistoryList = document.getElementById("sat-history-list");
|
liveLatest: document.getElementById("sat-live-latest"),
|
||||||
const satHistoryCount = document.getElementById("sat-history-count");
|
historyList: document.getElementById("sat-history-list"),
|
||||||
const satFilterInput = document.getElementById("sat-filter");
|
historyCount: document.getElementById("sat-history-count"),
|
||||||
const satSortSelect = document.getElementById("sat-sort");
|
filterInput: document.getElementById("sat-filter"),
|
||||||
const satTypeFilter = document.getElementById("sat-type-filter");
|
sortSelect: document.getElementById("sat-sort"),
|
||||||
const satAptState = document.getElementById("sat-apt-state");
|
typeFilter: document.getElementById("sat-type-filter"),
|
||||||
const satLrptState = document.getElementById("sat-lrpt-state");
|
aptState: document.getElementById("sat-apt-state"),
|
||||||
|
lrptState: document.getElementById("sat-lrpt-state"),
|
||||||
|
viewLiveBtn: document.getElementById("sat-view-live"),
|
||||||
|
viewHistoryBtn: document.getElementById("sat-view-history"),
|
||||||
|
viewPredBtn: document.getElementById("sat-view-predictions"),
|
||||||
|
predFilter: document.getElementById("sat-pred-filter"),
|
||||||
|
predMinEl: document.getElementById("sat-pred-min-el"),
|
||||||
|
predCategory: document.getElementById("sat-pred-category"),
|
||||||
|
predCurrentList: document.getElementById("sat-pred-current-list"),
|
||||||
|
predUpcomingList: document.getElementById("sat-pred-list"),
|
||||||
|
predCurrentSec: document.getElementById("sat-pred-current-section"),
|
||||||
|
predUpcomingSec: document.getElementById("sat-pred-upcoming-section"),
|
||||||
|
predStatus: document.getElementById("sat-pred-status"),
|
||||||
|
};
|
||||||
|
|
||||||
// ── State ───────────────────────────────────────────────────────────
|
// ── State ───────────────────────────────────────────────────────────
|
||||||
let satImageHistory = [];
|
let satImageHistory = [];
|
||||||
const SAT_MAX_IMAGES = 100;
|
const SAT_MAX_IMAGES = 100;
|
||||||
const SAT_PRED_PAGE_SIZE = 50; // max rows before "Show more"
|
const SAT_PRED_PAGE_SIZE = 50;
|
||||||
let satPredShowAll = false;
|
let satPredShowAll = false;
|
||||||
let satFilterText = "";
|
let satFilterText = "";
|
||||||
let satActiveView = "live"; // "live" | "history" | "predictions"
|
let satActiveView = "live"; // "live" | "history" | "predictions"
|
||||||
|
let satPredData = [];
|
||||||
|
let satPredFilterText = "";
|
||||||
|
let satPredMinEl = 0;
|
||||||
|
let satPredCategory = "all";
|
||||||
|
let satPredSatCount = 0;
|
||||||
|
let satPredCountdownTimer = null;
|
||||||
|
|
||||||
// ── UI scheduler helper ─────────────────────────────────────────────
|
// ── UI scheduler helper ─────────────────────────────────────────────
|
||||||
function scheduleSatUi(key, job) {
|
function scheduleSatUi(key, job) {
|
||||||
@@ -35,23 +54,16 @@ function scheduleSatUi(key, job) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── View switching ──────────────────────────────────────────────────
|
// ── View switching ──────────────────────────────────────────────────
|
||||||
const satViewLiveBtn = document.getElementById("sat-view-live");
|
|
||||||
const satViewHistoryBtn = document.getElementById("sat-view-history");
|
|
||||||
const satViewPredictionsBtn = document.getElementById("sat-view-predictions");
|
|
||||||
|
|
||||||
function switchSatView(view) {
|
function switchSatView(view) {
|
||||||
const leavingPredictions = satActiveView === "predictions" && view !== "predictions";
|
const leavingPredictions = satActiveView === "predictions" && view !== "predictions";
|
||||||
satActiveView = view;
|
satActiveView = view;
|
||||||
if (satLiveView) satLiveView.style.display = view === "live" ? "" : "none";
|
if (satDom.liveView) satDom.liveView.style.display = view === "live" ? "" : "none";
|
||||||
if (satHistoryView) satHistoryView.style.display = view === "history" ? "" : "none";
|
if (satDom.historyView) satDom.historyView.style.display = view === "history" ? "" : "none";
|
||||||
if (satPredictionsView) satPredictionsView.style.display = view === "predictions" ? "" : "none";
|
if (satDom.predictionsView) satDom.predictionsView.style.display = view === "predictions" ? "" : "none";
|
||||||
if (satViewLiveBtn) satViewLiveBtn.classList.toggle("sat-view-active", view === "live");
|
if (satDom.viewLiveBtn) satDom.viewLiveBtn.classList.toggle("sat-view-active", view === "live");
|
||||||
if (satViewHistoryBtn) satViewHistoryBtn.classList.toggle("sat-view-active", view === "history");
|
if (satDom.viewHistoryBtn) satDom.viewHistoryBtn.classList.toggle("sat-view-active", view === "history");
|
||||||
if (satViewPredictionsBtn) satViewPredictionsBtn.classList.toggle("sat-view-active", view === "predictions");
|
if (satDom.viewPredBtn) satDom.viewPredBtn.classList.toggle("sat-view-active", view === "predictions");
|
||||||
// Clear prediction DOM when leaving to reduce node count.
|
if (leavingPredictions) clearPredictionDom();
|
||||||
if (leavingPredictions) {
|
|
||||||
clearPredictionDom();
|
|
||||||
}
|
|
||||||
if (view === "history") {
|
if (view === "history") {
|
||||||
renderSatHistoryTable();
|
renderSatHistoryTable();
|
||||||
} else if (view === "predictions") {
|
} else if (view === "predictions") {
|
||||||
@@ -61,39 +73,38 @@ function switchSatView(view) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearPredictionDom() {
|
function clearPredictionDom() {
|
||||||
if (satPredCountdownTimer) { clearInterval(satPredCountdownTimer); satPredCountdownTimer = null; }
|
stopCountdownTimer();
|
||||||
if (satPredCurrentList) satPredCurrentList.innerHTML = "";
|
if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = "";
|
||||||
if (satPredUpcomingList) satPredUpcomingList.innerHTML = "";
|
if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = "";
|
||||||
}
|
}
|
||||||
window.clearSatPredictionDom = clearPredictionDom;
|
window.clearSatPredictionDom = clearPredictionDom;
|
||||||
|
|
||||||
satViewLiveBtn?.addEventListener("click", () => switchSatView("live"));
|
satDom.viewLiveBtn?.addEventListener("click", () => switchSatView("live"));
|
||||||
satViewHistoryBtn?.addEventListener("click", () => switchSatView("history"));
|
satDom.viewHistoryBtn?.addEventListener("click", () => switchSatView("history"));
|
||||||
satViewPredictionsBtn?.addEventListener("click", () => switchSatView("predictions"));
|
satDom.viewPredBtn?.addEventListener("click", () => switchSatView("predictions"));
|
||||||
|
|
||||||
// ── Live view: decoder state ────────────────────────────────────────
|
// ── Live view: decoder state ────────────────────────────────────────
|
||||||
// Updated from app.js render() via window.updateSatLiveState
|
|
||||||
let _lastSatAptOn = null, _lastSatLrptOn = null;
|
let _lastSatAptOn = null, _lastSatLrptOn = null;
|
||||||
window.updateSatLiveState = function (update) {
|
window.updateSatLiveState = function (update) {
|
||||||
if (!satAptState || !satLrptState) return;
|
if (!satDom.aptState || !satDom.lrptState) return;
|
||||||
const aptOn = !!update.wxsat_decode_enabled;
|
const aptOn = !!update.wxsat_decode_enabled;
|
||||||
const lrptOn = !!update.lrpt_decode_enabled;
|
const lrptOn = !!update.lrpt_decode_enabled;
|
||||||
if (aptOn !== _lastSatAptOn) {
|
if (aptOn !== _lastSatAptOn) {
|
||||||
_lastSatAptOn = aptOn;
|
_lastSatAptOn = aptOn;
|
||||||
satAptState.textContent = aptOn ? "Listening" : "Idle";
|
satDom.aptState.textContent = aptOn ? "Listening" : "Idle";
|
||||||
satAptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle");
|
satDom.aptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle");
|
||||||
}
|
}
|
||||||
if (lrptOn !== _lastSatLrptOn) {
|
if (lrptOn !== _lastSatLrptOn) {
|
||||||
_lastSatLrptOn = lrptOn;
|
_lastSatLrptOn = lrptOn;
|
||||||
satLrptState.textContent = lrptOn ? "Listening" : "Idle";
|
satDom.lrptState.textContent = lrptOn ? "Listening" : "Idle";
|
||||||
satLrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle");
|
satDom.lrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderSatLatestCard() {
|
function renderSatLatestCard() {
|
||||||
if (!satLiveLatest) return;
|
if (!satDom.liveLatest) return;
|
||||||
if (satImageHistory.length === 0) {
|
if (satImageHistory.length === 0) {
|
||||||
satLiveLatest.innerHTML =
|
satDom.liveLatest.innerHTML =
|
||||||
'<div style="color:var(--text-muted);font-size:0.82rem;">No images decoded yet. Enable a decoder and wait for a satellite pass.</div>';
|
'<div style="color:var(--text-muted);font-size:0.82rem;">No images decoded yet. Enable a decoder and wait for a satellite pass.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -124,19 +135,17 @@ function renderSatLatestCard() {
|
|||||||
html += ` <button type="button" class="sat-map-btn" onclick="window.satShowOnMap(${img.geo_bounds[0]},${img.geo_bounds[1]},${img.geo_bounds[2]},${img.geo_bounds[3]})" style="font-size:0.8rem;margin-top:0.25rem;margin-left:0.5rem;cursor:pointer;background:none;border:1px solid var(--accent);color:var(--accent);border-radius:3px;padding:1px 6px;">Show on Map</button>`;
|
html += ` <button type="button" class="sat-map-btn" onclick="window.satShowOnMap(${img.geo_bounds[0]},${img.geo_bounds[1]},${img.geo_bounds[2]},${img.geo_bounds[3]})" style="font-size:0.8rem;margin-top:0.25rem;margin-left:0.5rem;cursor:pointer;background:none;border:1px solid var(--accent);color:var(--accent);border-radius:3px;padding:1px 6px;">Show on Map</button>`;
|
||||||
}
|
}
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
satLiveLatest.innerHTML = html;
|
satDom.liveLatest.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── History view: table ─────────────────────────────────────────────
|
// ── History view: table ─────────────────────────────────────────────
|
||||||
function getSatFilteredHistory() {
|
function getSatFilteredHistory() {
|
||||||
let items = satImageHistory;
|
let items = satImageHistory;
|
||||||
|
|
||||||
// Type filter
|
const typeVal = satDom.typeFilter ? satDom.typeFilter.value : "all";
|
||||||
const typeVal = satTypeFilter ? satTypeFilter.value : "all";
|
|
||||||
if (typeVal === "apt") items = items.filter((i) => i._decoder === "apt");
|
if (typeVal === "apt") items = items.filter((i) => i._decoder === "apt");
|
||||||
else if (typeVal === "lrpt") items = items.filter((i) => i._decoder === "lrpt");
|
else if (typeVal === "lrpt") items = items.filter((i) => i._decoder === "lrpt");
|
||||||
|
|
||||||
// Text filter
|
|
||||||
if (satFilterText) {
|
if (satFilterText) {
|
||||||
items = items.filter((i) => {
|
items = items.filter((i) => {
|
||||||
const haystack = [
|
const haystack = [
|
||||||
@@ -145,18 +154,13 @@ function getSatFilteredHistory() {
|
|||||||
i.channels || "",
|
i.channels || "",
|
||||||
i.channel_a || "",
|
i.channel_a || "",
|
||||||
i.channel_b || "",
|
i.channel_b || "",
|
||||||
]
|
].join(" ").toUpperCase();
|
||||||
.join(" ")
|
|
||||||
.toUpperCase();
|
|
||||||
return haystack.includes(satFilterText);
|
return haystack.includes(satFilterText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort
|
const sortVal = satDom.sortSelect ? satDom.sortSelect.value : "newest";
|
||||||
const sortVal = satSortSelect ? satSortSelect.value : "newest";
|
if (sortVal === "oldest") items = items.slice().reverse();
|
||||||
if (sortVal === "oldest") {
|
|
||||||
items = items.slice().reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
@@ -194,18 +198,18 @@ function renderSatHistoryRow(img) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderSatHistoryTable() {
|
function renderSatHistoryTable() {
|
||||||
if (!satHistoryList) return;
|
if (!satDom.historyList) return;
|
||||||
const items = getSatFilteredHistory();
|
const items = getSatFilteredHistory();
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < items.length; i += 1) {
|
for (let i = 0; i < items.length; i += 1) {
|
||||||
fragment.appendChild(renderSatHistoryRow(items[i]));
|
fragment.appendChild(renderSatHistoryRow(items[i]));
|
||||||
}
|
}
|
||||||
satHistoryList.replaceChildren(fragment);
|
satDom.historyList.replaceChildren(fragment);
|
||||||
|
|
||||||
if (satHistoryCount) {
|
if (satDom.historyCount) {
|
||||||
const total = satImageHistory.length;
|
const total = satImageHistory.length;
|
||||||
const shown = items.length;
|
const shown = items.length;
|
||||||
satHistoryCount.textContent =
|
satDom.historyCount.textContent =
|
||||||
total === 0
|
total === 0
|
||||||
? "No images yet"
|
? "No images yet"
|
||||||
: shown === total
|
: shown === total
|
||||||
@@ -238,7 +242,7 @@ function addSatImage(img, decoder) {
|
|||||||
|
|
||||||
// ── Server callbacks ────────────────────────────────────────────────
|
// ── Server callbacks ────────────────────────────────────────────────
|
||||||
window.onServerSatImage = function (msg) {
|
window.onServerSatImage = function (msg) {
|
||||||
if (satStatus) satStatus.textContent = "Image received (NOAA APT)";
|
if (satDom.status) satDom.status.textContent = "Image received (NOAA APT)";
|
||||||
addSatImage(msg, "apt");
|
addSatImage(msg, "apt");
|
||||||
if (msg.geo_bounds && msg.path && window.addSatMapOverlay) {
|
if (msg.geo_bounds && msg.path && window.addSatMapOverlay) {
|
||||||
window.addSatMapOverlay(msg);
|
window.addSatMapOverlay(msg);
|
||||||
@@ -246,7 +250,7 @@ window.onServerSatImage = function (msg) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.onServerLrptImage = function (msg) {
|
window.onServerLrptImage = function (msg) {
|
||||||
if (satStatus) satStatus.textContent = "Image received (Meteor LRPT)";
|
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
|
||||||
addSatImage(msg, "lrpt");
|
addSatImage(msg, "lrpt");
|
||||||
if (msg.geo_bounds && msg.path && window.addSatMapOverlay) {
|
if (msg.geo_bounds && msg.path && window.addSatMapOverlay) {
|
||||||
window.addSatMapOverlay(msg);
|
window.addSatMapOverlay(msg);
|
||||||
@@ -255,7 +259,7 @@ window.onServerLrptImage = function (msg) {
|
|||||||
|
|
||||||
window.resetSatHistoryView = function () {
|
window.resetSatHistoryView = function () {
|
||||||
satImageHistory = [];
|
satImageHistory = [];
|
||||||
if (satHistoryList) satHistoryList.innerHTML = "";
|
if (satDom.historyList) satDom.historyList.innerHTML = "";
|
||||||
renderSatLatestCard();
|
renderSatLatestCard();
|
||||||
renderSatHistoryTable();
|
renderSatHistoryTable();
|
||||||
if (window.clearSatMapOverlays) window.clearSatMapOverlays();
|
if (window.clearSatMapOverlays) window.clearSatMapOverlays();
|
||||||
@@ -288,13 +292,13 @@ lrptDecodeToggleBtn?.addEventListener("click", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Filter / sort event listeners ───────────────────────────────────
|
// ── Filter / sort event listeners ───────────────────────────────────
|
||||||
satFilterInput?.addEventListener("input", () => {
|
satDom.filterInput?.addEventListener("input", () => {
|
||||||
satFilterText = satFilterInput.value.trim().toUpperCase();
|
satFilterText = satDom.filterInput.value.trim().toUpperCase();
|
||||||
renderSatHistoryTable();
|
renderSatHistoryTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
satSortSelect?.addEventListener("change", () => renderSatHistoryTable());
|
satDom.sortSelect?.addEventListener("change", () => renderSatHistoryTable());
|
||||||
satTypeFilter?.addEventListener("change", () => renderSatHistoryTable());
|
satDom.typeFilter?.addEventListener("change", () => renderSatHistoryTable());
|
||||||
|
|
||||||
// ── Settings: clear history ─────────────────────────────────────────
|
// ── Settings: clear history ─────────────────────────────────────────
|
||||||
document
|
document
|
||||||
@@ -309,55 +313,7 @@ document
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Predictions view ────────────────────────────────────────────────
|
// ── Predictions: helpers ────────────────────────────────────────────
|
||||||
let satPredData = [];
|
|
||||||
let satPredFilterText = "";
|
|
||||||
let satPredMinEl = 0;
|
|
||||||
let satPredCategory = "all";
|
|
||||||
let satPredSatCount = 0;
|
|
||||||
let satPredCountdownTimer = null;
|
|
||||||
const satPredFilterInput = document.getElementById("sat-pred-filter");
|
|
||||||
const satPredMinElSelect = document.getElementById("sat-pred-min-el");
|
|
||||||
const satPredCategorySelect = document.getElementById("sat-pred-category");
|
|
||||||
const satPredCurrentList = document.getElementById("sat-pred-current-list");
|
|
||||||
const satPredUpcomingList = document.getElementById("sat-pred-list");
|
|
||||||
const satPredCurrentSection = document.getElementById("sat-pred-current-section");
|
|
||||||
const satPredUpcomingSection = document.getElementById("sat-pred-upcoming-section");
|
|
||||||
const satPredStatus = document.getElementById("sat-pred-status");
|
|
||||||
|
|
||||||
function getFilteredPredictions() {
|
|
||||||
let items = satPredData;
|
|
||||||
if (satPredCategory !== "all") {
|
|
||||||
items = items.filter((p) => p.category === satPredCategory);
|
|
||||||
}
|
|
||||||
if (satPredMinEl > 0) {
|
|
||||||
items = items.filter((p) => p.max_elevation_deg >= satPredMinEl);
|
|
||||||
}
|
|
||||||
if (satPredFilterText) {
|
|
||||||
items = items.filter((p) => p.satellite.toUpperCase().includes(satPredFilterText));
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPredFilters() {
|
|
||||||
renderSatPredictions(getFilteredPredictions());
|
|
||||||
}
|
|
||||||
|
|
||||||
satPredFilterInput?.addEventListener("input", () => {
|
|
||||||
satPredFilterText = satPredFilterInput.value.trim().toUpperCase();
|
|
||||||
applyPredFilters();
|
|
||||||
});
|
|
||||||
|
|
||||||
satPredMinElSelect?.addEventListener("change", () => {
|
|
||||||
satPredMinEl = parseInt(satPredMinElSelect.value, 10) || 0;
|
|
||||||
applyPredFilters();
|
|
||||||
});
|
|
||||||
|
|
||||||
satPredCategorySelect?.addEventListener("change", () => {
|
|
||||||
satPredCategory = satPredCategorySelect.value;
|
|
||||||
applyPredFilters();
|
|
||||||
});
|
|
||||||
|
|
||||||
function azToCardinal(deg) {
|
function azToCardinal(deg) {
|
||||||
const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
|
const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
|
||||||
return dirs[Math.round(deg / 45) % 8];
|
return dirs[Math.round(deg / 45) % 8];
|
||||||
@@ -367,9 +323,7 @@ function formatPredTime(ms) {
|
|||||||
const d = new Date(ms);
|
const d = new Date(ms);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
const day = d.getUTCDay() !== now.getUTCDay()
|
const day = d.getUTCDay() !== now.getUTCDay() ? dayNames[d.getUTCDay()] + " " : "";
|
||||||
? dayNames[d.getUTCDay()] + " "
|
|
||||||
: "";
|
|
||||||
const hh = String(d.getUTCHours()).padStart(2, "0");
|
const hh = String(d.getUTCHours()).padStart(2, "0");
|
||||||
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
||||||
return `${day}${hh}:${mm}`;
|
return `${day}${hh}:${mm}`;
|
||||||
@@ -387,126 +341,27 @@ function formatCountdown(ms) {
|
|||||||
return `${m}:${String(s).padStart(2, "0")}`;
|
return `${m}:${String(s).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSatPredictions(passes, error) {
|
function elevationClass(deg) {
|
||||||
const currentList = satPredCurrentList;
|
if (deg >= 45) return "sat-pred-el-high";
|
||||||
const upcomingList = satPredUpcomingList;
|
if (deg >= 10) return "sat-pred-el-mid";
|
||||||
const currentSection = satPredCurrentSection;
|
return "sat-pred-el-low";
|
||||||
const upcomingSection = satPredUpcomingSection;
|
|
||||||
const status = satPredStatus;
|
|
||||||
|
|
||||||
// Stop any previous countdown timer
|
|
||||||
if (satPredCountdownTimer) { clearInterval(satPredCountdownTimer); satPredCountdownTimer = null; }
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
if (currentList) currentList.innerHTML = "";
|
|
||||||
if (upcomingList) upcomingList.innerHTML = "";
|
|
||||||
if (currentSection) currentSection.style.display = "none";
|
|
||||||
if (upcomingSection) upcomingSection.style.display = "none";
|
|
||||||
if (status) status.textContent = error;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(passes) || passes.length === 0) {
|
// ── Predictions: countdown timer management ─────────────────────────
|
||||||
if (currentList) currentList.innerHTML = "";
|
function stopCountdownTimer() {
|
||||||
if (upcomingList) upcomingList.innerHTML = "";
|
if (satPredCountdownTimer) {
|
||||||
if (currentSection) currentSection.style.display = "none";
|
|
||||||
if (upcomingSection) upcomingSection.style.display = "none";
|
|
||||||
if (status) status.textContent = "No passes found in the next 24 hours.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const current = passes.filter((p) => p.aos_ms <= now && p.los_ms > now);
|
|
||||||
const upcoming = passes.filter((p) => p.aos_ms > now);
|
|
||||||
|
|
||||||
// ── Current passes ──
|
|
||||||
if (currentSection) currentSection.style.display = current.length > 0 ? "" : "none";
|
|
||||||
if (currentList) {
|
|
||||||
if (current.length === 0) {
|
|
||||||
currentList.innerHTML = "";
|
|
||||||
} else {
|
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
for (const pass of current) {
|
|
||||||
const row = document.createElement("div");
|
|
||||||
row.className = "sat-pred-row-current";
|
|
||||||
const elClass = pass.max_elevation_deg >= 45
|
|
||||||
? "sat-pred-el-high"
|
|
||||||
: pass.max_elevation_deg >= 10
|
|
||||||
? "sat-pred-el-mid"
|
|
||||||
: "sat-pred-el-low";
|
|
||||||
const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`;
|
|
||||||
const remaining = Math.max(0, pass.los_ms - now);
|
|
||||||
row.innerHTML = [
|
|
||||||
`<span class="sat-pred-col-sat">${pass.satellite}</span>`,
|
|
||||||
`<span class="sat-pred-col-el ${elClass}">${pass.max_elevation_deg.toFixed(1)}°</span>`,
|
|
||||||
`<span class="sat-pred-col-time">${formatPredTime(pass.aos_ms)}</span>`,
|
|
||||||
`<span class="sat-pred-col-time">${formatPredTime(pass.los_ms)}</span>`,
|
|
||||||
`<span class="sat-pred-col-countdown" data-los="${pass.los_ms}">${formatCountdown(remaining)}</span>`,
|
|
||||||
`<span class="sat-pred-col-dir">${dir}</span>`,
|
|
||||||
].join("");
|
|
||||||
frag.appendChild(row);
|
|
||||||
}
|
|
||||||
currentList.replaceChildren(frag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Upcoming passes (capped to reduce DOM node count) ──
|
|
||||||
const upcomingLimit = satPredShowAll ? upcoming.length : SAT_PRED_PAGE_SIZE;
|
|
||||||
const visibleUpcoming = upcoming.slice(0, upcomingLimit);
|
|
||||||
const hiddenCount = upcoming.length - visibleUpcoming.length;
|
|
||||||
if (upcomingSection) upcomingSection.style.display = upcoming.length > 0 ? "" : "none";
|
|
||||||
if (upcomingList) {
|
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
for (const pass of visibleUpcoming) {
|
|
||||||
const row = document.createElement("div");
|
|
||||||
row.className = "sat-pred-row";
|
|
||||||
const elClass = pass.max_elevation_deg >= 45
|
|
||||||
? "sat-pred-el-high"
|
|
||||||
: pass.max_elevation_deg >= 10
|
|
||||||
? "sat-pred-el-mid"
|
|
||||||
: "sat-pred-el-low";
|
|
||||||
const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`;
|
|
||||||
row.innerHTML = [
|
|
||||||
`<span class="sat-pred-col-time">${formatPredTime(pass.aos_ms)}</span>`,
|
|
||||||
`<span class="sat-pred-col-sat">${pass.satellite}</span>`,
|
|
||||||
`<span class="sat-pred-col-el ${elClass}">${pass.max_elevation_deg.toFixed(1)}°</span>`,
|
|
||||||
`<span class="sat-pred-col-dur">${formatPredDuration(pass.duration_s)}</span>`,
|
|
||||||
`<span class="sat-pred-col-dir">${dir}</span>`,
|
|
||||||
].join("");
|
|
||||||
frag.appendChild(row);
|
|
||||||
}
|
|
||||||
if (hiddenCount > 0) {
|
|
||||||
const moreRow = document.createElement("div");
|
|
||||||
moreRow.className = "sat-pred-row";
|
|
||||||
moreRow.style.cursor = "pointer";
|
|
||||||
moreRow.style.textAlign = "center";
|
|
||||||
moreRow.innerHTML = `<span style="grid-column:1/-1;color:var(--accent);font-size:0.82rem;">Show ${hiddenCount} more passes\u2026</span>`;
|
|
||||||
moreRow.addEventListener("click", () => {
|
|
||||||
satPredShowAll = true;
|
|
||||||
renderSatPredictions(getFilteredPredictions());
|
|
||||||
});
|
|
||||||
frag.appendChild(moreRow);
|
|
||||||
}
|
|
||||||
upcomingList.replaceChildren(frag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Status ──
|
|
||||||
if (status) {
|
|
||||||
let text = `${current.length} active · ${upcoming.length} upcoming · times in UTC`;
|
|
||||||
if (satPredSatCount > 0) text += ` · ${satPredSatCount} satellites tracked`;
|
|
||||||
status.textContent = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Countdown timer: update "time left" every second ──
|
|
||||||
// Only run when predictions view is actually visible.
|
|
||||||
if (current.length > 0 && satActiveView === "predictions") {
|
|
||||||
const countdownEls = currentList ? currentList.querySelectorAll(".sat-pred-col-countdown") : [];
|
|
||||||
if (countdownEls.length > 0) {
|
|
||||||
satPredCountdownTimer = setInterval(() => {
|
|
||||||
// Pause timer if predictions view was hidden (e.g. switched tabs).
|
|
||||||
if (satActiveView !== "predictions") {
|
|
||||||
clearInterval(satPredCountdownTimer);
|
clearInterval(satPredCountdownTimer);
|
||||||
satPredCountdownTimer = null;
|
satPredCountdownTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCountdownTimer(container) {
|
||||||
|
const countdownEls = container ? container.querySelectorAll(".sat-pred-col-countdown") : [];
|
||||||
|
if (countdownEls.length === 0) return;
|
||||||
|
|
||||||
|
satPredCountdownTimer = setInterval(() => {
|
||||||
|
if (satActiveView !== "predictions") {
|
||||||
|
stopCountdownTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const n = Date.now();
|
const n = Date.now();
|
||||||
@@ -522,19 +377,150 @@ function renderSatPredictions(passes, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!anyActive) {
|
if (!anyActive) {
|
||||||
clearInterval(satPredCountdownTimer);
|
stopCountdownTimer();
|
||||||
satPredCountdownTimer = null;
|
|
||||||
renderSatPredictions(getFilteredPredictions());
|
renderSatPredictions(getFilteredPredictions());
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Predictions: row builders ───────────────────────────────────────
|
||||||
|
function buildCurrentPassRow(pass, now) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "sat-pred-row-current";
|
||||||
|
const dir = `${azToCardinal(pass.azimuth_aos_deg)} \u2192 ${azToCardinal(pass.azimuth_los_deg)}`;
|
||||||
|
const remaining = Math.max(0, pass.los_ms - now);
|
||||||
|
row.innerHTML = [
|
||||||
|
`<span class="sat-pred-col-sat">${pass.satellite}</span>`,
|
||||||
|
`<span class="sat-pred-col-el ${elevationClass(pass.max_elevation_deg)}">${pass.max_elevation_deg.toFixed(1)}\u00B0</span>`,
|
||||||
|
`<span class="sat-pred-col-time">${formatPredTime(pass.aos_ms)}</span>`,
|
||||||
|
`<span class="sat-pred-col-time">${formatPredTime(pass.los_ms)}</span>`,
|
||||||
|
`<span class="sat-pred-col-countdown" data-los="${pass.los_ms}">${formatCountdown(remaining)}</span>`,
|
||||||
|
`<span class="sat-pred-col-dir">${dir}</span>`,
|
||||||
|
].join("");
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUpcomingPassRow(pass) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "sat-pred-row";
|
||||||
|
const dir = `${azToCardinal(pass.azimuth_aos_deg)} \u2192 ${azToCardinal(pass.azimuth_los_deg)}`;
|
||||||
|
row.innerHTML = [
|
||||||
|
`<span class="sat-pred-col-time">${formatPredTime(pass.aos_ms)}</span>`,
|
||||||
|
`<span class="sat-pred-col-sat">${pass.satellite}</span>`,
|
||||||
|
`<span class="sat-pred-col-el ${elevationClass(pass.max_elevation_deg)}">${pass.max_elevation_deg.toFixed(1)}\u00B0</span>`,
|
||||||
|
`<span class="sat-pred-col-dur">${formatPredDuration(pass.duration_s)}</span>`,
|
||||||
|
`<span class="sat-pred-col-dir">${dir}</span>`,
|
||||||
|
].join("");
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Predictions: filter state ───────────────────────────────────────
|
||||||
|
function getFilteredPredictions() {
|
||||||
|
let items = satPredData;
|
||||||
|
if (satPredCategory !== "all") items = items.filter((p) => p.category === satPredCategory);
|
||||||
|
if (satPredMinEl > 0) items = items.filter((p) => p.max_elevation_deg >= satPredMinEl);
|
||||||
|
if (satPredFilterText) items = items.filter((p) => p.satellite.toUpperCase().includes(satPredFilterText));
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPredFilters() {
|
||||||
|
renderSatPredictions(getFilteredPredictions());
|
||||||
|
}
|
||||||
|
|
||||||
|
satDom.predFilter?.addEventListener("input", () => {
|
||||||
|
satPredFilterText = satDom.predFilter.value.trim().toUpperCase();
|
||||||
|
applyPredFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
satDom.predMinEl?.addEventListener("change", () => {
|
||||||
|
satPredMinEl = parseInt(satDom.predMinEl.value, 10) || 0;
|
||||||
|
applyPredFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
satDom.predCategory?.addEventListener("change", () => {
|
||||||
|
satPredCategory = satDom.predCategory.value;
|
||||||
|
applyPredFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Predictions: main render ────────────────────────────────────────
|
||||||
|
function renderSatPredictions(passes, error) {
|
||||||
|
stopCountdownTimer();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = "";
|
||||||
|
if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = "";
|
||||||
|
if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none";
|
||||||
|
if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none";
|
||||||
|
if (satDom.predStatus) satDom.predStatus.textContent = error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(passes) || passes.length === 0) {
|
||||||
|
if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = "";
|
||||||
|
if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = "";
|
||||||
|
if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none";
|
||||||
|
if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none";
|
||||||
|
if (satDom.predStatus) satDom.predStatus.textContent = "No passes found in the next 24 hours.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const current = passes.filter((p) => p.aos_ms <= now && p.los_ms > now);
|
||||||
|
const upcoming = passes.filter((p) => p.aos_ms > now);
|
||||||
|
|
||||||
|
// ── Current passes ──
|
||||||
|
if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = current.length > 0 ? "" : "none";
|
||||||
|
if (satDom.predCurrentList) {
|
||||||
|
if (current.length === 0) {
|
||||||
|
satDom.predCurrentList.innerHTML = "";
|
||||||
|
} else {
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
for (const pass of current) frag.appendChild(buildCurrentPassRow(pass, now));
|
||||||
|
satDom.predCurrentList.replaceChildren(frag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Upcoming passes ──
|
||||||
|
const upcomingLimit = satPredShowAll ? upcoming.length : SAT_PRED_PAGE_SIZE;
|
||||||
|
const visibleUpcoming = upcoming.slice(0, upcomingLimit);
|
||||||
|
const hiddenCount = upcoming.length - visibleUpcoming.length;
|
||||||
|
if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = upcoming.length > 0 ? "" : "none";
|
||||||
|
if (satDom.predUpcomingList) {
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
for (const pass of visibleUpcoming) frag.appendChild(buildUpcomingPassRow(pass));
|
||||||
|
if (hiddenCount > 0) {
|
||||||
|
const moreRow = document.createElement("div");
|
||||||
|
moreRow.className = "sat-pred-row";
|
||||||
|
moreRow.style.cursor = "pointer";
|
||||||
|
moreRow.style.textAlign = "center";
|
||||||
|
moreRow.innerHTML = `<span style="grid-column:1/-1;color:var(--accent);font-size:0.82rem;">Show ${hiddenCount} more passes\u2026</span>`;
|
||||||
|
moreRow.addEventListener("click", () => {
|
||||||
|
satPredShowAll = true;
|
||||||
|
renderSatPredictions(getFilteredPredictions());
|
||||||
|
});
|
||||||
|
frag.appendChild(moreRow);
|
||||||
|
}
|
||||||
|
satDom.predUpcomingList.replaceChildren(frag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status ──
|
||||||
|
if (satDom.predStatus) {
|
||||||
|
let text = `${current.length} active \u00B7 ${upcoming.length} upcoming \u00B7 times in UTC`;
|
||||||
|
if (satPredSatCount > 0) text += ` \u00B7 ${satPredSatCount} satellites tracked`;
|
||||||
|
satDom.predStatus.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Countdown timer ──
|
||||||
|
if (current.length > 0 && satActiveView === "predictions") {
|
||||||
|
startCountdownTimer(satDom.predCurrentList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Predictions: data loading ───────────────────────────────────────
|
||||||
async function loadSatPredictions() {
|
async function loadSatPredictions() {
|
||||||
if (satPredStatus) satPredStatus.textContent = "Loading predictions\u2026";
|
if (satDom.predStatus) satDom.predStatus.textContent = "Loading predictions\u2026";
|
||||||
if (satPredCurrentList) satPredCurrentList.innerHTML = "";
|
if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = "";
|
||||||
if (satPredUpcomingList) satPredUpcomingList.innerHTML = "";
|
if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = "";
|
||||||
try {
|
try {
|
||||||
const resp = await fetch("/sat_passes");
|
const resp = await fetch("/sat_passes");
|
||||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
@@ -554,11 +540,9 @@ async function loadSatPredictions() {
|
|||||||
|
|
||||||
// ── Navigate to map centered on satellite image bounds ──────────────
|
// ── Navigate to map centered on satellite image bounds ──────────────
|
||||||
window.satShowOnMap = function (south, west, north, east) {
|
window.satShowOnMap = function (south, west, north, east) {
|
||||||
// Enable sat filter if not active
|
|
||||||
if (typeof window.enableMapSourceFilter === "function") {
|
if (typeof window.enableMapSourceFilter === "function") {
|
||||||
window.enableMapSourceFilter("sat");
|
window.enableMapSourceFilter("sat");
|
||||||
}
|
}
|
||||||
// Navigate to the center of the image bounds
|
|
||||||
const lat = (south + north) / 2;
|
const lat = (south + north) / 2;
|
||||||
const lon = (west + east) / 2;
|
const lon = (west + east) / 2;
|
||||||
if (window.navigateToAprsMap) {
|
if (window.navigateToAprsMap) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
let interleaveTicker = null;
|
let interleaveTicker = null;
|
||||||
let schedulerStepPending = false;
|
let schedulerStepPending = false;
|
||||||
let schEntryEditIdx = null; // null = adding, number = editing that index
|
let schEntryEditIdx = null; // null = adding, number = editing that index
|
||||||
let schSatEditIdx = null; // null = adding, number = editing satellite entry
|
// Satellite entry editing state moved to sat-scheduler.js
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Init
|
// Init
|
||||||
@@ -866,225 +866,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Satellite overlay
|
// Satellite overlay (delegated to sat-scheduler.js)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
function getSatelliteEntries() {
|
|
||||||
return (currentConfig && currentConfig.satellites && Array.isArray(currentConfig.satellites.entries))
|
|
||||||
? currentConfig.satellites.entries
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureSatelliteConfig() {
|
|
||||||
if (!currentConfig) currentConfig = { remote: currentRigId, mode: "disabled", entries: [] };
|
|
||||||
if (!currentConfig.satellites) currentConfig.satellites = { enabled: false, pretune_secs: 60, entries: [] };
|
|
||||||
if (!currentConfig.satellites.entries) currentConfig.satellites.entries = [];
|
|
||||||
return currentConfig.satellites;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectSatelliteConfig() {
|
|
||||||
const enabledEl = document.getElementById("scheduler-sat-enabled");
|
|
||||||
const pretuneEl = document.getElementById("scheduler-sat-pretune");
|
|
||||||
const enabled = enabledEl ? enabledEl.checked : false;
|
|
||||||
const pretune = pretuneEl ? parseInt(pretuneEl.value, 10) : 60;
|
|
||||||
return {
|
|
||||||
enabled: enabled,
|
|
||||||
pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune,
|
|
||||||
entries: getSatelliteEntries(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSatelliteSection() {
|
function renderSatelliteSection() {
|
||||||
const satCfg = (currentConfig && currentConfig.satellites) || {};
|
if (window.satScheduler) window.satScheduler.renderSection();
|
||||||
const enabled = !!satCfg.enabled;
|
|
||||||
const enabledEl = document.getElementById("scheduler-sat-enabled");
|
|
||||||
if (enabledEl) enabledEl.checked = enabled;
|
|
||||||
|
|
||||||
const pretuneEl = document.getElementById("scheduler-sat-pretune");
|
|
||||||
if (pretuneEl) pretuneEl.value = satCfg.pretune_secs != null ? satCfg.pretune_secs : 60;
|
|
||||||
|
|
||||||
const bodyEl = document.getElementById("scheduler-sat-body");
|
|
||||||
if (bodyEl) bodyEl.style.display = enabled ? "" : "none";
|
|
||||||
|
|
||||||
renderSatelliteEntries();
|
|
||||||
renderSatPassStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSatelliteEntries() {
|
|
||||||
const tbody = document.getElementById("scheduler-sat-tbody");
|
|
||||||
if (!tbody) return;
|
|
||||||
tbody.innerHTML = "";
|
|
||||||
const entries = getSatelliteEntries();
|
|
||||||
entries.forEach(function (entry, idx) {
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
tr.innerHTML =
|
|
||||||
"<td>" + escHtml(entry.satellite || "") + "</td>" +
|
|
||||||
"<td>" + (entry.norad_id || "") + "</td>" +
|
|
||||||
"<td>" + escHtml(bmName(entry.bookmark_id)) + "</td>" +
|
|
||||||
"<td>" + (entry.min_elevation_deg != null ? entry.min_elevation_deg + "\u00B0" : "5\u00B0") + "</td>" +
|
|
||||||
"<td>" + (entry.priority || 0) + "</td>" +
|
|
||||||
'<td>' +
|
|
||||||
'<button class="sch-write sch-sat-edit-btn" data-idx="' + idx + '" type="button">Edit</button>' +
|
|
||||||
'<button class="sch-write sch-sat-remove-btn" data-idx="' + idx + '" type="button">Remove</button>' +
|
|
||||||
'</td>';
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
});
|
|
||||||
tbody.querySelectorAll(".sch-sat-edit-btn").forEach(function (btn) {
|
|
||||||
btn.addEventListener("click", function () {
|
|
||||||
const i = parseInt(btn.dataset.idx, 10);
|
|
||||||
const entry = getSatelliteEntries()[i];
|
|
||||||
if (entry) schOpenSatForm(entry, i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
tbody.querySelectorAll(".sch-sat-remove-btn").forEach(function (btn) {
|
|
||||||
btn.addEventListener("click", function () {
|
|
||||||
removeSatEntry(parseInt(btn.dataset.idx, 10));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSatEntry(idx) {
|
|
||||||
const sat = ensureSatelliteConfig();
|
|
||||||
sat.entries.splice(idx, 1);
|
|
||||||
renderSatelliteEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
function schOpenSatForm(entry, idx) {
|
|
||||||
schSatEditIdx = (idx != null) ? idx : null;
|
|
||||||
|
|
||||||
const titleEl = document.getElementById("sch-sat-form-title");
|
|
||||||
if (titleEl) titleEl.textContent = entry ? "Edit Satellite" : "Add Satellite";
|
|
||||||
|
|
||||||
const presetEl = document.getElementById("scheduler-sat-preset");
|
|
||||||
const nameEl = document.getElementById("scheduler-sat-name");
|
|
||||||
const noradEl = document.getElementById("scheduler-sat-norad");
|
|
||||||
const bmEl = document.getElementById("scheduler-sat-bookmark");
|
|
||||||
const minElEl = document.getElementById("scheduler-sat-min-el");
|
|
||||||
const prioEl = document.getElementById("scheduler-sat-priority");
|
|
||||||
const centerHzEl = document.getElementById("scheduler-sat-center-hz");
|
|
||||||
|
|
||||||
if (presetEl) presetEl.value = "";
|
|
||||||
if (nameEl) nameEl.value = entry ? (entry.satellite || "") : "";
|
|
||||||
if (noradEl) noradEl.value = entry ? (entry.norad_id || "") : "";
|
|
||||||
if (bmEl) bmEl.value = entry ? (entry.bookmark_id || "") : "";
|
|
||||||
if (minElEl) minElEl.value = entry && entry.min_elevation_deg != null ? entry.min_elevation_deg : 5;
|
|
||||||
if (prioEl) prioEl.value = entry && entry.priority != null ? entry.priority : 0;
|
|
||||||
if (centerHzEl) centerHzEl.value = entry && entry.center_hz ? entry.center_hz : "";
|
|
||||||
|
|
||||||
// Populate bookmark dropdown
|
|
||||||
renderBookmarkSelect("scheduler-sat-bookmark", entry ? entry.bookmark_id : null);
|
|
||||||
|
|
||||||
const wrap = document.getElementById("sch-sat-form-wrap");
|
|
||||||
if (wrap) {
|
|
||||||
wrap.style.display = "flex";
|
|
||||||
if (nameEl) nameEl.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function schCloseSatForm() {
|
|
||||||
const wrap = document.getElementById("sch-sat-form-wrap");
|
|
||||||
if (wrap) wrap.style.display = "none";
|
|
||||||
schSatEditIdx = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function schSatFormSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const nameEl = document.getElementById("scheduler-sat-name");
|
|
||||||
const noradEl = document.getElementById("scheduler-sat-norad");
|
|
||||||
const bmEl = document.getElementById("scheduler-sat-bookmark");
|
|
||||||
const minElEl = document.getElementById("scheduler-sat-min-el");
|
|
||||||
const prioEl = document.getElementById("scheduler-sat-priority");
|
|
||||||
const centerHzEl = document.getElementById("scheduler-sat-center-hz");
|
|
||||||
|
|
||||||
const satellite = nameEl ? nameEl.value.trim() : "";
|
|
||||||
const noradId = noradEl ? parseInt(noradEl.value, 10) : NaN;
|
|
||||||
const bmId = bmEl ? bmEl.value : "";
|
|
||||||
|
|
||||||
if (!satellite) { alert("Please enter a satellite name."); return; }
|
|
||||||
if (isNaN(noradId) || noradId <= 0) { alert("Please enter a valid NORAD catalog number."); return; }
|
|
||||||
if (!bmId) { alert("Please select a bookmark."); return; }
|
|
||||||
|
|
||||||
const minEl = minElEl ? parseFloat(minElEl.value) : 5;
|
|
||||||
const prio = prioEl ? parseInt(prioEl.value, 10) : 0;
|
|
||||||
const centerHzRaw = centerHzEl ? parseInt(centerHzEl.value, 10) : NaN;
|
|
||||||
|
|
||||||
const sat = ensureSatelliteConfig();
|
|
||||||
|
|
||||||
const entryData = {
|
|
||||||
satellite: satellite,
|
|
||||||
norad_id: noradId,
|
|
||||||
bookmark_id: bmId,
|
|
||||||
min_elevation_deg: isNaN(minEl) ? 5 : minEl,
|
|
||||||
priority: isNaN(prio) ? 0 : prio,
|
|
||||||
center_hz: !isNaN(centerHzRaw) && centerHzRaw > 0 ? centerHzRaw : null,
|
|
||||||
bookmark_ids: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (schSatEditIdx !== null) {
|
|
||||||
const existing = sat.entries[schSatEditIdx];
|
|
||||||
entryData.id = existing ? existing.id : ("sat_" + Date.now().toString(36));
|
|
||||||
sat.entries[schSatEditIdx] = entryData;
|
|
||||||
} else {
|
|
||||||
entryData.id = "sat_" + Date.now().toString(36);
|
|
||||||
sat.entries.push(entryData);
|
|
||||||
}
|
|
||||||
|
|
||||||
schCloseSatForm();
|
|
||||||
renderSatelliteEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
function wireSatPresetChange() {
|
|
||||||
const presetEl = document.getElementById("scheduler-sat-preset");
|
|
||||||
if (!presetEl || presetEl._wired) return;
|
|
||||||
presetEl._wired = true;
|
|
||||||
presetEl.addEventListener("change", function () {
|
|
||||||
if (!presetEl.value) return;
|
|
||||||
const parts = presetEl.value.split("|");
|
|
||||||
const nameEl = document.getElementById("scheduler-sat-name");
|
|
||||||
const noradEl = document.getElementById("scheduler-sat-norad");
|
|
||||||
if (nameEl) nameEl.value = parts[0] || "";
|
|
||||||
if (noradEl) noradEl.value = parts[1] || "";
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSatPassStatus() {
|
function renderSatPassStatus() {
|
||||||
const el = document.getElementById("scheduler-sat-pass-status");
|
if (window.satScheduler) window.satScheduler.renderPassStatus();
|
||||||
if (!el) return;
|
|
||||||
const entries = getSatelliteEntries();
|
|
||||||
if (entries.length === 0) {
|
|
||||||
el.innerHTML = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Show active satellite from status if available.
|
|
||||||
if (currentSchedulerStatus && currentSchedulerStatus.active_satellite) {
|
|
||||||
el.innerHTML =
|
|
||||||
'<span class="sch-sat-active-badge">PASS ACTIVE: ' +
|
|
||||||
escHtml(currentSchedulerStatus.active_satellite) +
|
|
||||||
'</span>';
|
|
||||||
} else {
|
|
||||||
el.innerHTML = '<span style="color:var(--text-muted);font-size:0.8rem;">No satellite pass active. Predictions available in the SAT tab.</span>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectSatelliteConfig() {
|
||||||
|
return window.satScheduler
|
||||||
|
? window.satScheduler.collectSatelliteConfig()
|
||||||
|
: { enabled: false, pretune_secs: 60, entries: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function wireSatelliteEvents() {
|
function wireSatelliteEvents() {
|
||||||
const enabledEl = document.getElementById("scheduler-sat-enabled");
|
// Expose bridge for sat-scheduler.js to access shared state.
|
||||||
if (enabledEl) {
|
window.schedulerBridge = {
|
||||||
enabledEl.addEventListener("change", function () {
|
getConfig: function () { return currentConfig; },
|
||||||
const bodyEl = document.getElementById("scheduler-sat-body");
|
getStatus: function () { return currentSchedulerStatus; },
|
||||||
if (bodyEl) bodyEl.style.display = enabledEl.checked ? "" : "none";
|
getBookmarks: function () { return bookmarkList; },
|
||||||
});
|
};
|
||||||
}
|
if (window.satScheduler) window.satScheduler.wireEvents();
|
||||||
|
|
||||||
const addBtn = document.getElementById("scheduler-sat-add-btn");
|
|
||||||
if (addBtn) addBtn.addEventListener("click", function () { schOpenSatForm(null, null); });
|
|
||||||
|
|
||||||
const satForm = document.getElementById("sch-sat-form");
|
|
||||||
if (satForm) satForm.addEventListener("submit", schSatFormSubmit);
|
|
||||||
|
|
||||||
const cancelBtn = document.getElementById("sch-sat-form-cancel");
|
|
||||||
if (cancelBtn) cancelBtn.addEventListener("click", schCloseSatForm);
|
|
||||||
|
|
||||||
wireSatPresetChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1689,6 +1689,7 @@ define_gz_cache!(gz_cw_js, status::CW_JS, "cw.js");
|
|||||||
define_gz_cache!(gz_sat_js, status::SAT_JS, "sat.js");
|
define_gz_cache!(gz_sat_js, status::SAT_JS, "sat.js");
|
||||||
define_gz_cache!(gz_bookmarks_js, status::BOOKMARKS_JS, "bookmarks.js");
|
define_gz_cache!(gz_bookmarks_js, status::BOOKMARKS_JS, "bookmarks.js");
|
||||||
define_gz_cache!(gz_scheduler_js, status::SCHEDULER_JS, "scheduler.js");
|
define_gz_cache!(gz_scheduler_js, status::SCHEDULER_JS, "scheduler.js");
|
||||||
|
define_gz_cache!(gz_sat_scheduler_js, status::SAT_SCHEDULER_JS, "sat-scheduler.js");
|
||||||
define_gz_cache!(gz_background_decode_js, status::BACKGROUND_DECODE_JS, "background-decode.js");
|
define_gz_cache!(gz_background_decode_js, status::BACKGROUND_DECODE_JS, "background-decode.js");
|
||||||
define_gz_cache!(gz_vchan_js, status::VCHAN_JS, "vchan.js");
|
define_gz_cache!(gz_vchan_js, status::VCHAN_JS, "vchan.js");
|
||||||
|
|
||||||
@@ -2249,6 +2250,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(sat_js)
|
.service(sat_js)
|
||||||
.service(bookmarks_js)
|
.service(bookmarks_js)
|
||||||
.service(scheduler_js)
|
.service(scheduler_js)
|
||||||
|
.service(sat_scheduler_js)
|
||||||
.service(background_decode_js)
|
.service(background_decode_js)
|
||||||
.service(vchan_js)
|
.service(vchan_js)
|
||||||
// Virtual channels
|
// Virtual channels
|
||||||
@@ -2421,6 +2423,12 @@ async fn scheduler_js(req: HttpRequest) -> impl Responder {
|
|||||||
static_asset_response(&req, "application/javascript; charset=utf-8", &c.gz, &c.etag)
|
static_asset_response(&req, "application/javascript; charset=utf-8", &c.gz, &c.etag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/sat-scheduler.js")]
|
||||||
|
async fn sat_scheduler_js(req: HttpRequest) -> impl Responder {
|
||||||
|
let c = gz_sat_scheduler_js();
|
||||||
|
static_asset_response(&req, "application/javascript; charset=utf-8", &c.gz, &c.etag)
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/background-decode.js")]
|
#[get("/background-decode.js")]
|
||||||
async fn background_decode_js(req: HttpRequest) -> impl Responder {
|
async fn background_decode_js(req: HttpRequest) -> impl Responder {
|
||||||
let c = gz_background_decode_js();
|
let c = gz_background_decode_js();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub const CW_JS: &str = include_str!("../assets/web/plugins/cw.js");
|
|||||||
pub const SAT_JS: &str = include_str!("../assets/web/plugins/sat.js");
|
pub const SAT_JS: &str = include_str!("../assets/web/plugins/sat.js");
|
||||||
pub const BOOKMARKS_JS: &str = include_str!("../assets/web/plugins/bookmarks.js");
|
pub const BOOKMARKS_JS: &str = include_str!("../assets/web/plugins/bookmarks.js");
|
||||||
pub const SCHEDULER_JS: &str = include_str!("../assets/web/plugins/scheduler.js");
|
pub const SCHEDULER_JS: &str = include_str!("../assets/web/plugins/scheduler.js");
|
||||||
|
pub const SAT_SCHEDULER_JS: &str = include_str!("../assets/web/plugins/sat-scheduler.js");
|
||||||
pub const BACKGROUND_DECODE_JS: &str = include_str!("../assets/web/plugins/background-decode.js");
|
pub const BACKGROUND_DECODE_JS: &str = include_str!("../assets/web/plugins/background-decode.js");
|
||||||
pub const VCHAN_JS: &str = include_str!("../assets/web/plugins/vchan.js");
|
pub const VCHAN_JS: &str = include_str!("../assets/web/plugins/vchan.js");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user