[refactor](trx-frontend): define module service boundaries

This commit is contained in:
sjg
2026-08-01 01:44:26 +02:00
parent 830f7299fe
commit 7db7fad9b0
3 changed files with 56 additions and 54 deletions
@@ -645,7 +645,7 @@ function flushDeferredDecodeMapSync() {
if (!decodeMapSyncPending || decodeHistoryReplayActive || !window.trx?.map?.aprsMap) return;
decodeMapSyncPending = false;
scheduleUiFrameJob("decode-map-maintenance", () => {
window.trx.map?.pruneMapHistory();
window.trx.modules.map?.pruneMapHistory();
});
}
@@ -1279,7 +1279,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
if (typeof bmPopulateScopePicker === "function") bmPopulateScopePicker();
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
}
window.trx.map?.updateMapRigFilter();
window.trx.modules.map?.updateMapRigFilter();
}
@@ -1305,7 +1305,7 @@ async function refreshRigList() {
serverRigs = rigs;
serverActiveRigId = data.active_remote || null;
applyRigList(data.active_remote, rigIds, displayNames);
window.trx.map?.syncAprsReceiverMarker();
window.trx.modules.map?.syncAprsReceiverMarker();
} catch (e) {
// Non-fatal: SSE/status path still drives main UI.
}
@@ -3143,9 +3143,9 @@ function render(update) {
const grid = latLonToMaidenhead(serverLat, serverLon);
locationSubtitle.textContent = `Location: ${grid}`;
locationSubtitle.style.display = "";
window.trx.map?.reverseGeocodeLocation(serverLat, serverLon, grid);
window.trx.modules.map?.reverseGeocodeLocation(serverLat, serverLon, grid);
}
window.trx.map?.syncAprsReceiverMarker();
window.trx.modules.map?.syncAprsReceiverMarker();
if (typeof update.initial_map_zoom === "number" && Number.isFinite(update.initial_map_zoom)) {
initialMapZoom = Math.max(1, Math.round(update.initial_map_zoom));
}
@@ -3894,7 +3894,7 @@ async function switchRigFromSelect(selectEl) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.map?.syncAprsReceiverMarker();
window.trx.modules.map?.syncAprsReceiverMarker();
// Switch this session's rig and reconnect SSE to the new rig's
// state channel.
try {
@@ -4480,23 +4480,23 @@ function updateTabHistory(name, replaceHistory = false) {
}
// Initialise the Leaflet map, waiting for both Leaflet (L) and map-core.js
// (window.trx.map) if they haven't loaded yet.
// (window.trx.modules.map) if they haven't loaded yet.
let _mapInitTimer = null;
function _initMapWhenReady() {
const loadingEl = document.getElementById("map-loading");
if (window.trx.map && typeof L !== "undefined") {
if (window.trx.modules.map && typeof L !== "undefined") {
if (_mapInitTimer) { clearInterval(_mapInitTimer); _mapInitTimer = null; }
if (loadingEl) loadingEl.classList.add("is-hidden");
window.trx.map.initAprsMap();
window.trx.map.sizeAprsMapToViewport();
window.trx.modules.map.initAprsMap();
window.trx.modules.map.sizeAprsMapToViewport();
// The map panel was just made visible (display:none → ""); the browser
// may not have laid it out yet, so getBoundingClientRect() can return
// stale/zero dimensions. Double-rAF ensures a full layout pass has
// completed before we re-measure and tell Leaflet about its real size.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.trx.map.sizeAprsMapToViewport();
if (window.trx.map.aprsMap) window.trx.map.aprsMap.invalidateSize();
window.trx.modules.map.sizeAprsMapToViewport();
if (window.trx.modules.map.aprsMap) window.trx.modules.map.aprsMap.invalidateSize();
});
});
return;
@@ -4544,7 +4544,7 @@ function navigateToTab(name, options = {}) {
_initMapWhenReady();
}
if (name === "statistics") {
window.trx.map?.scheduleStatsRender();
window.trx.modules.map?.scheduleStatsRender();
}
if (name === "recorder") {
refreshRecorderStatus();
@@ -4721,10 +4721,11 @@ if (headerAuthBtn) {
// ── Shared namespace for lazy-loaded modules ────────────────────────────────
// Modules (map-core.js, screenshot.js) access core state and utilities via
// window.trx. Modules register their own APIs as sub-namespaces
// (e.g. window.trx.map, window.trx.screenshot).
window.trx = Object.create(null);
// (e.g. window.trx.modules.map, window.trx.modules.screenshot).
const trxState = Object.create(null);
const trxModules = Object.create(null);
// -- State getters (backed by core-scoped variables) --
Object.defineProperties(window.trx, {
Object.defineProperties(trxState, {
serverLat: { get() { return serverLat; }, set(v) { serverLat = v; } },
serverLon: { get() { return serverLon; }, set(v) { serverLon = v; } },
lastFreqHz: { get() { return lastFreqHz; } },
@@ -4762,7 +4763,7 @@ Object.defineProperties(window.trx, {
signalOverlayGl: { get() { return signalOverlayGl; } },
});
// -- Shared utility functions --
Object.assign(window.trx, {
const trxCore = Object.freeze({
saveSetting, loadSetting, showHint, escapeMapHtml, formatFreq, formatFreqForHumans,
formatWavelength, formatBwLabel, formatUptime, formatSigStrength, formatSignal,
postPath, scheduleUiFrameJob, navigateToTab, rigBadgeColor,
@@ -4772,18 +4773,19 @@ Object.assign(window.trx, {
currentTheme, canvasPalette, currentStyle,
cssColorToRgba, rgbaWithAlpha, isBinsArray, estimateNoiseFloorDb,
spectrumVisibleRange, drawSpectrum,
bandForHz: function(hz) { return window.trx.map?.bandForHz?.(hz); },
bandForHz: function(hz) { return trxModules.map?.bandForHz?.(hz); },
markDecodeMapSyncPending,
decodeHistoryMapRenderingDeferred,
updateDocumentTitle,
activeChannelRds,
});
Object.defineProperties(window.trx, {
Object.defineProperties(trxState, {
decodeHistoryReplayActive: { get() { return decodeHistoryReplayActive; } },
decodeMapSyncPending: { get() { return decodeMapSyncPending; } },
_activeTab: { get() { return _activeTab; } },
locationSubtitle: { get() { return locationSubtitle; } },
});
window.trx = Object.freeze({ state: trxState, core: trxCore, modules: trxModules });
// Load plugin scripts now that window.trx is populated. Dynamic scripts are
// async so they must not be created before the namespace they depend on exists.
@@ -4797,7 +4799,7 @@ window.addEventListener("resize", resizeHeaderSignalCanvas);
// ── Map module (extracted to map-core.js, lazy-loaded) ──────────────────────
// The map, statistics, and geolocation code (~3,450 lines) has been moved to
// map-core.js and is loaded on demand when the Map tab is first activated.
// Core communicates with the map module via window.trx.map.* namespace.
// Core communicates with the map module via window.trx.modules.map.* namespace.
// ── Geo utilities (shared with map-core.js via window.trx) ─────────────────
function haversineKm(lat1, lon1, lat2, lon2) {
@@ -4932,7 +4934,7 @@ document.querySelectorAll(".sub-tab-bar").forEach(_wireSubTabBar);
window.addEventListener("resize", () => {
const mapTab = document.getElementById("tab-map");
if (!mapTab || mapTab.style.display === "none") return;
window.trx.map?.sizeAprsMapToViewport();
window.trx.modules.map?.sizeAprsMapToViewport();
});
// --- Signal measurement ---
@@ -6102,8 +6104,8 @@ function dispatchDecodeMessage(msg, skipStats) {
if (msg.type === "wefax" && window.onServerWefax) window.onServerWefax(msg);
if (msg.type === "wefax_progress" && window.onServerWefaxProgress) window.onServerWefaxProgress(msg);
if (!skipStats && msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress" && msg.type !== "wefax" && msg.type !== "wefax_progress") {
window.trx.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
window.trx.map?.scheduleStatsRender();
window.trx.modules.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
window.trx.modules.map?.scheduleStatsRender();
}
}
@@ -6112,10 +6114,10 @@ function dispatchDecodeBatch(batch) {
// Record statistics for every message in the batch regardless of dispatch path.
for (const msg of batch) {
if (msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress" && msg.type !== "wefax" && msg.type !== "wefax_progress") {
window.trx.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
window.trx.modules.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
}
}
window.trx.map?.scheduleStatsRender();
window.trx.modules.map?.scheduleStatsRender();
const type = String(batch[0]?.type || "");
const uniformType = batch.every((msg) => String(msg?.type || "") === type);
if (uniformType) {
@@ -6200,9 +6202,9 @@ function restoreDecodeHistoryGroup(kind, messages) {
// Record statistics for restored history messages.
if (kind !== "lrpt_image" && kind !== "lrpt_progress" && kind !== "wefax" && kind !== "wefax_progress") {
for (const msg of messages) {
window.trx.map?.statsRecordDecode(kind, msg.rig_id || msg.remote || null, msg.ts_ms || undefined);
window.trx.modules.map?.statsRecordDecode(kind, msg.rig_id || msg.remote || null, msg.ts_ms || undefined);
}
window.trx.map?.scheduleStatsRender();
window.trx.modules.map?.scheduleStatsRender();
}
if (kind === "ais") {
if (window.restoreAisHistory) { window.restoreAisHistory(messages); }
@@ -8000,12 +8002,12 @@ window.addEventListener("keydown", (event) => {
// S — spectrum screenshot (lazy-loads screenshot.js on first use)
if (key === "s") {
event.preventDefault();
if (window.trx.screenshot) {
void window.trx.screenshot.captureSpectrumScreenshot();
if (window.trx.modules.screenshot) {
void window.trx.modules.screenshot.captureSpectrumScreenshot();
} else {
const s = document.createElement("script");
s.src = "/screenshot.js";
s.onload = () => { void window.trx.screenshot?.captureSpectrumScreenshot(); };
s.onload = () => { void window.trx.modules.screenshot?.captureSpectrumScreenshot(); };
document.body.appendChild(s);
}
return;