diff --git a/static/js/modes/meshtastic.js b/static/js/modes/meshtastic.js index e017ad1..dba40ee 100644 --- a/static/js/modes/meshtastic.js +++ b/static/js/modes/meshtastic.js @@ -16,8 +16,9 @@ const Meshtastic = (function() { // Map state let meshMap = null; - let meshMarkers = {}; // nodeId -> marker - let localNodeId = null; + let meshMarkers = {}; // nodeId -> marker + let localNodeId = null; + let clickDelegationAttached = false; /** * Initialize the Meshtastic mode @@ -32,11 +33,14 @@ const Meshtastic = (function() { /** * Setup event delegation for dynamically created elements */ - function setupEventDelegation() { - // Handle button clicks in Leaflet popups and elsewhere - document.addEventListener('click', function(e) { - const tracerouteBtn = e.target.closest('.mesh-traceroute-btn'); - if (tracerouteBtn) { + function setupEventDelegation() { + if (clickDelegationAttached) return; + clickDelegationAttached = true; + + // Handle button clicks in Leaflet popups and elsewhere + document.addEventListener('click', function(e) { + const tracerouteBtn = e.target.closest('.mesh-traceroute-btn'); + if (tracerouteBtn) { const nodeId = tracerouteBtn.dataset.nodeId; if (nodeId) { sendTraceroute(nodeId); diff --git a/static/js/modes/sstv.js b/static/js/modes/sstv.js index ca8e34a..c7b5d25 100644 --- a/static/js/modes/sstv.js +++ b/static/js/modes/sstv.js @@ -14,10 +14,11 @@ const SSTV = (function() { let issMarker = null; let issTrackLine = null; let issPosition = null; - let issUpdateInterval = null; - let countdownInterval = null; - let nextPassData = null; - let pendingMapInvalidate = false; + let issUpdateInterval = null; + let countdownInterval = null; + let nextPassData = null; + let pendingMapInvalidate = false; + let locationListenersAttached = false; // ISS frequency const ISS_FREQ = 145.800; @@ -92,10 +93,12 @@ const SSTV = (function() { if (latInput && storedLat) latInput.value = storedLat; if (lonInput && storedLon) lonInput.value = storedLon; - // Add change handlers to save and refresh - if (latInput) latInput.addEventListener('change', saveLocationFromInputs); - if (lonInput) lonInput.addEventListener('change', saveLocationFromInputs); - } + if (!locationListenersAttached) { + if (latInput) latInput.addEventListener('change', saveLocationFromInputs); + if (lonInput) lonInput.addEventListener('change', saveLocationFromInputs); + locationListenersAttached = true; + } + } /** * Save location from input fields diff --git a/static/js/modes/weather-satellite.js b/static/js/modes/weather-satellite.js index 1489aa5..c12eb90 100644 --- a/static/js/modes/weather-satellite.js +++ b/static/js/modes/weather-satellite.js @@ -27,11 +27,24 @@ const WeatherSat = (function() { let consoleAutoHideTimer = null; let currentModalFilename = null; let locationListenersAttached = false; + let initialized = false; /** * Initialize the Weather Satellite mode */ function init() { + if (initialized) { + checkStatus(); + loadImages(); + loadLocationInputs(); + loadPasses(); + startCountdownTimer(); + checkSchedulerStatus(); + initGroundMap(); + return; + } + initialized = true; + checkStatus(); loadImages(); loadLocationInputs(); @@ -39,14 +52,6 @@ const WeatherSat = (function() { startCountdownTimer(); checkSchedulerStatus(); initGroundMap(); - - // Re-filter passes when satellite selection changes - const satSelect = document.getElementById('weatherSatSelect'); - if (satSelect) { - satSelect.addEventListener('change', () => { - applyPassFilter(); - }); - } } /** diff --git a/templates/index.html b/templates/index.html index 6b6a320..988aae2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4386,8 +4386,11 @@ } } + let modeSwitchRequestId = 0; + // Mode switching async function switchMode(mode, options = {}) { + const requestId = ++modeSwitchRequestId; const { updateUrl = true } = options; const switchStartMs = performance.now(); const previousMode = currentMode; @@ -4457,6 +4460,7 @@ const stopPhaseMs = Math.round(performance.now() - stopPhaseStartMs); await styleReadyPromise; await scriptReadyPromise; + if (requestId !== modeSwitchRequestId) return; // Generic module cleanup — destroy previous mode's timers, SSE, etc. if (previousMode && previousMode !== mode) { @@ -4465,6 +4469,7 @@ try { destroyFn(); } catch(e) { console.warn(`[switchMode] destroy ${previousMode} failed:`, e); } } } + if (requestId !== modeSwitchRequestId) return; currentMode = mode; document.body.setAttribute('data-mode', mode); @@ -4480,6 +4485,7 @@ // Sync with local status syncLocalModeStates(); } + if (requestId !== modeSwitchRequestId) return; // Close dropdowns and update active state closeAllDropdowns(); @@ -4799,6 +4805,7 @@ } else if (mode === 'ook') { OokMode.init(); } + if (requestId !== modeSwitchRequestId) return; // Waterfall destroy is now handled by moduleDestroyMap above.