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);
window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker();
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
@@ -3593,7 +3593,7 @@ async function switchRigFromSelect(selectEl) {
updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(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 bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker();
@@ -4333,10 +4333,8 @@ async function initializeApp() {
}
}
function initSettingsUI() {
if (typeof initScheduler === "function") {
initScheduler(lastActiveRigId, authRole);
wireSchedulerEvents();
}
window.trx.modules.scheduler?.initialize(lastActiveRigId, authRole);
window.trx.modules.scheduler?.wireEvents();
if (typeof initBackgroundDecode === "function") {
initBackgroundDecode(lastActiveRigId, authRole);
wireBackgroundDecodeEvents();
@@ -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", "/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) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
@@ -24,8 +24,9 @@
centerHz: document.getElementById("scheduler-sat-center-hz")
};
let editIdx = null;
let eventsWired = false;
function getBridge() {
return satSchedulerWindow.schedulerBridge ?? null;
return satSchedulerWindow.trx?.modules?.scheduler ?? null;
}
function getConfig() {
const b = getBridge();
@@ -230,6 +231,8 @@
if (dom.norad) dom.norad.value = parts[1] || "";
}
function wireEvents() {
if (eventsWired) return;
eventsWired = true;
if (dom.enabled) {
const enabledInput = dom.enabled;
dom.enabled.addEventListener("change", function() {
@@ -255,5 +258,9 @@
renderPassStatus,
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"),
screenshot: path.join(sourceDir, "screenshot.ts"),
"webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"),
scheduler: path.join(sourceDir, "plugins", "scheduler.js"),
},
outdir: outputDir,
bundle: false,
@@ -52,6 +51,7 @@ await build({
"sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.ts"),
vchan: path.join(sourceDir, "plugins", "vchan.ts"),
bookmarks: path.join(sourceDir, "plugins", "bookmarks.ts"),
scheduler: path.join(sourceDir, "plugins", "scheduler.ts"),
},
outdir: outputDir,
bundle: true,
@@ -1269,7 +1269,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
window.trx.modules.scheduler?.setRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker();
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
@@ -3935,7 +3935,7 @@ async function switchRigFromSelect(selectEl) {
updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(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 bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker();
@@ -4759,10 +4759,8 @@ async function initializeApp() {
}
function initSettingsUI() {
if (typeof initScheduler === "function") {
initScheduler(lastActiveRigId, authRole);
wireSchedulerEvents();
}
window.trx.modules.scheduler?.initialize(lastActiveRigId, authRole);
window.trx.modules.scheduler?.wireEvents();
if (typeof initBackgroundDecode === "function") {
initBackgroundDecode(lastActiveRigId, authRole);
wireBackgroundDecodeEvents();
@@ -16,7 +16,7 @@ const pluginGroups: Readonly<Record<PluginGroup, readonly string[]>> = {
const loaded = new Set<string>();
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> {
return new Promise((resolve, reject) => {
@@ -7,13 +7,12 @@ import type {
SatelliteScheduleConfig,
SatelliteScheduleEntry,
SatelliteSchedulerApi,
SatelliteSchedulerBridge,
SatelliteSchedulerStatus,
SchedulerConfigWithSatellites,
} from "./satellite-types";
import type { SchedulerConfig, SchedulerService } from "./scheduler-types.js";
interface SatelliteSchedulerWindow {
schedulerBridge?: SatelliteSchedulerBridge;
trx?: { modules?: { scheduler?: SchedulerService } };
satScheduler?: SatelliteSchedulerApi;
trxUi?: { notify(message: string, options: { kind: "error" }): void };
}
@@ -49,15 +48,15 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
// ── Local state ───────────────────────────────────────────────────
let editIdx: number | null = null;
let eventsWired = false;
// ── Scheduler bridge ──────────────────────────────────────────────
// These accessors call into scheduler.js via window.schedulerBridge,
// which is set up by scheduler.js after it initializes.
function getBridge(): SatelliteSchedulerBridge | null {
return satSchedulerWindow.schedulerBridge ?? null;
// These accessors use the shared typed scheduler service when available.
function getBridge(): SchedulerService | null {
return satSchedulerWindow.trx?.modules?.scheduler ?? null;
}
function getConfig(): SchedulerConfigWithSatellites | null {
function getConfig(): SchedulerConfig | null {
const b = getBridge();
return b?.getConfig() ?? null;
}
@@ -309,6 +308,8 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
// ── Wire all events ───────────────────────────────────────────────
function wireEvents() {
if (eventsWired) return;
eventsWired = true;
if (dom.enabled) {
const enabledInput = dom.enabled;
dom.enabled.addEventListener("change", function () {
@@ -334,4 +335,8 @@ const satSchedulerWindow = window as unknown as SatelliteSchedulerWindow;
renderPassStatus: renderPassStatus,
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);
});