refactor: replace feature globals with typed services

This commit is contained in:
sjg
2026-08-01 16:05:40 +02:00
parent 3d49839ed5
commit 26a167cb82
16 changed files with 219 additions and 149 deletions
@@ -384,7 +384,7 @@
sdrSquelchSupported = false;
}
updateSdrSquelchControlVisibility();
if (typeof vchanApplyCapabilities === "function") vchanApplyCapabilities(caps);
window.trx?.modules?.vchan?.applyCapabilities(caps);
}
var freqEl = document.getElementById("freq");
var centerFreqEl = document.getElementById("center-freq");
@@ -1217,9 +1217,9 @@
if (themeToggleBtn) {
themeToggleBtn.addEventListener("click", () => {
setTheme(currentTheme() === "dark" ? "light" : "dark");
updateMapBaseLayerForTheme(currentTheme());
syncLocatorMarkerStyles();
refreshAisMarkerColors();
window.trx.modules.map?.updateMapBaseLayerForTheme(currentTheme());
window.trx.modules.map?.syncLocatorMarkerStyles();
window.trx.modules.map?.refreshAisMarkerColors();
scheduleOverviewDraw();
if (typeof scheduleSpectrumDraw === "function" && lastSpectrumData) scheduleSpectrumDraw();
});
@@ -1227,9 +1227,9 @@
if (headerStylePickSelect) {
headerStylePickSelect.addEventListener("change", () => {
setStyle(headerStylePickSelect.value);
updateMapBaseLayerForTheme(currentTheme());
syncLocatorMarkerStyles();
refreshAisMarkerColors();
window.trx.modules.map?.updateMapBaseLayerForTheme(currentTheme());
window.trx.modules.map?.syncLocatorMarkerStyles();
window.trx.modules.map?.refreshAisMarkerColors();
});
}
function readyText() {
@@ -1315,9 +1315,9 @@
window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) {
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 || "");
window.trx.modules.backgroundDecode?.setRig(lastActiveRigId);
window.trx.modules.bookmarks?.populateScopePicker();
window.trx.modules.bookmarks?.fetch(document.getElementById("bm-category-filter")?.value || "");
}
window.trx.modules.map?.updateMapRigFilter();
}
@@ -1562,12 +1562,13 @@
}
}
}
if (typeof vchanChannels !== "undefined" && Array.isArray(vchanChannels)) {
vchanChannels.forEach((ch) => {
const virtualChannels = window.trx.modules.vchan?.channels || [];
if (virtualChannels.length > 0) {
virtualChannels.forEach((ch) => {
if (!Number.isFinite(ch.freq_hz) || ch.freq_hz <= 0) return;
const xc = hzToX(ch.freq_hz);
if (xc < 0 || xc > W) return;
const isActive = ch.id === vchanActiveId;
const isActive = ch.id === window.trx.modules.vchan?.activeId;
const color = cssColorToRgba("#38bdf8");
if (isActive) {
signalOverlayGl.drawSegments([xc, 0, xc, H], color, Math.max(1.5, dpr * 1.5));
@@ -1852,7 +1853,8 @@
refreshWavelengthDisplay(lastFreqHz);
}
function activeRdsChannelId() {
if (typeof vchanActiveId !== "undefined" && vchanActiveId) return vchanActiveId;
const virtualChannelId = window.trx.modules.vchan?.activeId;
if (virtualChannelId) return virtualChannelId;
return null;
}
function activeChannelRds() {
@@ -1861,23 +1863,25 @@
if (activeId) {
const rds = vchanRdsById.get(activeId);
if (rds) return rds;
if (typeof vchanChannels !== "undefined" && Array.isArray(vchanChannels) && vchanChannels.length > 0) {
if (vchanChannels[0].id === activeId) return primaryRds;
const virtualChannels = window.trx.modules.vchan?.channels || [];
if (virtualChannels.length > 0) {
if (virtualChannels[0].id === activeId) return primaryRds;
}
}
return primaryRds;
}
function activeChannelIsWfm() {
if (typeof vchanChannels !== "undefined" && Array.isArray(vchanChannels) && vchanChannels.length > 0) {
const virtualChannels = window.trx.modules.vchan?.channels || [];
if (virtualChannels.length > 0) {
const activeId = activeRdsChannelId();
const active = vchanChannels.find((ch) => ch.id === activeId) || vchanChannels[0];
const active = virtualChannels.find((ch) => ch.id === activeId) || virtualChannels[0];
return String(active?.mode || "").toUpperCase() === "WFM";
}
return lastModeName === "WFM";
}
function activeChannelFreqHz() {
if (typeof vchanActiveChannel === "function") {
const ch = vchanActiveChannel();
if (window.trx.modules.vchan) {
const ch = window.trx.modules.vchan.activeChannel();
if (Number.isFinite(ch?.freq_hz)) return ch.freq_hz;
}
return lastFreqHz;
@@ -1899,11 +1903,12 @@
}
function collectRdsOverlayEntries() {
const entries = [];
if (typeof vchanChannels !== "undefined" && Array.isArray(vchanChannels) && vchanChannels.length > 0) {
for (const ch of vchanChannels) {
const virtualChannels = window.trx.modules.vchan?.channels || [];
if (virtualChannels.length > 0) {
for (const ch of virtualChannels) {
if (String(ch?.mode || "").toUpperCase() !== "WFM") continue;
if (!Number.isFinite(ch?.freq_hz)) continue;
const rds = vchanRdsById.get(ch.id) || (vchanChannels[0].id === ch.id ? primaryRds : null);
const rds = vchanRdsById.get(ch.id) || (virtualChannels[0].id === ch.id ? primaryRds : null);
if (!rds) continue;
entries.push({ id: ch.id, freq_hz: ch.freq_hz, rds });
}
@@ -3145,7 +3150,7 @@ ${unsupportedBandSummary()}`;
prevRenderData.mode = update.status.mode;
const mode = normalizeMode(update.status.mode);
const modeUpper2 = mode ? mode.toUpperCase() : "";
const onVirtual = typeof vchanIsOnVirtual === "function" && vchanIsOnVirtual();
const onVirtual = window.trx.modules.vchan?.isOnVirtual() === true;
if (!onVirtual) {
modeEl.value = modeUpper2;
if (modeUpper2 === "WFM" && lastModeName !== "WFM") {
@@ -3500,10 +3505,10 @@ ${unsupportedBandSummary()}`;
sseSessionId = d.session_id || null;
} catch (_) {
}
if (typeof vchanHandleSession === "function") vchanHandleSession(evt.data);
window.trx.modules.vchan?.handleSession(evt.data);
});
es.addEventListener("channels", (evt) => {
if (typeof vchanHandleChannels === "function") vchanHandleChannels(evt.data);
window.trx.modules.vchan?.handleChannels(evt.data);
});
es.onerror = () => {
if (es.readyState === EventSource.CLOSED) {
@@ -3600,9 +3605,7 @@ ${unsupportedBandSummary()}`;
async function takeSchedulerControlForDecoderDisable(buttonEl) {
const enabled = buttonEl?.dataset?.enabled === "true" || /^\s*Disable\b/i.test(buttonEl?.textContent || "");
if (!enabled) return;
if (typeof window.vchanTakeSchedulerControl === "function") {
await window.vchanTakeSchedulerControl();
}
await window.trx.modules.vchan?.takeSchedulerControl();
}
window.takeSchedulerControlForDecoderDisable = takeSchedulerControlForDecoderDisable;
async function switchRigFromSelect(selectEl) {
@@ -3635,8 +3638,8 @@ ${unsupportedBandSummary()}`;
updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(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.backgroundDecode?.setRig(lastActiveRigId);
window.trx.modules.bookmarks?.fetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker();
connect();
stopSpectrumStreaming();
@@ -3914,7 +3917,7 @@ ${unsupportedBandSummary()}`;
setControlPending(modeEl, true);
showHint("Setting mode…");
try {
if (typeof vchanInterceptMode === "function" && await vchanInterceptMode(mode)) {
if (await window.trx.modules.vchan?.interceptMode(mode)) {
showHint("Channel mode set", 1500);
return;
}
@@ -4047,7 +4050,7 @@ ${unsupportedBandSummary()}`;
scheduleSpectrumDraw();
}
try {
if (typeof vchanInterceptBandwidth === "function" && await vchanInterceptBandwidth(clamped)) return;
if (await window.trx.modules.vchan?.interceptBandwidth(clamped)) return;
await postPath(`/set_bandwidth?hz=${clamped}`);
if (Number.isFinite(lastFreqHz)) {
await ensureTunedBandwidthCoverage(lastFreqHz);
@@ -4131,7 +4134,7 @@ ${unsupportedBandSummary()}`;
}
async function applyAutoBandwidth() {
if (!lastSpectrumData || lastFreqHz == null) return;
const onVirtual = typeof vchanIsOnVirtual === "function" && vchanIsOnVirtual();
const onVirtual = window.trx.modules.vchan?.isOnVirtual() === true;
const interference = onVirtual ? {} : { cci: lastWfmCci, aci: lastWfmAci };
const estimated = estimateOccupiedBandwidth(lastSpectrumData, lastFreqHz, interference);
if (!Number.isFinite(estimated) || estimated <= 0) {
@@ -4155,7 +4158,7 @@ ${unsupportedBandSummary()}`;
}
window.trxUi?.notify(`Auto BW: ${formatBwLabel(estimated)}${reason}`, { kind: "success", duration: 5e3 });
try {
if (typeof vchanInterceptBandwidth === "function" && await vchanInterceptBandwidth(estimated)) return;
if (await window.trx.modules.vchan?.interceptBandwidth(estimated)) return;
await postPath(`/set_bandwidth?hz=${estimated}`);
if (Number.isFinite(lastFreqHz)) {
await ensureTunedBandwidthCoverage(lastFreqHz);
@@ -4373,9 +4376,9 @@ ${unsupportedBandSummary()}`;
function initSettingsUI() {
window.trx.modules.scheduler?.initialize(lastActiveRigId, authRole);
window.trx.modules.scheduler?.wireEvents();
if (typeof initBackgroundDecode === "function") {
initBackgroundDecode(lastActiveRigId, authRole);
wireBackgroundDecodeEvents();
if (window.trx.modules.backgroundDecode) {
window.trx.modules.backgroundDecode.initialize(lastActiveRigId, authRole);
window.trx.modules.backgroundDecode.wireEvents();
}
}
document.getElementById("auth-form").addEventListener("submit", async (e) => {
@@ -4619,7 +4622,7 @@ ${unsupportedBandSummary()}`;
if (!bm) return null;
const parts = [];
if (bm.name) parts.push(String(bm.name));
if (typeof bmFmtFreq === "function") parts.push(bmFmtFreq(bm.freq_hz));
if (window.trx.modules.bookmarks) parts.push(window.trx.modules.bookmarks.formatFrequency(bm.freq_hz));
if (bm.mode) parts.push(String(bm.mode));
if (bm.locator) parts.push(String(bm.locator));
const distance = bookmarkDistanceText(bm);
@@ -6517,8 +6520,9 @@ ${unsupportedBandSummary()}`;
}
vchanRdsById = next;
vchanSignalDbById = nextSig;
if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) {
sigLastDbm = nextSig.get(vchanActiveId);
const virtualChannelId = window.trx.modules.vchan?.activeId;
if (virtualChannelId && nextSig.has(virtualChannelId)) {
sigLastDbm = nextSig.get(virtualChannelId);
refreshSigStrengthDisplay();
}
updateRdsPsOverlay(primaryRds);
@@ -7159,7 +7163,7 @@ ${unsupportedBandSummary()}`;
}
function createBookmarkChip(bm, colorMap, options = {}) {
const span = document.createElement("span");
const freqStr = typeof bmFmtFreq === "function" ? bmFmtFreq(bm.freq_hz) : bm.freq_hz + "Hz";
const freqStr = window.trx.modules.bookmarks ? window.trx.modules.bookmarks.formatFrequency(bm.freq_hz) : bm.freq_hz + "Hz";
const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
span.className = "spectrum-bookmark-chip";
if (options.sideStack) {
@@ -7745,7 +7749,7 @@ ${unsupportedBandSummary()}`;
if (_bwDragEdge) {
try {
const bwHz = Math.round(currentBandwidthHz);
if (!(typeof vchanInterceptBandwidth === "function" && await vchanInterceptBandwidth(bwHz))) {
if (!await window.trx.modules.vchan?.interceptBandwidth(bwHz)) {
await postPath(`/set_bandwidth?hz=${bwHz}`);
if (Number.isFinite(lastFreqHz)) {
await ensureTunedBandwidthCoverage(lastFreqHz, currentBandwidthHz);
@@ -356,7 +356,11 @@ var bgdWindow = window;
});
}
}
bgdWindow.initBackgroundDecode = initBackgroundDecode;
bgdWindow.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
bgdWindow.setBackgroundDecodeRig = setBackgroundDecodeRig;
bgdWindow.trx ??= {};
bgdWindow.trx.modules ??= {};
bgdWindow.trx.modules.backgroundDecode = {
initialize: initBackgroundDecode,
wireEvents: wireBackgroundDecodeEvents,
setRig: setBackgroundDecodeRig
};
})();
@@ -343,15 +343,13 @@ function bmApply(bm) {
bridge.scheduleSpectrumDraw();
}
const tunePromise = (async () => {
if (typeof bridge.vchanTakeSchedulerControl === "function") {
await bridge.vchanTakeSchedulerControl();
}
const onVirtual = typeof bridge.vchanInterceptMode === "function" && await bridge.vchanInterceptMode(bm.mode);
await bridge.trx?.modules?.vchan?.takeSchedulerControl();
const onVirtual = await bridge.trx?.modules?.vchan?.interceptMode(bm.mode) ?? false;
if (!onVirtual) {
await bridge.postPath("/set_mode?mode=" + encodeURIComponent(bm.mode));
}
if (bm.bandwidth_hz) {
const bwHandledByVchan = typeof bridge.vchanInterceptBandwidth === "function" && await bridge.vchanInterceptBandwidth(bm.bandwidth_hz);
const bwHandledByVchan = await bridge.trx?.modules?.vchan?.interceptBandwidth(bm.bandwidth_hz) ?? false;
if (!bwHandledByVchan) {
await bridge.postPath(`/set_bandwidth?hz=${bm.bandwidth_hz}`);
}
@@ -414,7 +412,10 @@ bridge.trx.modules.bookmarks = {
invalidateColors() {
bmOverlayRevision += 1;
},
apply: bmApply
apply: bmApply,
formatFrequency: bmFmtFreq,
fetch: bmFetch,
populateScopePicker: bmPopulateScopePicker
};
function bmUpdateSelectionUi() {
const count = bmSelected.size;
@@ -3090,6 +3090,8 @@ var mapWindow = window;
createAisMarker,
getAisAccentColor,
refreshAisMarkerColors,
updateMapBaseLayerForTheme,
syncLocatorMarkerStyles,
setMapRadioPathTo,
buildReceiverPopupHtml,
rebuildDecodeContactPaths,
@@ -865,12 +865,12 @@ function schedulerEl(id) {
const targetId = target.id;
schedulerStepPending = true;
renderSchedulerStepControls();
Promise.resolve(schedulerWindow.vchanTakeSchedulerControl?.() ?? null).then(function() {
Promise.resolve(schedulerWindow.trx.modules.vchan?.takeSchedulerControl() ?? null).then(function() {
return apiActivateSchedulerEntry(rigId, targetId);
}).then(function(status) {
currentSchedulerStatus = status || null;
return Promise.resolve(
schedulerWindow.vchanToggleSchedulerRelease?.() ?? null
schedulerWindow.trx.modules.vchan?.releaseToScheduler() ?? null
).then(function() {
renderStatus(status);
renderSchedulerInterleaveStatus();
@@ -93,7 +93,6 @@ async function vchanTakeSchedulerControl() {
console.error("scheduler control takeover failed", e);
}
}
vchanWindow.vchanTakeSchedulerControl = vchanTakeSchedulerControl;
function vchanHandleSession(data) {
try {
const d = JSON.parse(data);
@@ -123,8 +122,6 @@ function vchanHandleChannels(data) {
console.warn("vchan: bad channels event", e);
}
}
vchanWindow.vchanHandleSession = vchanHandleSession;
vchanWindow.vchanHandleChannels = vchanHandleChannels;
function vchanRender() {
const picker = document.getElementById("vchan-picker");
if (!picker) return;
@@ -267,12 +264,10 @@ function vchanApplyCapabilities(caps) {
picker.style.display = caps && caps.filter_controls ? "" : "none";
vchanRenderSchedulerRelease();
}
vchanWindow.vchanApplyCapabilities = vchanApplyCapabilities;
function vchanIsOnVirtual() {
if (!vchanActiveId || vchanChannels.length === 0) return false;
return vchanActiveId !== vchanChannels[0]?.id;
}
vchanWindow.vchanIsOnVirtual = vchanIsOnVirtual;
function vchanActiveChannel() {
return vchanChannels.find((c) => c.id === vchanActiveId) || null;
}
@@ -406,15 +401,34 @@ async function vchanSetChannelMode(mode) {
console.error("vchan: set mode error", e);
}
}
vchanWindow.vchanInterceptMode = async function(mode) {
async function vchanInterceptMode(mode) {
if (!vchanIsOnVirtual()) return false;
await vchanSetChannelMode(mode);
return true;
};
vchanWindow.vchanInterceptBandwidth = async function(bwHz) {
}
async function vchanInterceptBandwidth(bwHz) {
if (!vchanIsOnVirtual()) return false;
await vchanSetChannelBandwidth(bwHz);
return true;
}
vchanWindow.trx ??= {};
vchanWindow.trx.modules ??= {};
vchanWindow.trx.modules.vchan = {
get channels() {
return vchanChannels;
},
get activeId() {
return vchanActiveId;
},
activeChannel: vchanActiveChannel,
applyCapabilities: vchanApplyCapabilities,
handleSession: vchanHandleSession,
handleChannels: vchanHandleChannels,
isOnVirtual: vchanIsOnVirtual,
interceptMode: vchanInterceptMode,
interceptBandwidth: vchanInterceptBandwidth,
takeSchedulerControl: vchanTakeSchedulerControl,
releaseToScheduler: vchanToggleSchedulerRelease
};
(function() {
const original = vchanWindow.setRigFrequency;