diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js index 0547d1a1..8fd3ae18 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js @@ -10,7 +10,7 @@ const pluginGroups = { }; const loaded = /* @__PURE__ */ new Set(); const loading = /* @__PURE__ */ new Map(); -const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js"]); +const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js", "/sat-scheduler.js"]); function loadLegacyScript(path) { return new Promise((resolve, reject) => { const script = document.createElement("script"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat-scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat-scheduler.js index 47709cdd..fcd55ae2 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat-scheduler.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat-scheduler.js @@ -1,256 +1,259 @@ "use strict"; -(function() { - "use strict"; - 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") - }; - let editIdx = null; - 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 markDirty() { - var b = getBridge(); - if (typeof b.markDirty === "function") b.markDirty(); - } - 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, """); - } - function formatFreq(hz) { - if (hz >= 1e6) return (hz / 1e6).toFixed(3) + " MHz"; - if (hz >= 1e3) return (hz / 1e3).toFixed(1) + " kHz"; - return hz + " Hz"; - } - 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, - pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune, - entries: getSatelliteEntries() +(() => { + // src/plugins/sat-scheduler.ts + var satSchedulerWindow = window; + (function() { + "use strict"; + 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") }; - } - 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(); - } - 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 + "°" : "5°"; - 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); + let editIdx = null; + function getBridge() { + return satSchedulerWindow.schedulerBridge ?? null; + } + function getConfig() { + const b = getBridge(); + return b?.getConfig() ?? null; + } + function getStatus() { + const b = getBridge(); + return b?.getStatus() ?? null; + } + function getBookmarks() { + const b = getBridge(); + return b?.getBookmarks() ?? []; + } + function markDirty() { + getBridge()?.markDirty(); + } + function bmName(id) { + const bm = getBookmarks().find(function(b) { + return b.id === id; }); - tdActions.appendChild(editBtn); - var removeBtn = document.createElement("button"); - removeBtn.className = "sch-write"; - removeBtn.type = "button"; - removeBtn.textContent = "Remove"; - removeBtn.addEventListener("click", function() { - removeEntry(idx); + return bm ? bm.name : id; + } + function escHtml(s) { + return s.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 `${String(hz)} Hz`; + } + function getSatelliteEntries() { + const config = getConfig(); + return config && config.satellites && Array.isArray(config.satellites.entries) ? config.satellites.entries : []; + } + function ensureSatelliteConfig() { + const config = getConfig(); + if (!config) return { enabled: false, pretune_secs: 60, entries: [] }; + if (!config.satellites) config.satellites = { enabled: false, pretune_secs: 60, entries: [] }; + return config.satellites; + } + function collectSatelliteConfig() { + const enabled = dom.enabled ? dom.enabled.checked : false; + const pretune = dom.pretune ? parseInt(dom.pretune.value, 10) : 60; + return { + enabled, + pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune, + entries: getSatelliteEntries() + }; + } + function renderSection() { + const config = getConfig(); + const satCfg = config?.satellites; + const enabled = satCfg?.enabled ?? false; + if (dom.enabled) dom.enabled.checked = enabled; + if (dom.pretune) dom.pretune.value = String(satCfg?.pretune_secs ?? 60); + if (dom.body) dom.body.style.display = enabled ? "" : "none"; + renderEntries(); + renderPassStatus(); + } + function renderEntries() { + if (!dom.tbody) return; + const entries = getSatelliteEntries(); + const frag = document.createDocumentFragment(); + entries.forEach(function(entry, idx) { + const tr = document.createElement("tr"); + const tdSat = document.createElement("td"); + tdSat.textContent = entry.satellite || ""; + tr.appendChild(tdSat); + const tdNorad = document.createElement("td"); + tdNorad.textContent = String(entry.norad_id || ""); + tr.appendChild(tdNorad); + const tdBm = document.createElement("td"); + tdBm.textContent = bmName(entry.bookmark_id); + tr.appendChild(tdBm); + const tdEl = document.createElement("td"); + tdEl.textContent = `${String(entry.min_elevation_deg)}°`; + tr.appendChild(tdEl); + const tdPrio = document.createElement("td"); + tdPrio.textContent = String(entry.priority || 0); + tr.appendChild(tdPrio); + const tdActions = document.createElement("td"); + const editBtn = document.createElement("button"); + editBtn.className = "sch-write"; + editBtn.type = "button"; + editBtn.textContent = "Edit"; + editBtn.addEventListener("click", function() { + openForm(entry, idx); + }); + tdActions.appendChild(editBtn); + const 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); }); - tdActions.appendChild(removeBtn); - tr.appendChild(tdActions); - frag.appendChild(tr); - }); - dom.tbody.replaceChildren(frag); - } - function renderPassStatus() { - if (!dom.passStatus) return; - var entries = getSatelliteEntries(); - if (entries.length === 0) { - dom.passStatus.innerHTML = ""; - return; + dom.tbody.replaceChildren(frag); } - var status = getStatus(); - if (status && status.active_satellite) { - dom.passStatus.innerHTML = 'PASS ACTIVE: ' + escHtml(status.active_satellite) + ""; - } else { - dom.passStatus.innerHTML = 'No satellite pass active. Predictions available in the SAT tab.'; + function renderPassStatus() { + if (!dom.passStatus) return; + const entries = getSatelliteEntries(); + if (entries.length === 0) { + dom.passStatus.innerHTML = ""; + return; + } + const status = getStatus(); + if (status && status.active_satellite) { + dom.passStatus.innerHTML = 'PASS ACTIVE: ' + escHtml(status.active_satellite) + ""; + } else { + dom.passStatus.innerHTML = 'No satellite pass active. Predictions available in the SAT tab.'; + } } - } - function renderBookmarkSelect(selectedId) { - if (!dom.bookmark) return; - dom.bookmark.innerHTML = ''; - 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); - }); - } - function removeEntry(idx) { - var sat = ensureSatelliteConfig(); - sat.entries.splice(idx, 1); - renderEntries(); - markDirty(); - } - 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(); + function renderBookmarkSelect(selectedId) { + const bookmarkSelect = dom.bookmark; + if (!bookmarkSelect) return; + bookmarkSelect.innerHTML = ''; + getBookmarks().forEach(function(bm) { + const 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; + bookmarkSelect.appendChild(opt); + }); } - } - function closeForm() { - if (dom.formWrap) dom.formWrap.style.display = "none"; - editIdx = null; - } - 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) { - window.trxUi?.notify("Enter a satellite name.", { kind: "error" }); - document.getElementById("scheduler-sat-name")?.focus(); - return; + function removeEntry(idx) { + const sat = ensureSatelliteConfig(); + sat.entries.splice(idx, 1); + renderEntries(); + markDirty(); } - if (isNaN(noradId) || noradId <= 0) { - window.trxUi?.notify("Enter a valid NORAD catalog number.", { kind: "error" }); - document.getElementById("scheduler-sat-norad")?.focus(); - return; + 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 ? String(entry.norad_id || "") : ""; + if (dom.minEl) dom.minEl.value = String(entry?.min_elevation_deg ?? 5); + if (dom.priority) dom.priority.value = String(entry?.priority ?? 0); + if (dom.centerHz) dom.centerHz.value = entry?.center_hz ? String(entry.center_hz) : ""; + renderBookmarkSelect(entry ? entry.bookmark_id : null); + if (dom.formWrap) { + dom.formWrap.style.display = "flex"; + if (dom.name) dom.name.focus(); + } } - if (!bmId) { - window.trxUi?.notify("Select a bookmark.", { kind: "error" }); - document.getElementById("scheduler-sat-bookmark")?.focus(); - return; + function closeForm() { + if (dom.formWrap) dom.formWrap.style.display = "none"; + editIdx = null; } - 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, - 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: [] + function onFormSubmit(e) { + e.preventDefault(); + const satellite = dom.name ? dom.name.value.trim() : ""; + const noradId = dom.norad ? parseInt(dom.norad.value, 10) : NaN; + const bmId = dom.bookmark ? dom.bookmark.value : ""; + if (!satellite) { + satSchedulerWindow.trxUi?.notify("Enter a satellite name.", { kind: "error" }); + dom.name?.focus(); + return; + } + if (isNaN(noradId) || noradId <= 0) { + satSchedulerWindow.trxUi?.notify("Enter a valid NORAD catalog number.", { kind: "error" }); + dom.norad?.focus(); + return; + } + if (!bmId) { + satSchedulerWindow.trxUi?.notify("Select a bookmark.", { kind: "error" }); + dom.bookmark?.focus(); + return; + } + const minEl = dom.minEl ? parseFloat(dom.minEl.value) : 5; + const prio = dom.priority ? parseInt(dom.priority.value, 10) : 0; + const centerHzRaw = dom.centerHz ? parseInt(dom.centerHz.value, 10) : NaN; + const sat = ensureSatelliteConfig(); + const existing = editIdx !== null ? sat.entries[editIdx] : void 0; + const entryData = { + id: existing?.id ?? `sat_${Date.now().toString(36)}`, + 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) { + sat.entries[editIdx] = entryData; + } else { + sat.entries.push(entryData); + } + closeForm(); + renderEntries(); + markDirty(); + } + function onPresetChange() { + if (!dom.preset || !dom.preset.value) return; + const parts = dom.preset.value.split("|"); + if (dom.name) dom.name.value = parts[0] || ""; + if (dom.norad) dom.norad.value = parts[1] || ""; + } + function wireEvents() { + if (dom.enabled) { + const enabledInput = dom.enabled; + dom.enabled.addEventListener("change", function() { + if (dom.body) dom.body.style.display = enabledInput.checked ? "" : "none"; + markDirty(); + }); + } + if (dom.pretune) { + dom.pretune.addEventListener("input", function() { + markDirty(); + }); + } + 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); + } + satSchedulerWindow.satScheduler = { + wireEvents, + renderSection, + renderPassStatus, + collectSatelliteConfig }; - 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(); - markDirty(); - } - 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] || ""; - } - function wireEvents() { - if (dom.enabled) { - dom.enabled.addEventListener("change", function() { - if (dom.body) dom.body.style.display = dom.enabled.checked ? "" : "none"; - markDirty(); - }); - } - if (dom.pretune) { - dom.pretune.addEventListener("input", function() { - markDirty(); - }); - } - 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); - } - window.satScheduler = { - wireEvents, - renderSection, - renderPassStatus, - collectSatelliteConfig - }; + })(); })(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs index e64d8b7f..83f0d9ed 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs @@ -23,7 +23,6 @@ await build({ screenshot: path.join(sourceDir, "screenshot.ts"), "webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"), bookmarks: path.join(sourceDir, "plugins", "bookmarks.js"), - "sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.js"), scheduler: path.join(sourceDir, "plugins", "scheduler.js"), vchan: path.join(sourceDir, "plugins", "vchan.js"), }, @@ -51,6 +50,7 @@ await build({ aprs: path.join(sourceDir, "plugins", "aprs.ts"), "hf-aprs": path.join(sourceDir, "plugins", "hf-aprs.ts"), sat: path.join(sourceDir, "plugins", "sat.ts"), + "sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.ts"), }, outdir: outputDir, bundle: true, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts index 1c839079..0de2e53d 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts @@ -16,7 +16,7 @@ const pluginGroups: Readonly> = { const loaded = new Set(); const loading = new Map>(); -const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js"]); +const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js", "/ais.js", "/aprs.js", "/hf-aprs.js", "/sat.js", "/sat-scheduler.js"]); function loadLegacyScript(path: string): Promise { return new Promise((resolve, reject) => { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.ts similarity index 62% rename from src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.js rename to src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.ts index 166ff0e9..61ba946e 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/sat-scheduler.ts @@ -2,6 +2,23 @@ // // SPDX-License-Identifier: GPL-2.0-or-later +import type { + SatelliteBookmark, + SatelliteScheduleConfig, + SatelliteScheduleEntry, + SatelliteSchedulerApi, + SatelliteSchedulerBridge, + SatelliteSchedulerStatus, + SchedulerConfigWithSatellites, +} from "./satellite-types"; + +interface SatelliteSchedulerWindow { + schedulerBridge?: SatelliteSchedulerBridge; + satScheduler?: SatelliteSchedulerApi; + trxUi?: { notify(message: string, options: { kind: "error" }): void }; +} +const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow; + // 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. @@ -11,93 +28,91 @@ // ── DOM references (cached once) ────────────────────────────────── const dom = { - enabled: document.getElementById("scheduler-sat-enabled"), - pretune: document.getElementById("scheduler-sat-pretune"), + enabled: document.getElementById("scheduler-sat-enabled") as HTMLInputElement | null, + pretune: document.getElementById("scheduler-sat-pretune") as HTMLInputElement | null, 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"), + form: document.getElementById("sch-sat-form") as HTMLFormElement | null, 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"), + preset: document.getElementById("scheduler-sat-preset") as HTMLSelectElement | null, + name: document.getElementById("scheduler-sat-name") as HTMLInputElement | null, + norad: document.getElementById("scheduler-sat-norad") as HTMLInputElement | null, + bookmark: document.getElementById("scheduler-sat-bookmark") as HTMLSelectElement | null, + minEl: document.getElementById("scheduler-sat-min-el") as HTMLInputElement | null, + priority: document.getElementById("scheduler-sat-priority") as HTMLInputElement | null, + centerHz: document.getElementById("scheduler-sat-center-hz") as HTMLInputElement | null, }; // ── Local state ─────────────────────────────────────────────────── - let editIdx = null; // null = adding, number = editing + let editIdx: number | null = null; // ── 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 getBridge(): SatelliteSchedulerBridge | null { + return satSchedulerWindow.schedulerBridge ?? null; } - function getConfig() { + function getConfig(): SchedulerConfigWithSatellites | null { const b = getBridge(); - return typeof b.getConfig === "function" ? b.getConfig() : null; + return b?.getConfig() ?? null; } - function getStatus() { + function getStatus(): SatelliteSchedulerStatus | null { const b = getBridge(); - return typeof b.getStatus === "function" ? b.getStatus() : null; + return b?.getStatus() ?? null; } - function getBookmarks() { + function getBookmarks(): SatelliteBookmark[] { const b = getBridge(); - return typeof b.getBookmarks === "function" ? b.getBookmarks() : []; + return b?.getBookmarks() ?? []; } function markDirty() { - var b = getBridge(); - if (typeof b.markDirty === "function") b.markDirty(); + getBridge()?.markDirty(); } - function bmName(id) { + function bmName(id: string): string { const bm = getBookmarks().find(function (b) { return b.id === id; }); - return bm ? bm.name : String(id || ""); + return bm ? bm.name : id; } - function escHtml(s) { - return String(s) + function escHtml(s: string): string { + return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } - function formatFreq(hz) { + function formatFreq(hz: number): string { if (hz >= 1e6) return (hz / 1e6).toFixed(3) + " MHz"; if (hz >= 1e3) return (hz / 1e3).toFixed(1) + " kHz"; - return hz + " Hz"; + return `${String(hz)} Hz`; } // ── Satellite config helpers ────────────────────────────────────── - function getSatelliteEntries() { - var config = getConfig(); + function getSatelliteEntries(): SatelliteScheduleEntry[] { + const config = getConfig(); return (config && config.satellites && Array.isArray(config.satellites.entries)) ? config.satellites.entries : []; } - function ensureSatelliteConfig() { - var config = getConfig(); + function ensureSatelliteConfig(): SatelliteScheduleConfig { + const 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; + function collectSatelliteConfig(): SatelliteScheduleConfig { + const enabled = dom.enabled ? dom.enabled.checked : false; + const pretune = dom.pretune ? parseInt(dom.pretune.value, 10) : 60; return { enabled: enabled, pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune, @@ -107,12 +122,12 @@ // ── Render: section ─────────────────────────────────────────────── function renderSection() { - var config = getConfig(); - var satCfg = (config && config.satellites) || {}; - var enabled = !!satCfg.enabled; + const config = getConfig(); + const satCfg = config?.satellites; + const enabled = satCfg?.enabled ?? false; if (dom.enabled) dom.enabled.checked = enabled; - if (dom.pretune) dom.pretune.value = satCfg.pretune_secs != null ? satCfg.pretune_secs : 60; + if (dom.pretune) dom.pretune.value = String(satCfg?.pretune_secs ?? 60); if (dom.body) dom.body.style.display = enabled ? "" : "none"; renderEntries(); @@ -122,35 +137,35 @@ // ── Render: entries table ───────────────────────────────────────── function renderEntries() { if (!dom.tbody) return; - var entries = getSatelliteEntries(); - var frag = document.createDocumentFragment(); + const entries = getSatelliteEntries(); + const frag = document.createDocumentFragment(); entries.forEach(function (entry, idx) { - var tr = document.createElement("tr"); + const tr = document.createElement("tr"); - var tdSat = document.createElement("td"); + const tdSat = document.createElement("td"); tdSat.textContent = entry.satellite || ""; tr.appendChild(tdSat); - var tdNorad = document.createElement("td"); - tdNorad.textContent = entry.norad_id || ""; + const tdNorad = document.createElement("td"); + tdNorad.textContent = String(entry.norad_id || ""); tr.appendChild(tdNorad); - var tdBm = document.createElement("td"); + const 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"); + const tdEl = document.createElement("td"); + tdEl.textContent = `${String(entry.min_elevation_deg)}\u00B0`; tr.appendChild(tdEl); - var tdPrio = document.createElement("td"); - tdPrio.textContent = entry.priority || 0; + const tdPrio = document.createElement("td"); + tdPrio.textContent = String(entry.priority || 0); tr.appendChild(tdPrio); - var tdActions = document.createElement("td"); + const tdActions = document.createElement("td"); - var editBtn = document.createElement("button"); + const editBtn = document.createElement("button"); editBtn.className = "sch-write"; editBtn.type = "button"; editBtn.textContent = "Edit"; @@ -159,7 +174,7 @@ }); tdActions.appendChild(editBtn); - var removeBtn = document.createElement("button"); + const removeBtn = document.createElement("button"); removeBtn.className = "sch-write"; removeBtn.type = "button"; removeBtn.textContent = "Remove"; @@ -178,12 +193,12 @@ // ── Render: pass status ─────────────────────────────────────────── function renderPassStatus() { if (!dom.passStatus) return; - var entries = getSatelliteEntries(); + const entries = getSatelliteEntries(); if (entries.length === 0) { dom.passStatus.innerHTML = ""; return; } - var status = getStatus(); + const status = getStatus(); if (status && status.active_satellite) { dom.passStatus.innerHTML = 'PASS ACTIVE: ' + @@ -196,37 +211,38 @@ } // ── Render: bookmark dropdown ───────────────────────────────────── - function renderBookmarkSelect(selectedId) { - if (!dom.bookmark) return; - dom.bookmark.innerHTML = ''; + function renderBookmarkSelect(selectedId: string | null): void { + const bookmarkSelect = dom.bookmark; + if (!bookmarkSelect) return; + bookmarkSelect.innerHTML = ''; getBookmarks().forEach(function (bm) { - var opt = document.createElement("option"); + const 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); + bookmarkSelect.appendChild(opt); }); } // ── Entry management ────────────────────────────────────────────── - function removeEntry(idx) { - var sat = ensureSatelliteConfig(); + function removeEntry(idx: number): void { + const sat = ensureSatelliteConfig(); sat.entries.splice(idx, 1); renderEntries(); markDirty(); } // ── Form: open ──────────────────────────────────────────────────── - function openForm(entry, idx) { + function openForm(entry: SatelliteScheduleEntry | null, idx: number | null): void { 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 : ""; + if (dom.norad) dom.norad.value = entry ? String(entry.norad_id || "") : ""; + if (dom.minEl) dom.minEl.value = String(entry?.min_elevation_deg ?? 5); + if (dom.priority) dom.priority.value = String(entry?.priority ?? 0); + if (dom.centerHz) dom.centerHz.value = entry?.center_hz ? String(entry.center_hz) : ""; renderBookmarkSelect(entry ? entry.bookmark_id : null); @@ -243,24 +259,26 @@ } // ── Form: submit ────────────────────────────────────────────────── - function onFormSubmit(e) { + function onFormSubmit(e: SubmitEvent): void { 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 : ""; + const satellite = dom.name ? dom.name.value.trim() : ""; + const noradId = dom.norad ? parseInt(dom.norad.value, 10) : NaN; + const bmId = dom.bookmark ? dom.bookmark.value : ""; - if (!satellite) { window.trxUi?.notify("Enter a satellite name.", { kind: "error" }); document.getElementById("scheduler-sat-name")?.focus(); return; } - if (isNaN(noradId) || noradId <= 0) { window.trxUi?.notify("Enter a valid NORAD catalog number.", { kind: "error" }); document.getElementById("scheduler-sat-norad")?.focus(); return; } - if (!bmId) { window.trxUi?.notify("Select a bookmark.", { kind: "error" }); document.getElementById("scheduler-sat-bookmark")?.focus(); return; } + if (!satellite) { satSchedulerWindow.trxUi?.notify("Enter a satellite name.", { kind: "error" }); dom.name?.focus(); return; } + if (isNaN(noradId) || noradId <= 0) { satSchedulerWindow.trxUi?.notify("Enter a valid NORAD catalog number.", { kind: "error" }); dom.norad?.focus(); return; } + if (!bmId) { satSchedulerWindow.trxUi?.notify("Select a bookmark.", { kind: "error" }); dom.bookmark?.focus(); 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; + const minEl = dom.minEl ? parseFloat(dom.minEl.value) : 5; + const prio = dom.priority ? parseInt(dom.priority.value, 10) : 0; + const centerHzRaw = dom.centerHz ? parseInt(dom.centerHz.value, 10) : NaN; - var sat = ensureSatelliteConfig(); + const sat = ensureSatelliteConfig(); - var entryData = { + const existing = editIdx !== null ? sat.entries[editIdx] : undefined; + const entryData: SatelliteScheduleEntry = { + id: existing?.id ?? `sat_${Date.now().toString(36)}`, satellite: satellite, norad_id: noradId, bookmark_id: bmId, @@ -271,11 +289,8 @@ }; 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); } @@ -287,7 +302,7 @@ // ── Preset change handler ───────────────────────────────────────── function onPresetChange() { if (!dom.preset || !dom.preset.value) return; - var parts = dom.preset.value.split("|"); + const parts = dom.preset.value.split("|"); if (dom.name) dom.name.value = parts[0] || ""; if (dom.norad) dom.norad.value = parts[1] || ""; } @@ -295,8 +310,9 @@ // ── Wire all events ─────────────────────────────────────────────── function wireEvents() { if (dom.enabled) { + const enabledInput = dom.enabled; dom.enabled.addEventListener("change", function () { - if (dom.body) dom.body.style.display = dom.enabled.checked ? "" : "none"; + if (dom.body) dom.body.style.display = enabledInput.checked ? "" : "none"; markDirty(); }); } @@ -312,7 +328,7 @@ } // ── Public API ──────────────────────────────────────────────────── - window.satScheduler = { + satSchedulerWindow.satScheduler = { wireEvents: wireEvents, renderSection: renderSection, renderPassStatus: renderPassStatus, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/satellite-types.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/satellite-types.ts index ad069e63..6eebb1cb 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/satellite-types.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/satellite-types.ts @@ -53,3 +53,31 @@ export interface SatelliteScheduleConfig { pretune_secs: number; entries: SatelliteScheduleEntry[]; } + +export interface SatelliteBookmark { + id: string; + name: string; + freq_hz: number; + mode: string; +} + +export interface SchedulerConfigWithSatellites { + satellites?: SatelliteScheduleConfig; + [key: string]: unknown; +} + +export interface SatelliteSchedulerStatus { active_satellite?: string | null } + +export interface SatelliteSchedulerBridge { + getConfig(): SchedulerConfigWithSatellites | null; + getStatus(): SatelliteSchedulerStatus | null; + getBookmarks(): SatelliteBookmark[]; + markDirty(): void; +} + +export interface SatelliteSchedulerApi { + wireEvents(): void; + renderSection(): void; + renderPassStatus(): void; + collectSatelliteConfig(): SatelliteScheduleConfig; +} diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/sat-scheduler.test.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/sat-scheduler.test.mjs new file mode 100644 index 00000000..6895e5db --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/sat-scheduler.test.mjs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2026 Stan Grams +// +// SPDX-License-Identifier: GPL-2.0-or-later + +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import vm from "node:vm"; + +test("satellite scheduler exposes a typed config adapter with safe defaults", async () => { + const window = {}; + const context = vm.createContext({ + window, + document: { getElementById: () => null, createDocumentFragment: () => ({ appendChild() {} }) }, + Date, + Number, + String, + Array, + console, + }); + const source = await readFile(new URL("../../assets/web/generated/sat-scheduler.js", import.meta.url), "utf8"); + new vm.Script(source).runInContext(context); + + assert.equal(typeof window.satScheduler.wireEvents, "function"); + assert.deepEqual( + JSON.parse(JSON.stringify(window.satScheduler.collectSatelliteConfig())), + { enabled: false, pretune_secs: 60, entries: [] }, + ); +});