[feat](trx-frontend-http): callsign link, centered freq, title, jog lock

- Make server callsign a clickable link to qrzcq.com
- Center frequency input text and use DSEG14 Classic font
- Include callsign in page title (e.g. N0CALL - trx-frontend-http v0.1.0)
- Block jog wheel when rig lock is active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-08 10:43:34 +01:00
parent a8e09f2f0b
commit a0ca4ceeda
3 changed files with 18 additions and 8 deletions
@@ -36,6 +36,8 @@ let lastFreqHz = null;
let jogStep = 1000; // default 1 kHz let jogStep = 1000; // default 1 kHz
let jogAngle = 0; let jogAngle = 0;
let lastClientCount = null; let lastClientCount = null;
let lastLocked = false;
const originalTitle = document.title;
function readyText() { function readyText() {
return lastClientCount !== null ? `Ready \u00b7 ${lastClientCount} user${lastClientCount !== 1 ? "s" : ""}` : "Ready"; return lastClientCount !== null ? `Ready \u00b7 ${lastClientCount} user${lastClientCount !== 1 ? "s" : ""}` : "Ready";
@@ -155,10 +157,15 @@ function render(update) {
} }
// Server subtitle: "trx-server vX.Y.Z hosted by CALL" // Server subtitle: "trx-server vX.Y.Z hosted by CALL"
if (update.server_version || update.server_callsign) { if (update.server_version || update.server_callsign) {
let text = "trx-server"; let parts = "trx-server";
if (update.server_version) text += ` v${update.server_version}`; if (update.server_version) parts += ` v${update.server_version}`;
if (update.server_callsign) text += ` hosted by ${update.server_callsign}`; if (update.server_callsign) {
serverSubtitle.textContent = text; const cs = update.server_callsign;
serverSubtitle.innerHTML = `${parts} hosted by <a href="https://www.qrzcq.com/call/${encodeURIComponent(cs)}" target="_blank" rel="noopener">${cs}</a>`;
document.title = `${cs} - ${originalTitle}`;
} else {
serverSubtitle.textContent = parts;
}
} }
setDisabled(false); setDisabled(false);
if (update.info && update.info.capabilities && Array.isArray(update.info.capabilities.supported_modes)) { if (update.info && update.info.capabilities && Array.isArray(update.info.capabilities.supported_modes)) {
@@ -280,8 +287,8 @@ function render(update) {
if (typeof update.clients === "number") lastClientCount = update.clients; if (typeof update.clients === "number") lastClientCount = update.clients;
powerHint.textContent = readyText(); powerHint.textContent = readyText();
const locked = update.status && update.status.lock === true; lastLocked = update.status && update.status.lock === true;
lockBtn.textContent = locked ? "Unlock" : "Lock"; lockBtn.textContent = lastLocked ? "Unlock" : "Lock";
const tx = update.status && update.status.tx ? update.status.tx : null; const tx = update.status && update.status.tx ? update.status.tx : null;
txMeters.style.display = ""; txMeters.style.display = "";
@@ -462,6 +469,7 @@ const jogUpBtn = document.getElementById("jog-up");
const jogStepEl = document.getElementById("jog-step"); const jogStepEl = document.getElementById("jog-step");
async function jogFreq(direction) { async function jogFreq(direction) {
if (lastLocked) { showHint("Locked", 1500); return; }
if (lastFreqHz === null) return; if (lastFreqHz === null) return;
const newHz = lastFreqHz + direction * jogStep; const newHz = lastFreqHz + direction * jogStep;
if (!freqAllowed(newHz)) { if (!freqAllowed(newHz)) {
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>{pkg} v{ver} status</title> <title>{pkg} v{ver}</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" />
<link rel="stylesheet" href="/style.css" /> <link rel="stylesheet" href="/style.css" />
@@ -19,7 +19,7 @@ body { font-family: sans-serif; margin: 0; min-height: 100vh; display: flex; ali
.label { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 6px; display: block; } .label { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 6px; display: block; }
.status { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.1rem 1rem; } .status { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.1rem 1rem; }
input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; font-size: 1rem; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); color: var(--text); } input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; font-size: 1rem; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); color: var(--text); }
#freq { font-family: 'DSEG14 Classic', monospace; font-size: 2rem; padding: 0.5rem 0.6rem; letter-spacing: 0.05em; } #freq { font-family: 'DSEG14 Classic', monospace; font-size: 2rem; padding: 0.5rem 0.6rem; letter-spacing: 0.05em; text-align: center; }
.controls-row { .controls-row {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -140,6 +140,8 @@ small { color: var(--text-muted); }
.logo-bg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; opacity: 0.2; } .logo-bg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; opacity: 0.2; }
.logo-bg img { max-width: 50%; max-height: 50%; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); } .logo-bg img { max-width: 50%; max-height: 50%; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); }
.subtitle { color: var(--text-muted); font-size: 0.95rem; } .subtitle { color: var(--text-muted); font-size: 0.95rem; }
.subtitle a { color: var(--accent-green); text-decoration: none; }
.subtitle a:hover { text-decoration: underline; }
.band-tag { display: inline-block; padding: 2px 6px; border-radius: 6px; background: var(--btn-bg); color: var(--text); font-size: 0.82rem; border: 1px solid var(--border-light); margin-left: 6px; } .band-tag { display: inline-block; padding: 2px 6px; border-radius: 6px; background: var(--btn-bg); color: var(--text); font-size: 0.82rem; border: 1px solid var(--border-light); margin-left: 6px; }
.signal { display: flex; gap: 0.6rem; align-items: center; } .signal { display: flex; gap: 0.6rem; align-items: center; }
.signal-bar { flex: 1 1 auto; height: 12px; border-radius: 999px; background: var(--btn-bg); border: 1px solid var(--border-light); overflow: hidden; } .signal-bar { flex: 1 1 auto; height: 12px; border-radius: 999px; background: var(--btn-bg); border: 1px solid var(--border-light); overflow: hidden; }