refactor: convert scheduler to typed module

This commit is contained in:
sjg
2026-08-01 13:23:30 +02:00
parent c7994347e9
commit 0338c8b8d2
11 changed files with 1747 additions and 1533 deletions
@@ -1258,7 +1258,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
updateRigIdentitySummary(lastActiveRigId); updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId); window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) { if (rigListChanged) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId); window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId); if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker(); if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker();
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || ""); if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
@@ -3593,7 +3593,7 @@ async function switchRigFromSelect(selectEl) {
updateRigSubtitle(lastActiveRigId); updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(lastActiveRigId); updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId); window.trxUi?.setActiveRig(lastActiveRigId);
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId); window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId); if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || ""); if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker(); window.trx.modules.map?.syncAprsReceiverMarker();
@@ -4333,10 +4333,8 @@ async function initializeApp() {
} }
} }
function initSettingsUI() { function initSettingsUI() {
if (typeof initScheduler === "function") { window.trx.modules.scheduler?.initialize(lastActiveRigId, authRole);
initScheduler(lastActiveRigId, authRole); window.trx.modules.scheduler?.wireEvents();
wireSchedulerEvents();
}
if (typeof initBackgroundDecode === "function") { if (typeof initBackgroundDecode === "function") {
initBackgroundDecode(lastActiveRigId, authRole); initBackgroundDecode(lastActiveRigId, authRole);
wireBackgroundDecodeEvents(); wireBackgroundDecodeEvents();
@@ -10,7 +10,7 @@ const pluginGroups = {
}; };
const loaded = /* @__PURE__ */ new Set(); const loaded = /* @__PURE__ */ new Set();
const loading = /* @__PURE__ */ new Map(); 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", "/sat-scheduler.js", "/vchan.js", "/bookmarks.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", "/vchan.js", "/bookmarks.js", "/scheduler.js"]);
function loadLegacyScript(path) { function loadLegacyScript(path) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const script = document.createElement("script"); const script = document.createElement("script");
@@ -24,8 +24,9 @@
centerHz: document.getElementById("scheduler-sat-center-hz") centerHz: document.getElementById("scheduler-sat-center-hz")
}; };
let editIdx = null; let editIdx = null;
let eventsWired = false;
function getBridge() { function getBridge() {
return satSchedulerWindow.schedulerBridge ?? null; return satSchedulerWindow.trx?.modules?.scheduler ?? null;
} }
function getConfig() { function getConfig() {
const b = getBridge(); const b = getBridge();
@@ -230,6 +231,8 @@
if (dom.norad) dom.norad.value = parts[1] || ""; if (dom.norad) dom.norad.value = parts[1] || "";
} }
function wireEvents() { function wireEvents() {
if (eventsWired) return;
eventsWired = true;
if (dom.enabled) { if (dom.enabled) {
const enabledInput = dom.enabled; const enabledInput = dom.enabled;
dom.enabled.addEventListener("change", function() { dom.enabled.addEventListener("change", function() {
@@ -255,5 +258,9 @@
renderPassStatus, renderPassStatus,
collectSatelliteConfig collectSatelliteConfig
}; };
if (getBridge()) {
wireEvents();
renderSection();
}
})(); })();
})(); })();
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,6 @@ await build({
"plugin-runtime": path.join(sourceDir, "plugin-runtime.ts"), "plugin-runtime": path.join(sourceDir, "plugin-runtime.ts"),
screenshot: path.join(sourceDir, "screenshot.ts"), screenshot: path.join(sourceDir, "screenshot.ts"),
"webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"), "webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"),
scheduler: path.join(sourceDir, "plugins", "scheduler.js"),
}, },
outdir: outputDir, outdir: outputDir,
bundle: false, bundle: false,
@@ -52,6 +51,7 @@ await build({
"sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.ts"), "sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.ts"),
vchan: path.join(sourceDir, "plugins", "vchan.ts"), vchan: path.join(sourceDir, "plugins", "vchan.ts"),
bookmarks: path.join(sourceDir, "plugins", "bookmarks.ts"), bookmarks: path.join(sourceDir, "plugins", "bookmarks.ts"),
scheduler: path.join(sourceDir, "plugins", "scheduler.ts"),
}, },
outdir: outputDir, outdir: outputDir,
bundle: true, bundle: true,
@@ -1269,7 +1269,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
updateRigIdentitySummary(lastActiveRigId); updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId); window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) { if (rigListChanged) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId); window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId); if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker(); if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker();
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || ""); if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
@@ -3935,7 +3935,7 @@ async function switchRigFromSelect(selectEl) {
updateRigSubtitle(lastActiveRigId); updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(lastActiveRigId); updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId); window.trxUi?.setActiveRig(lastActiveRigId);
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId); window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId); if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || ""); if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker(); window.trx.modules.map?.syncAprsReceiverMarker();
@@ -4759,10 +4759,8 @@ async function initializeApp() {
} }
function initSettingsUI() { function initSettingsUI() {
if (typeof initScheduler === "function") { window.trx.modules.scheduler?.initialize(lastActiveRigId, authRole);
initScheduler(lastActiveRigId, authRole); window.trx.modules.scheduler?.wireEvents();
wireSchedulerEvents();
}
if (typeof initBackgroundDecode === "function") { if (typeof initBackgroundDecode === "function") {
initBackgroundDecode(lastActiveRigId, authRole); initBackgroundDecode(lastActiveRigId, authRole);
wireBackgroundDecodeEvents(); wireBackgroundDecodeEvents();
@@ -16,7 +16,7 @@ const pluginGroups: Readonly<Record<PluginGroup, readonly string[]>> = {
const loaded = new Set<string>(); const loaded = new Set<string>();
const loading = new Map<string, Promise<void>>(); const loading = new Map<string, Promise<void>>();
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", "/vchan.js", "/bookmarks.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", "/vchan.js", "/bookmarks.js", "/scheduler.js"]);
function loadLegacyScript(path: string): Promise<void> { function loadLegacyScript(path: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -7,13 +7,12 @@ import type {
SatelliteScheduleConfig, SatelliteScheduleConfig,
SatelliteScheduleEntry, SatelliteScheduleEntry,
SatelliteSchedulerApi, SatelliteSchedulerApi,
SatelliteSchedulerBridge,
SatelliteSchedulerStatus, SatelliteSchedulerStatus,
SchedulerConfigWithSatellites,
} from "./satellite-types"; } from "./satellite-types";
import type { SchedulerConfig, SchedulerService } from "./scheduler-types.js";
interface SatelliteSchedulerWindow { interface SatelliteSchedulerWindow {
schedulerBridge?: SatelliteSchedulerBridge; trx?: { modules?: { scheduler?: SchedulerService } };
satScheduler?: SatelliteSchedulerApi; satScheduler?: SatelliteSchedulerApi;
trxUi?: { notify(message: string, options: { kind: "error" }): void }; trxUi?: { notify(message: string, options: { kind: "error" }): void };
} }
@@ -49,15 +48,15 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
// ── Local state ─────────────────────────────────────────────────── // ── Local state ───────────────────────────────────────────────────
let editIdx: number | null = null; let editIdx: number | null = null;
let eventsWired = false;
// ── Scheduler bridge ────────────────────────────────────────────── // ── Scheduler bridge ──────────────────────────────────────────────
// These accessors call into scheduler.js via window.schedulerBridge, // These accessors use the shared typed scheduler service when available.
// which is set up by scheduler.js after it initializes. function getBridge(): SchedulerService | null {
function getBridge(): SatelliteSchedulerBridge | null { return satSchedulerWindow.trx?.modules?.scheduler ?? null;
return satSchedulerWindow.schedulerBridge ?? null;
} }
function getConfig(): SchedulerConfigWithSatellites | null { function getConfig(): SchedulerConfig | null {
const b = getBridge(); const b = getBridge();
return b?.getConfig() ?? null; return b?.getConfig() ?? null;
} }
@@ -309,6 +308,8 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
// ── Wire all events ─────────────────────────────────────────────── // ── Wire all events ───────────────────────────────────────────────
function wireEvents() { function wireEvents() {
if (eventsWired) return;
eventsWired = true;
if (dom.enabled) { if (dom.enabled) {
const enabledInput = dom.enabled; const enabledInput = dom.enabled;
dom.enabled.addEventListener("change", function () { dom.enabled.addEventListener("change", function () {
@@ -334,4 +335,8 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
renderPassStatus: renderPassStatus, renderPassStatus: renderPassStatus,
collectSatelliteConfig: collectSatelliteConfig, collectSatelliteConfig: collectSatelliteConfig,
}; };
if (getBridge()) {
wireEvents();
renderSection();
}
})(); })();
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later
import type { SatelliteScheduleConfig, SatelliteSchedulerApi } from "./satellite-types.js";
export type SchedulerMode = "disabled" | "grayline" | "time_span";
export interface SchedulerBookmark {
id: string;
name: string;
freq_hz: number;
mode: string;
}
export interface GraylineConfig {
lat: number;
lon: number;
transition_window_min: number;
day_bookmark_id: string | null;
night_bookmark_id: string | null;
dawn_bookmark_id: string | null;
dusk_bookmark_id: string | null;
}
export interface ScheduleEntry {
id?: string;
start_min: number;
end_min: number;
bookmark_id: string;
label: string | null;
interleave_min: number | null;
center_hz: number | null;
bookmark_ids: string[];
record: boolean;
exclusive?: boolean;
}
export interface SchedulerConfig {
remote: string | null;
mode: SchedulerMode;
grayline: GraylineConfig | null;
entries: ScheduleEntry[];
interleave_min?: number | null;
satellites?: SatelliteScheduleConfig | null;
}
export interface SchedulerStatus {
active: boolean;
last_entry_id?: string | null;
last_bookmark_id?: string | null;
last_bookmark_name?: string | null;
last_applied_utc?: number | null;
last_center_hz?: number | null;
last_bookmark_ids?: string[];
active_satellite?: string | null;
freq_hz?: number | null;
mode?: string | null;
active_decoders?: string[];
}
export interface SchedulerService {
initialize(rigId: string | null, role: string | null): void;
destroy(): void;
setRig(rigId: string | null): void;
wireEvents(): void;
getConfig(): SchedulerConfig | null;
getStatus(): SchedulerStatus | null;
getBookmarks(): SchedulerBookmark[];
markDirty(): void;
}
export interface SchedulerWindow extends Window {
trx: { modules: { scheduler?: SchedulerService } };
trxUi: {
confirm(options: { title: string; message: string; confirmLabel: string; danger?: boolean }): Promise<boolean>;
notify?(message: string, options: { kind: "error" }): void;
};
authRole?: string | null;
lastActiveRigId?: string | null;
serverLat?: number | null;
serverLon?: number | null;
vchanTakeSchedulerControl?(): Promise<void>;
vchanToggleSchedulerRelease?(): Promise<unknown>;
satScheduler?: SatelliteSchedulerApi;
}
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// 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("scheduler registers a typed module service without lifecycle globals", async () => {
const window = { trx: { modules: {} }, trxUi: { confirm: async () => true } };
const context = vm.createContext({
window,
document: {
activeElement: null,
addEventListener() {},
getElementById: () => null,
querySelector: () => null,
},
localStorage: { getItem: () => null, setItem() {} },
setInterval: () => 1,
clearInterval() {},
setTimeout: () => 1,
HTMLElement: class HTMLElement {},
Element: class Element {},
Date,
Number,
String,
Array,
Promise,
WeakSet,
console,
});
const source = await readFile(new URL("../../assets/web/generated/scheduler.js", import.meta.url), "utf8");
new vm.Script(source).runInContext(context);
const service = window.trx.modules.scheduler;
assert.equal(typeof service.initialize, "function");
assert.equal(typeof service.setRig, "function");
assert.equal(typeof service.getConfig, "function");
assert.equal(window.initScheduler, undefined);
assert.equal(window.schedulerBridge, undefined);
});