diff --git a/static/js/mode-registry.js b/static/js/mode-registry.js new file mode 100644 index 0000000..072a34c --- /dev/null +++ b/static/js/mode-registry.js @@ -0,0 +1,298 @@ +// Single source of truth for SPA mode wiring. Each entry drives (after the +// derivation tasks that follow): +// - modeCatalog (label/indicator/outputTitle/group) +// - sidebar active-state toggles (elementId) +// - the destroy map (destroy hook, or module.destroy?.()) +// - visuals container display (visuals: true) +// - init dispatch in switchMode (init hook) +// +// Loaded in before the DOM and before mode modules. init/destroy bodies +// reference globals lazily (only called later from switchMode), so nothing here +// is evaluated at load time. +window.INTERCEPT_MODES = { + pager: { + label: 'Pager', indicator: 'PAGER', outputTitle: 'Pager Decoder', group: 'signals', + elementId: 'pagerMode', + visuals: false, + destroy: () => { if (eventSource) { eventSource.close(); eventSource = null; } }, + }, + sensor: { + label: '433MHz', indicator: '433MHZ', outputTitle: '433MHz Sensor Monitor', group: 'signals', + elementId: 'sensorMode', + visuals: false, + destroy: () => { if (eventSource) { eventSource.close(); eventSource = null; } }, + }, + rtlamr: { + label: 'Meters', indicator: 'METERS', outputTitle: 'Utility Meter Monitor', group: 'signals', + elementId: 'rtlamrMode', + visuals: false, + destroy: () => { if (eventSource) { eventSource.close(); eventSource = null; } }, + }, + subghz: { + label: 'SubGHz', indicator: 'SUBGHZ', outputTitle: 'SubGHz Transceiver', group: 'signals', + elementId: 'subghzMode', + visuals: true, + module: 'SubGhz', + init: () => { + SubGhz.init(); + }, + }, + aprs: { + label: 'APRS', indicator: 'APRS', outputTitle: 'APRS Tracker', group: 'tracking', + elementId: 'aprsMode', + visuals: true, + destroy: () => { + if (typeof destroyAprsMode === 'function') { + destroyAprsMode(); + } else if (aprsEventSource) { + aprsEventSource.close(); + aprsEventSource = null; + } + }, + init: () => { + checkAprsTools(); + initAprsMap(); + // Fix map sizing on mobile after container becomes visible + setTimeout(() => { + if (aprsMap) aprsMap.invalidateSize(); + }, 100); + }, + }, + gps: { + label: 'GPS', indicator: 'GPS', outputTitle: 'GPS Receiver', group: 'tracking', + elementId: 'gpsMode', + visuals: true, + module: 'GPS', + init: () => { + GPS.init(); + }, + }, + radiosonde: { + label: 'Radiosonde', indicator: 'SONDE', outputTitle: 'Radiosonde Decoder', group: 'tracking', + elementId: 'radiosondeMode', + visuals: true, + destroy: () => { if (radiosondeEventSource) { radiosondeEventSource.close(); radiosondeEventSource = null; } }, + init: () => { + initRadiosondeWaveform(); + initRadiosondeMap(); + setTimeout(() => { + if (radiosondeMap) radiosondeMap.invalidateSize(); + }, 100); + }, + }, + satellite: { + label: 'Satellite', indicator: 'SATELLITE', outputTitle: 'Satellite Monitor', group: 'space', + elementId: 'satelliteMode', + visuals: true, + init: () => { + initPolarPlot(); + initSatelliteList(); + }, + }, + sstv: { + label: 'ISS SSTV', indicator: 'ISS SSTV', outputTitle: 'ISS SSTV Decoder', group: 'space', + elementId: 'sstvMode', + visuals: true, + module: 'SSTV', + init: () => { + SSTV.init(); + setTimeout(() => { + if (typeof SSTV !== 'undefined' && SSTV.invalidateMap) SSTV.invalidateMap(); + }, 120); + }, + }, + weathersat: { + label: 'Weather Sat', indicator: 'WEATHER SAT', outputTitle: 'Weather Satellite Decoder', group: 'space', + elementId: 'weatherSatMode', + visuals: true, + module: 'WeatherSat', + init: () => { + WeatherSat.init(); + setTimeout(() => { + WeatherSat.invalidateMap(); + }, 100); + }, + }, + sstv_general: { + label: 'HF SSTV', indicator: 'HF SSTV', outputTitle: 'HF SSTV Decoder', group: 'space', + elementId: 'sstvGeneralMode', + visuals: true, + module: 'SSTVGeneral', + init: () => { + SSTVGeneral.init(); + }, + }, + wefax: { + label: 'WeFax', indicator: 'WEFAX', outputTitle: 'Weather Fax Decoder', group: 'space', + elementId: 'wefaxMode', + visuals: true, + module: 'WeFax', + init: () => { + WeFax.init(); + }, + }, + spaceweather: { + label: 'Space Weather', indicator: 'SPACE WX', outputTitle: 'Space Weather Monitor', group: 'space', + elementId: 'spaceWeatherMode', + visuals: true, + module: 'SpaceWeather', + init: () => { + SpaceWeather.init(); + }, + }, + meteor: { + label: 'Meteor', indicator: 'METEOR', outputTitle: 'Meteor Scatter Monitor', group: 'space', + elementId: 'meteorMode', + visuals: true, + module: 'MeteorScatter', + init: () => { + MeteorScatter.init(); + }, + }, + wifi: { + label: 'WiFi', indicator: 'WIFI', outputTitle: 'WiFi Scanner', group: 'wireless', + elementId: 'wifiMode', + visuals: true, + module: 'WiFiMode', + init: () => { + refreshWifiInterfaces(); + initRadar(); + initWatchList(); + // Initialize v2 WiFi components + if (typeof WiFiMode !== 'undefined') { + WiFiMode.init(); + } + }, + }, + bluetooth: { + label: 'Bluetooth', indicator: 'BLUETOOTH', outputTitle: 'Bluetooth Scanner', group: 'wireless', + elementId: 'bluetoothMode', + visuals: true, + module: 'BluetoothMode', + init: () => { + refreshBtInterfaces(); + initBtRadar(); + }, + }, + bt_locate: { + label: 'BT Locate', indicator: 'BT LOCATE', outputTitle: 'BT Locate — SAR Tracker', group: 'wireless', + elementId: 'btLocateMode', + visuals: true, + module: 'BtLocate', + init: () => { + BtLocate.init(); + setTimeout(() => { + if (typeof BtLocate !== 'undefined' && BtLocate.invalidateMap) BtLocate.invalidateMap(); + }, 100); + setTimeout(() => { + if (typeof BtLocate !== 'undefined' && BtLocate.invalidateMap) BtLocate.invalidateMap(); + }, 320); + }, + }, + wifi_locate: { + label: 'WiFi Locate', indicator: 'WF LOCATE', outputTitle: 'WiFi Locate', group: 'wireless', + elementId: 'wflMode', + visuals: true, + module: 'WiFiLocate', + init: () => { + WiFiLocate.init(); + }, + }, + meshtastic: { + label: 'Meshtastic', indicator: 'MESHTASTIC', outputTitle: 'Meshtastic Mesh Monitor', group: 'wireless', + elementId: 'meshtasticMode', + visuals: true, + module: 'Meshtastic', + init: () => { + Meshtastic.init(); + // Fix map sizing after container becomes visible + setTimeout(() => { + Meshtastic.invalidateMap(); + }, 100); + }, + }, + meshcore: { + label: 'Meshcore', indicator: 'MESHCORE', outputTitle: 'Meshcore Mesh Monitor', group: 'wireless', + elementId: 'meshcoreMode', + visuals: true, + module: 'MeshCore', + init: () => { + MeshCore.init(); + setTimeout(() => { + MeshCore.invalidateMap(); + }, 100); + }, + }, + tscm: { + label: 'TSCM', indicator: 'TSCM', outputTitle: 'TSCM Counter-Surveillance', group: 'intel', + elementId: 'tscmMode', + visuals: true, + destroy: () => { if (tscmEventSource) { tscmEventSource.close(); tscmEventSource = null; } }, + }, + drone: { + label: 'Drone Intel', indicator: 'DRONE', outputTitle: 'Drone Intelligence', group: 'intel', + elementId: 'droneMode', + visuals: true, + module: 'DroneMode', + init: () => { + if (typeof DroneMode !== 'undefined') { + DroneMode.init(); + setTimeout(() => { DroneMode.invalidateMap?.(); }, 100); + } + }, + }, + spystations: { + label: 'Spy Stations', indicator: 'SPY STATIONS', outputTitle: 'Spy Stations', group: 'intel', + elementId: 'spystationsMode', + visuals: true, + module: 'SpyStations', + init: () => { + SpyStations.init(); + }, + }, + websdr: { + label: 'WebSDR', indicator: 'WEBSDR', outputTitle: 'HF/Shortwave WebSDR', group: 'intel', + elementId: 'websdrMode', + visuals: true, + module: 'WebSDR', + init: () => { + if (typeof initWebSDR === 'function') initWebSDR(); + }, + }, + waterfall: { + label: 'Waterfall', indicator: 'WATERFALL', outputTitle: 'Spectrum Waterfall', group: 'signals', + elementId: 'waterfallMode', + visuals: true, + module: 'Waterfall', + init: () => { + if (typeof Waterfall !== 'undefined') Waterfall.init(); + }, + }, + morse: { + label: 'Morse', indicator: 'MORSE', outputTitle: 'CW/Morse Decoder', group: 'signals', + elementId: 'morseMode', + visuals: true, + module: 'MorseMode', + init: () => { + MorseMode.init(); + }, + }, + system: { + label: 'System', indicator: 'SYSTEM', outputTitle: 'System Health Monitor', group: 'system', + elementId: 'systemMode', + visuals: true, + module: 'SystemHealth', + init: () => { + SystemHealth.init(); + }, + }, + ook: { + label: 'OOK Decoder', indicator: 'OOK', outputTitle: 'OOK Signal Decoder', group: 'signals', + elementId: 'ookMode', + visuals: true, + module: 'OokMode', + init: () => { + OokMode.init(); + }, + }, +}; diff --git a/templates/index.html b/templates/index.html index 96d4ce4..945043b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3684,6 +3684,7 @@ +