Files
reflector-dashboard/lib/fmt.js
T

105 lines
3.7 KiB
JavaScript

// SvxPortal — web front-end for SvxReflector
// Copyright (C) 2026 SA7AUX (Björn)
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version. See the LICENSE file for details.
// fmt.js — shared formatting helpers for every web (dmr-web, svx-web, svxlink-web).
// Canonical copy: <dmr-portal>/lib/fmt.js; other repos carry a COPY.
// Every function here replaced an identical local copy; outputs are unchanged.
(function () {
'use strict';
// HTML-escape for innerHTML/attribute use.
function escHtml(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
// Two-digit zero pad (clock fields).
function fmt2(n) { return String(n).padStart(2, '0'); }
// Age in seconds → "12s" / "5m" / "3h" / "2d"; "—" when unknown.
function fmtAge(s) {
if (s == null || s < 0) return '—';
if (s < 60) return s + 's';
if (s < 3600) return Math.floor(s / 60) + 'm';
if (s < 86400) return Math.floor(s / 3600) + 'h';
return Math.floor(s / 86400) + 'd';
}
// Duration in seconds → "42s" / "3m 5s" / "1h 07m".
function fmtDur(s) {
s = Number(s) || 0;
if (s < 60) return s + 's';
const m = Math.floor(s / 60), r = s % 60;
if (s < 3600) return m + 'm ' + r + 's';
const h = Math.floor(s / 3600), mm = Math.floor((s % 3600) / 60);
return h + 'h ' + mm + 'm';
}
// Unix seconds → local date/time string; "—" when missing.
function fmtTs(ts) {
if (!ts) return '—';
try { return new Date(ts * 1000).toLocaleString(); } catch (_) { return '—'; }
}
// Callsigns: "SA7AUX-R" / "SA7AUX (x)" → "SA7AUX"; natural sort by prefix,
// number, suffix so SM7A < SM7B < SM70A.
function baseCallsign(cs) {
return String(cs || '').split(/[-(]/)[0].trim();
}
function splitForSort(cs) {
const b = baseCallsign(cs).toUpperCase();
const m = b.match(/^([A-Z]+)(\d+)([A-Z0-9]*)$/);
if (!m) return { pre: b, num: Number.POSITIVE_INFINITY, post: '' };
return { pre: m[1], num: Number(m[2]), post: (m[3] || '') };
}
function callsignCompare(a, b) {
const A = splitForSort(a);
const B = splitForSort(b);
if (A.pre !== B.pre) return A.pre < B.pre ? -1 : 1;
if (A.num !== B.num) return A.num - B.num;
if (A.post !== B.post) return A.post < B.post ? -1 : 1;
const ba = baseCallsign(a).toUpperCase();
const bb = baseCallsign(b).toUpperCase();
if (ba !== bb) return ba < bb ? -1 : 1;
const fa = String(a).toUpperCase();
const fb = String(b).toUpperCase();
if (fa !== fb) return fa < fb ? -1 : 1;
return 0;
}
// Frequencies: "145,600 MHz" → 145.6; fmtFreq → "145.60000" (5 decimals).
function parseMHz(v) {
if (v == null) return NaN;
if (typeof v === 'number') return v;
const s = String(v).replace(',', '.').trim();
const m = s.match(/(\d+(?:\.\d+)?)/);
return m ? Number(m[1]) : NaN;
}
function fmtFreq(v) {
if (v == null) return '';
if (typeof v === 'number') {
if (!Number.isFinite(v)) return '';
return v.toFixed(5);
}
const s = String(v).trim();
if (!s) return '';
const mhz = parseMHz(s);
if (Number.isFinite(mhz) && mhz > 0) {
return mhz.toFixed(5);
}
return s;
}
window.escHtml = escHtml;
window.fmt2 = fmt2;
window.fmtAge = fmtAge;
window.fmtDur = fmtDur;
window.fmtTs = fmtTs;
window.baseCallsign = baseCallsign;
window.callsignCompare = callsignCompare;
window.parseMHz = parseMHz;
window.fmtFreq = fmtFreq;
})();