From 339f6cc04b50e461bec15cefe3a0e696e8051c5e Mon Sep 17 00:00:00 2001 From: James Smith Date: Fri, 3 Jul 2026 08:56:26 +0100 Subject: [PATCH] feat: wire SignalIdModal to waterfall and global nav Co-Authored-By: Claude Sonnet 4.6 --- static/js/modes/waterfall.js | 223 ++---------------------- templates/partials/modes/waterfall.html | 27 +-- templates/partials/nav.html | 6 + 3 files changed, 22 insertions(+), 234 deletions(-) diff --git a/static/js/modes/waterfall.js b/static/js/modes/waterfall.js index 03ee8f6..07884e2 100644 --- a/static/js/modes/waterfall.js +++ b/static/js/modes/waterfall.js @@ -235,206 +235,6 @@ const Waterfall = (function () { .replace(/'/g, '''); } - function _safeSigIdUrl(url) { - try { - const parsed = new URL(String(url || '')); - if (parsed.protocol === 'https:' && parsed.hostname.endsWith('sigidwiki.com')) { - return parsed.toString(); - } - } catch (_) { - // Ignore malformed URLs. - } - return null; - } - - function _setSignalIdStatus(text, isError = false) { - const el = document.getElementById('wfSigIdStatus'); - if (!el) return; - el.textContent = text || ''; - el.style.color = isError ? 'var(--accent-red)' : 'var(--text-dim)'; - } - - function _signalIdFreqInput() { - return document.getElementById('wfSigIdFreq'); - } - - function _syncSignalIdFreq(force = false) { - const input = _signalIdFreqInput(); - if (!input) return; - if (!force && document.activeElement === input) return; - input.value = _monitorFreqMhz.toFixed(4); - } - - function _clearSignalIdPanels() { - const local = document.getElementById('wfSigIdResult'); - const external = document.getElementById('wfSigIdExternal'); - if (local) { - local.style.display = 'none'; - local.innerHTML = ''; - } - if (external) { - external.style.display = 'none'; - external.innerHTML = ''; - } - } - - function _signalIdModeHint() { - const modeEl = document.getElementById('wfSigIdMode'); - const raw = String(modeEl?.value || 'auto').toLowerCase(); - if (!raw || raw === 'auto') return _getMonitorMode(); - return raw; - } - - function _renderLocalSignalGuess(result, frequencyMhz) { - const panel = document.getElementById('wfSigIdResult'); - if (!panel) return; - - if (!result || result.status !== 'ok') { - panel.style.display = 'block'; - panel.innerHTML = '
Local signal guess failed
'; - return; - } - - const label = _escapeHtml(result.primary_label || 'Unknown Signal'); - const confidence = _escapeHtml(result.confidence || 'LOW'); - const confidenceColor = { - HIGH: 'var(--accent-green)', - MEDIUM: 'var(--accent-orange)', - LOW: 'var(--text-dim)', - }[String(result.confidence || '').toUpperCase()] || 'var(--text-dim)'; - const explanation = _escapeHtml(result.explanation || ''); - const tags = Array.isArray(result.tags) ? result.tags : []; - const alternatives = Array.isArray(result.alternatives) ? result.alternatives : []; - - const tagsHtml = tags.slice(0, 8).map((tag) => ( - `${_escapeHtml(tag)}` - )).join(''); - - const altsHtml = alternatives.slice(0, 4).map((alt) => { - const altLabel = _escapeHtml(alt.label || 'Unknown'); - const altConf = _escapeHtml(alt.confidence || 'LOW'); - return `${altLabel} (${altConf})`; - }).join(', '); - - panel.style.display = 'block'; - panel.innerHTML = ` -
-
${label}
-
${confidence}
-
-
${Number(frequencyMhz).toFixed(4)} MHz
-
${explanation}
- ${tagsHtml ? `
${tagsHtml}
` : ''} - ${altsHtml ? `
Also: ${altsHtml}
` : ''} - `; - } - - function _renderExternalSignalMatches(result) { - const panel = document.getElementById('wfSigIdExternal'); - if (!panel) return; - - if (!result || result.status !== 'ok') { - panel.style.display = 'block'; - panel.innerHTML = '
SigID Wiki lookup failed
'; - return; - } - - const matches = Array.isArray(result.matches) ? result.matches : []; - if (!matches.length) { - panel.style.display = 'block'; - panel.innerHTML = '
SigID Wiki: no close matches
'; - return; - } - - const items = matches.slice(0, 5).map((match) => { - const title = _escapeHtml(match.title || 'Unknown'); - const safeUrl = _safeSigIdUrl(match.url); - const titleHtml = safeUrl - ? `${title}` - : `${title}`; - const freqs = Array.isArray(match.frequencies_mhz) - ? match.frequencies_mhz.slice(0, 3).map((f) => Number(f).toFixed(4)).join(', ') - : ''; - const modes = Array.isArray(match.modes) ? match.modes.join(', ') : ''; - const mods = Array.isArray(match.modulations) ? match.modulations.join(', ') : ''; - const distance = Number.isFinite(match.distance_hz) ? `${Math.round(match.distance_hz)} Hz offset` : ''; - return ` -
-
${titleHtml}
-
- ${freqs ? `Freq: ${_escapeHtml(freqs)} MHz` : 'Freq: n/a'} - ${distance ? ` • ${_escapeHtml(distance)}` : ''} -
-
- ${modes ? `Mode: ${_escapeHtml(modes)}` : 'Mode: n/a'} - ${mods ? ` • Modulation: ${_escapeHtml(mods)}` : ''} -
-
- `; - }).join(''); - - const label = result.search_used ? 'SigID Wiki (search fallback)' : 'SigID Wiki'; - panel.style.display = 'block'; - panel.innerHTML = `
${_escapeHtml(label)}
${items}`; - } - - function useTuneForSignalId() { - _syncSignalIdFreq(true); - _setSignalIdStatus(`Using tuned ${_monitorFreqMhz.toFixed(4)} MHz`); - } - - async function identifySignal() { - const input = _signalIdFreqInput(); - const fallbackFreq = Number.isFinite(_monitorFreqMhz) ? _monitorFreqMhz : _currentCenter(); - const frequencyMhz = Number.parseFloat(input?.value || `${fallbackFreq}`); - if (!Number.isFinite(frequencyMhz) || frequencyMhz <= 0) { - _setSignalIdStatus('Signal ID frequency is invalid', true); - return; - } - if (input) input.value = frequencyMhz.toFixed(4); - - const modulation = _signalIdModeHint(); - _setSignalIdStatus(`Identifying ${frequencyMhz.toFixed(4)} MHz...`); - _clearSignalIdPanels(); - - const localReq = fetch('/receiver/signal/guess', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ frequency_mhz: frequencyMhz, modulation }), - }).then((r) => r.json()); - - const externalReq = fetch('/signalid/sigidwiki', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ frequency_mhz: frequencyMhz, modulation, limit: 5 }), - }).then((r) => r.json()); - - const [localRes, externalRes] = await Promise.allSettled([localReq, externalReq]); - - const localOk = localRes.status === 'fulfilled' && localRes.value && localRes.value.status === 'ok'; - const externalOk = externalRes.status === 'fulfilled' && externalRes.value && externalRes.value.status === 'ok'; - - if (localRes.status === 'fulfilled') { - _renderLocalSignalGuess(localRes.value, frequencyMhz); - } else { - _renderLocalSignalGuess({ status: 'error' }, frequencyMhz); - } - - if (externalRes.status === 'fulfilled') { - _renderExternalSignalMatches(externalRes.value); - } else { - _renderExternalSignalMatches({ status: 'error' }); - } - - if (localOk && externalOk) { - _setSignalIdStatus(`Signal ID complete for ${frequencyMhz.toFixed(4)} MHz`); - } else if (localOk) { - _setSignalIdStatus(`Local ID complete; SigID lookup unavailable`, true); - } else { - _setSignalIdStatus('Signal ID lookup failed', true); - } - } - function _safeMode(mode) { const raw = String(mode || '').toLowerCase(); if (['wfm', 'fm', 'am', 'usb', 'lsb'].includes(raw)) return raw; @@ -1124,7 +924,6 @@ const Waterfall = (function () { const modeReadout = document.getElementById('wfRxModeReadout'); if (modeReadout) modeReadout.textContent = _getMonitorMode().toUpperCase(); - _syncSignalIdFreq(false); _updateTuneLine(); _updateHeroReadout(); } @@ -1436,11 +1235,10 @@ const Waterfall = (function () { _switchMode('subghz'); _setHandoffStatus(`Sent ${freq.toFixed(4)} MHz to SubGHz`); } else if (target === 'signalid') { - useTuneForSignalId(); - _setHandoffStatus(`Running Signal ID at ${currentFreq.toFixed(4)} MHz`); - identifySignal().catch((err) => { - _setSignalIdStatus(`Signal ID failed: ${err && err.message ? err.message : 'unknown error'}`, true); - }); + if (window.SignalIdModal) { + SignalIdModal.open({ frequency_mhz: currentFreq, modulation: _getMonitorMode() }); + } + _setHandoffStatus(`Opening Signal ID for ${currentFreq.toFixed(4)} MHz`); } else { throw new Error('Unsupported handoff target'); } @@ -3436,9 +3234,6 @@ const Waterfall = (function () { _renderScanLog(); _syncScanStatsUi(); _setHandoffStatus('Ready'); - _setSignalIdStatus('Ready'); - _syncSignalIdFreq(true); - _clearSignalIdPanels(); _setScanState('Scan idle'); _updateScanButtons(); setScanRangeFromView(); @@ -3512,8 +3307,14 @@ const Waterfall = (function () { applyPreset, stopMonitor, handoff, - identifySignal, - useTuneForSignalId, + openSignalId: function() { + if (window.SignalIdModal) { + SignalIdModal.open({ + frequency_mhz: Number.isFinite(_monitorFreqMhz) ? _monitorFreqMhz : _currentCenter(), + modulation: _getMonitorMode(), + }); + } + }, quickTune: quickTunePreset, addBookmarkFromInput, removeBookmark, diff --git a/templates/partials/modes/waterfall.html b/templates/partials/modes/waterfall.html index 5b7a257..0fabcdc 100644 --- a/templates/partials/modes/waterfall.html +++ b/templates/partials/modes/waterfall.html @@ -127,30 +127,11 @@

Signal Identification

- Identify current frequency using local catalog and SigID Wiki matches. + Identify the current frequency against the local signal database.
-
- - -
-
- - -
-
- - -
-
Ready
- - +
diff --git a/templates/partials/nav.html b/templates/partials/nav.html index 592c5df..b7f8666 100644 --- a/templates/partials/nav.html +++ b/templates/partials/nav.html @@ -148,6 +148,12 @@
+ {{ mode_item('tscm', 'TSCM', '') }} {{ mode_item('spystations', 'Spy Stations', '') }} {{ mode_item('websdr', 'WebSDR', '') }}