From 5731631ebc4d1478d967706cddc40a8191baba46 Mon Sep 17 00:00:00 2001 From: James Smith Date: Thu, 19 Mar 2026 00:06:47 +0000 Subject: [PATCH] Harden APRS mode teardown and map fallback --- templates/index.html | 88 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/templates/index.html b/templates/index.html index 85debbc..acbedab 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4218,7 +4218,14 @@ acars: () => { if (acarsMainEventSource) { acarsMainEventSource.close(); acarsMainEventSource = null; } }, vdl2: () => { if (vdl2MainEventSource) { vdl2MainEventSource.close(); vdl2MainEventSource = null; } }, radiosonde: () => { if (radiosondeEventSource) { radiosondeEventSource.close(); radiosondeEventSource = null; } }, - aprs: () => { if (aprsEventSource) { aprsEventSource.close(); aprsEventSource = null; } }, + aprs: () => { + if (typeof destroyAprsMode === 'function') { + destroyAprsMode(); + } else if (aprsEventSource) { + aprsEventSource.close(); + aprsEventSource = null; + } + }, tscm: () => { if (tscmEventSource) { tscmEventSource.close(); tscmEventSource = null; } }, meteor: () => typeof MeteorScatter !== 'undefined' && MeteorScatter.destroy?.(), ook: () => typeof OokMode !== 'undefined' && OokMode.destroy?.(), @@ -9880,6 +9887,7 @@ let aprsStationCount = 0; let aprsMeterLastUpdate = 0; let aprsMeterCheckInterval = null; + let aprsClockInterval = null; const APRS_METER_TIMEOUT = 5000; // 5 seconds for "no signal" state // APRS user location (from GPS or shared observer location) @@ -10023,6 +10031,47 @@ }); } + function createAprsFallbackGridLayer() { + const layer = L.gridLayer({ + tileSize: 256, + updateWhenIdle: true, + attribution: 'Local fallback grid' + }); + layer.createTile = function(coords) { + const tile = document.createElement('canvas'); + tile.width = 256; + tile.height = 256; + const ctx = tile.getContext('2d'); + + ctx.fillStyle = '#08121c'; + ctx.fillRect(0, 0, 256, 256); + + ctx.strokeStyle = 'rgba(0, 212, 255, 0.12)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(256, 0); + ctx.moveTo(0, 0); + ctx.lineTo(0, 256); + ctx.stroke(); + + ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)'; + ctx.beginPath(); + ctx.moveTo(128, 0); + ctx.lineTo(128, 256); + ctx.moveTo(0, 128); + ctx.lineTo(256, 128); + ctx.stroke(); + + ctx.fillStyle = 'rgba(160, 220, 255, 0.28)'; + ctx.font = '11px "JetBrains Mono", monospace'; + ctx.fillText(`Z${coords.z} X${coords.x} Y${coords.y}`, 12, 22); + + return tile; + }; + return layer; + } + async function initAprsMap() { if (aprsMap) return; @@ -10054,13 +10103,8 @@ aprsMap = L.map('aprsMap').setView([initialLat, initialLon], initialZoom); window.aprsMap = aprsMap; - // Add fallback tiles immediately so the map is visible instantly - const fallbackTiles = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', { - attribution: '© OSM © CARTO', - maxZoom: 19, - subdomains: 'abcd', - className: 'tile-layer-cyan' - }).addTo(aprsMap); + // Zero-network fallback so mode switches never block on external tiles. + const fallbackTiles = createAprsFallbackGridLayer().addTo(aprsMap); // Upgrade tiles in background via Settings (with timeout fallback) if (typeof Settings !== 'undefined') { @@ -10084,7 +10128,8 @@ } // Update time display (both map header and function bar) - setInterval(() => { + if (aprsClockInterval) clearInterval(aprsClockInterval); + aprsClockInterval = setInterval(() => { const now = new Date(); const timeStr = now.toLocaleTimeString('en-US', { hour12: false }); const utcStr = now.toUTCString().slice(17, 25) + ' UTC'; @@ -10097,6 +10142,31 @@ }, 1000); } + function destroyAprsMode() { + stopAprsMeterCheck(); + if (aprsEventSource) { + aprsEventSource.close(); + aprsEventSource = null; + } + if (aprsPollTimer) { + clearInterval(aprsPollTimer); + aprsPollTimer = null; + } + if (aprsClockInterval) { + clearInterval(aprsClockInterval); + aprsClockInterval = null; + } + if (aprsMap) { + try { + aprsMap.remove(); + } catch (_) {} + aprsMap = null; + window.aprsMap = null; + } + aprsMarkers = {}; + aprsUserMarker = null; + } + function updateAprsStatus(state, freq) { // Update function bar status const stripDot = document.getElementById('aprsStripDot');