build: enable frontend module code splitting

This commit is contained in:
sjg
2026-08-01 14:40:59 +02:00
parent 1a42d58075
commit 26be675242
25 changed files with 8931 additions and 9455 deletions
@@ -1,271 +1,10 @@
"use strict";
(() => {
// src/plugins/ftx-family.ts
var bridge = window;
function finiteNumber(value) {
const number = typeof value === "number" ? value : Number(value);
return Number.isFinite(number) ? number : null;
}
function isAlphaNumeric(value) {
return value !== void 0 && /[A-Za-z0-9]/.test(value);
}
function isGrid(value) {
const normalized = value.trim().toUpperCase();
return /^[A-R]{2}\d{2}(?:[A-X]{2})?$/.test(normalized) && normalized !== "RR73" && normalized !== "73" && normalized !== "RR";
}
function escapeFtxHtml(input) {
return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
function extractFtxGrids(message) {
return [...new Set(message.toUpperCase().split(/[^A-Z0-9]+/).filter(isGrid))];
}
function tokenize(message) {
return message.toUpperCase().split(/[^A-Z0-9/]+/).filter(Boolean);
}
function isCallsign(token) {
return token !== void 0 && token.length >= 3 && token.length <= 12 && !["CQ", "DE", "QRZ", "DX"].includes(token) && !isGrid(token) && /^[A-Z0-9/]{1,5}\d[A-Z0-9/]{1,6}$/.test(token);
}
function extractFtxLocatorDetails(message) {
const tokens = tokenize(message);
const grids = extractFtxGrids(message);
const gridIndex = tokens.findIndex(isGrid);
const callsigns = tokens.slice(0, gridIndex < 0 ? tokens.length : gridIndex).filter(isCallsign);
const directed = callsigns.length >= 2 && !["CQ", "DE", "QRZ"].includes(tokens[0] ?? "");
const source = directed ? callsigns[1] ?? null : callsigns[0] ?? null;
const target = directed ? callsigns[0] ?? null : null;
return grids.map((grid) => ({ grid, station: source, source, target }));
}
function extractFtxCallsign(message) {
return extractFtxLocatorDetails(message)[0]?.station ?? tokenize(message).find(isCallsign) ?? null;
}
function renderFtxMessage(message) {
let html = "";
let index = 0;
while (index < message.length) {
if (!isAlphaNumeric(message[index])) {
html += escapeFtxHtml(message[index] ?? "");
index += 1;
continue;
}
let end = index + 1;
while (end < message.length && isAlphaNumeric(message[end])) end += 1;
const token = message.slice(index, end);
const grid = token.toUpperCase();
html += isGrid(grid) ? `<span class="ft8-locator" data-locator-grid="${grid}" role="button" tabindex="0" aria-label="Show locator ${grid} on map">${grid}</span>` : escapeFtxHtml(token);
index = end;
}
return html;
}
function installFtxCompatibilityHelpers() {
bridge.renderFt8Message = renderFtxMessage;
bridge.ft8EscapeHtml = escapeFtxHtml;
bridge.ft8ExtractLocatorDetails = extractFtxLocatorDetails;
bridge.ft8ExtractAllGrids = extractFtxGrids;
bridge.ft8ExtractLikelyCallsign = extractFtxCallsign;
}
function initializeFt8FamilyBar() {
const labels = { ft8: "FT8", ft4: "FT4", ft2: "FT2" };
const builders = {};
const dismissed = { ft8: 0, ft4: 0, ft2: 0 };
const overlay = document.getElementById("ft8-bar-overlay");
let active = "ft8";
const update = () => {
if (!overlay) return;
const mode = (document.getElementById("mode")?.value ?? "").toUpperCase();
const result = builders[active]?.();
if (mode !== "DIG" && mode !== "USB" || !result || result.count === 0 || result.newestTsMs <= dismissed[active]) {
overlay.style.display = "none";
overlay.innerHTML = "";
return;
}
const label = labels[active];
overlay.innerHTML = `<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">${label}</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-actions"><span class="aprs-bar-window">Last 15 minutes</span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearFt8Bar()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();window.clearFt8Bar();}" aria-label="Clear ${label} overlay">Clear</span></span><button class="aprs-bar-close" type="button" onclick="window.closeFt8Bar()" aria-label="Close ${label} overlay">&times;</button></span></div>${result.html}`;
overlay.style.display = "flex";
};
bridge.registerFt8FamilyBarRenderer = (decoder, builder) => {
builders[decoder] = builder;
};
bridge.setFt8FamilyBarDecoder = (decoder) => {
active = decoder;
update();
};
bridge.updateFt8Bar = update;
bridge.clearFt8Bar = () => {
bridge.trxPluginRuntime.reset(active);
};
bridge.closeFt8Bar = () => {
dismissed[active] = Date.now();
update();
};
}
function initializeFtxDecoder(config) {
const { id, label, periodMs, periodDigits = 1 } = config;
const status = document.getElementById(`${id}-status`);
const period = document.getElementById(`${id}-period`);
const messagesElement = document.getElementById(`${id}-messages`);
const filterInput = document.getElementById(`${id}-filter`);
let filterText = "";
let history = [];
const renderMessage = (message) => {
return bridge.renderFt8Message?.(message) ?? renderFtxMessage(message);
};
const retentionMs = () => bridge.getDecodeHistoryRetentionMs?.() ?? 864e5;
const prune = () => {
const cutoff = Date.now() - retentionMs();
history = history.filter((message) => (finiteNumber(message._tsMs ?? message.ts_ms) ?? 0) >= cutoff);
};
const schedule = (job) => {
if (bridge.trxScheduleUiFrameJob) bridge.trxScheduleUiFrameJob(`${id}-history`, job);
else job();
};
const displayFrequency = (value) => {
const raw = finiteNumber(value);
if (raw === null) return null;
const base = finiteNumber(bridge.ft8BaseHz);
return base !== null && base > 0 && raw >= 0 && raw < 1e5 ? base + raw : raw;
};
const renderRow = (message) => {
const row = document.createElement("div");
row.className = "ft8-row";
const raw = message.message ?? "";
row.dataset.message = raw.toUpperCase();
row.dataset.decoder = id;
const storedFrequency = finiteNumber(message.freq_hz);
row.dataset.storedFreqHz = storedFrequency === null ? "" : String(storedFrequency);
const snr = finiteNumber(message.snr_db);
const delta = finiteNumber(message.dt_s);
const frequency = displayFrequency(message.freq_hz);
const timestamp = finiteNumber(message._tsMs ?? message.ts_ms);
const time = timestamp === null ? "--:--:--" : new Date(timestamp).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
row.innerHTML = `<span class="ft8-time">${time}</span><span class="ft8-snr">${snr?.toFixed(1) ?? "--"}</span><span class="ft8-dt">${delta?.toFixed(2) ?? "--"}</span><span class="ft8-freq">${frequency?.toFixed(0) ?? "--"}</span><span class="ft8-msg">${renderMessage(raw)}</span>`;
return row;
};
const render = () => {
prune();
if (!messagesElement) return;
const fragment = document.createDocumentFragment();
let count = 0;
for (const message of history) {
if (count >= 200) break;
if (filterText && !(message.message ?? "").toUpperCase().includes(filterText)) continue;
fragment.appendChild(renderRow(message));
count += 1;
}
messagesElement.replaceChildren(fragment);
};
const normalize = (message) => {
const raw = message.message ?? "";
const locatorDetails = bridge.ft8ExtractLocatorDetails?.(raw) ?? extractFtxLocatorDetails(raw);
const grids = locatorDetails.length > 0 ? locatorDetails.map(({ grid }) => grid) : bridge.ft8ExtractAllGrids?.(raw) ?? extractFtxGrids(raw);
const station = bridge.ft8ExtractLikelyCallsign?.(raw) ?? extractFtxCallsign(raw);
const frequency = displayFrequency(message.freq_hz);
if (grids.length > 0) {
bridge.mapAddLocator?.(raw, grids, id, station, {
...message,
freq_hz: frequency ?? message.freq_hz,
locator_details: locatorDetails
});
}
return {
receiver: bridge.getDecodeRigMeta?.() ?? null,
ts_ms: message.ts_ms,
snr_db: message.snr_db,
dt_s: message.dt_s,
freq_hz: frequency ?? message.freq_hz,
message: message.message,
_tsMs: finiteNumber(message.ts_ms) ?? Date.now()
};
};
const receiveBatch = (messages) => {
if (messages.length === 0) return;
if (status) status.textContent = "Receiving";
history = messages.map(normalize).reverse().concat(history);
prune();
bridge.setFt8FamilyBarDecoder?.(id);
bridge.updateFt8Bar?.();
schedule(render);
};
const reset = () => {
history = [];
bridge.updateFt8Bar?.();
render();
bridge.clearMapMarkersByType?.(id);
};
const barFrames = () => {
const recent = history.filter((message) => (finiteNumber(message._tsMs ?? message.ts_ms) ?? 0) >= Date.now() - 9e5).slice(0, 8);
let html = "";
for (const message of recent) {
const timestamp = finiteNumber(message._tsMs ?? message.ts_ms);
const time = timestamp === null ? "" : `<span class="aprs-bar-time">${bridge.fmtTime?.(timestamp) ?? ""}</span>`;
const snr = finiteNumber(message.snr_db);
const delta = finiteNumber(message.dt_s);
const frequency = displayFrequency(message.freq_hz);
const detail = [snr === null ? "-- dB" : `${snr.toFixed(1)} dB`, delta === null ? null : `dt ${delta.toFixed(2)}`, frequency === null ? null : `${frequency.toFixed(0)} Hz`].filter((part) => part !== null).join(" · ");
html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${time}<span class="aprs-bar-call">${renderMessage(message.message ?? "")}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
}
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
};
bridge.trxPluginRuntime.registerDecoder({
id,
onMessage: (message) => {
receiveBatch([message]);
},
onBatch: receiveBatch,
restore: receiveBatch,
prune: () => {
prune();
render();
},
reset
});
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
const updatePeriod = () => {
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
};
updatePeriod();
window.setInterval(updatePeriod, 250);
filterInput?.addEventListener("input", () => {
filterText = filterInput.value.trim().toUpperCase();
render();
});
messagesElement?.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) return;
const grid = event.target.closest(".ft8-locator[data-locator-grid]")?.dataset.locatorGrid;
if (grid) {
bridge.navigateToMapLocator?.(grid.toUpperCase(), id);
event.preventDefault();
}
});
const toggle = document.getElementById(`${id}-decode-toggle-btn`);
toggle?.addEventListener("click", () => {
void (async () => {
try {
await bridge.takeSchedulerControlForDecoderDisable?.(toggle);
await bridge.postPath?.(`/toggle_${id}_decode`);
} catch (error) {
console.error(`${label} toggle failed`, error);
}
})();
});
document.getElementById(`settings-clear-${id}-history`)?.addEventListener("click", () => {
void (async () => {
if (!await bridge.trxUi.confirm({ title: `Clear ${label} history?`, message: `All stored ${label} decodes will be permanently removed.`, confirmLabel: "Clear history" })) return;
try {
await bridge.postPath?.(`/clear_${id}_decode`);
reset();
} catch (error) {
console.error(`${label} history clear failed`, error);
}
})();
});
}
import {
initializeFt8FamilyBar,
initializeFtxDecoder,
installFtxCompatibilityHelpers
} from "./chunk-SGMG5LG2.js";
// src/plugins/ft8.ts
installFtxCompatibilityHelpers();
initializeFt8FamilyBar();
initializeFtxDecoder({ id: "ft8", label: "FT8", periodMs: 15e3, periodDigits: 0 });
})();
// src/plugins/ft8.ts
installFtxCompatibilityHelpers();
initializeFt8FamilyBar();
initializeFtxDecoder({ id: "ft8", label: "FT8", periodMs: 15e3, periodDigits: 0 });