diff --git a/app/templates/v2/components/queue_widget.html b/app/templates/v2/components/queue_widget.html index c9197c2..6624d17 100644 --- a/app/templates/v2/components/queue_widget.html +++ b/app/templates/v2/components/queue_widget.html @@ -86,8 +86,17 @@ }) .catch(function () { /* still */ }); } - // erster Aufruf direkt + danach alle 5 s + // Polling: erster Aufruf + alle 5 s. Bei Tab-Wechsel/Hide pausieren, + // damit der Browser CPU/Akku spart und keine Listener akkumuliert (#183). poll(); - setInterval(poll, 5000); + let _qwInterval = setInterval(poll, 5000); + document.addEventListener('visibilitychange', function () { + if (document.hidden) { + if (_qwInterval) { clearInterval(_qwInterval); _qwInterval = null; } + } else if (!_qwInterval) { + poll(); + _qwInterval = setInterval(poll, 5000); + } + }); })(); diff --git a/app/templates/v2/screens/admin_queue.html b/app/templates/v2/screens/admin_queue.html index 26ac41a..fe8c2d9 100644 --- a/app/templates/v2/screens/admin_queue.html +++ b/app/templates/v2/screens/admin_queue.html @@ -217,6 +217,15 @@ async function refresh() { } refresh(); -setInterval(refresh, 5000); +let _queueInterval = setInterval(refresh, 5000); +// Pause-Polling wenn Tab versteckt (#183). +document.addEventListener('visibilitychange', function () { + if (document.hidden) { + if (_queueInterval) { clearInterval(_queueInterval); _queueInterval = null; } + } else if (!_queueInterval) { + refresh(); + _queueInterval = setInterval(refresh, 5000); + } +}); {% endblock %} diff --git a/app/templates/v2/screens/admin_stand.html b/app/templates/v2/screens/admin_stand.html index d9d5346..cfd8a09 100644 --- a/app/templates/v2/screens/admin_stand.html +++ b/app/templates/v2/screens/admin_stand.html @@ -282,6 +282,15 @@ async function loadStand() { } loadStand(); -setInterval(loadStand, 30000); +let _standInterval = setInterval(loadStand, 30000); +// Pause-Polling wenn Tab versteckt (#183). +document.addEventListener('visibilitychange', function () { + if (document.hidden) { + if (_standInterval) { clearInterval(_standInterval); _standInterval = null; } + } else if (!_standInterval) { + loadStand(); + _standInterval = setInterval(loadStand, 30000); + } +}); {% endblock %} diff --git a/app/templates/v2/screens/auswertungen.html b/app/templates/v2/screens/auswertungen.html index 7680dbc..9eb8a56 100644 --- a/app/templates/v2/screens/auswertungen.html +++ b/app/templates/v2/screens/auswertungen.html @@ -665,8 +665,12 @@ async function showZeitreihe(bundesland, partei) { ''; if (window.Chart) { + // Modal-Chart-Tracking: vor Re-Render alte Instanz zerstören. + if (window._zeitreiheModalChart) { + try { window._zeitreiheModalChart.destroy(); } catch (_) {} + } const ctx = document.getElementById('zeitreihe-chart'); - new Chart(ctx, { + window._zeitreiheModalChart = new Chart(ctx, { type: 'line', data: { labels: z.wahlperioden.map(r => 'WP ' + r.wp), @@ -700,13 +704,23 @@ async function showZeitreihe(bundesland, partei) { } } +function _destroyModalChart() { + if (window._zeitreiheModalChart) { + try { window._zeitreiheModalChart.destroy(); } catch (_) {} + window._zeitreiheModalChart = null; + } +} function closeModal(ev) { if (!ev || ev.target.id === 'modal-backdrop') { document.getElementById('modal-backdrop').classList.remove('show'); + _destroyModalChart(); } } document.addEventListener('keydown', (e) => { - if (e.key === 'Escape') document.getElementById('modal-backdrop').classList.remove('show'); + if (e.key === 'Escape') { + document.getElementById('modal-backdrop').classList.remove('show'); + _destroyModalChart(); + } }); // ─── Stimmverhalten × Gemeinwohl ──────────────────────────────────────────── diff --git a/app/templates/v2/screens/cluster.html b/app/templates/v2/screens/cluster.html index 4694f31..dc82cab 100644 --- a/app/templates/v2/screens/cluster.html +++ b/app/templates/v2/screens/cluster.html @@ -267,7 +267,21 @@ function showCluster(idx) { renderClusterGraph(cl); } +// Aktuelle Force-Simulation — beim Verlassen der Detail-View stoppen, +// sonst läuft Tick-Listener weiter und akkumuliert Memory (#183). +let _currentForceSim = null; + +function _stopForceSim() { + if (_currentForceSim) { + try { _currentForceSim.stop(); } catch (_) {} + _currentForceSim = null; + } + const container = document.getElementById('cluster-graph'); + if (container) container.innerHTML = ''; +} + function renderClusterGraph(cl) { + _stopForceSim(); const container = document.getElementById('cluster-graph'); if (!container || typeof d3 === 'undefined') return; const rawNodes = cl.nodes || []; @@ -309,7 +323,7 @@ function renderClusterGraph(cl) { .attr('width', width).attr('height', height) .attr('viewBox', `0 0 ${width} ${height}`); - const sim = d3.forceSimulation(nodes) + const sim = (_currentForceSim = d3.forceSimulation(nodes)) .force('link', d3.forceLink(links).id(d => d.id) .distance(l => (1 - (l.sim || 0)) * 220 + 50) .strength(l => l.sim || 0.5)) @@ -358,6 +372,7 @@ function renderClusterGraph(cl) { } function showList() { + _stopForceSim(); document.getElementById('cluster-list').style.display = ''; document.getElementById('cluster-detail').style.display = 'none'; }