mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-04-29 22:55:23 +00:00
fix: heatmap opacity flash on new packet arrival
When new data arrived, toggleHeatmap() destroyed and recreated the heat layer, causing a brief flash at full opacity before the CSS opacity was applied via setTimeout. Now reuses the existing layer via setLatLngs() for data updates, and hooks the 'add' event for immediate opacity on first creation. No more flash. All 12 E2E tests pass locally.
This commit is contained in:
+25
-25
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="MeshCore Analyzer">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/meshcore-analyzer/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1774373169">
|
||||
<link rel="stylesheet" href="home.css?v=1774373169">
|
||||
<link rel="stylesheet" href="live.css?v=1774373169">
|
||||
<link rel="stylesheet" href="style.css?v=1774374809">
|
||||
<link rel="stylesheet" href="home.css?v=1774374809">
|
||||
<link rel="stylesheet" href="live.css?v=1774374809">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -81,27 +81,27 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1774373169"></script>
|
||||
<script src="customize.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774373169"></script>
|
||||
<script src="hop-resolver.js?v=1774373169"></script>
|
||||
<script src="hop-display.js?v=1774373169"></script>
|
||||
<script src="app.js?v=1774373169"></script>
|
||||
<script src="home.js?v=1774373169"></script>
|
||||
<script src="packet-filter.js?v=1774373169"></script>
|
||||
<script src="packets.js?v=1774373169"></script>
|
||||
<script src="map.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774373169" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1774374809"></script>
|
||||
<script src="customize.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774374809"></script>
|
||||
<script src="hop-resolver.js?v=1774374809"></script>
|
||||
<script src="hop-display.js?v=1774374809"></script>
|
||||
<script src="app.js?v=1774374809"></script>
|
||||
<script src="home.js?v=1774374809"></script>
|
||||
<script src="packet-filter.js?v=1774374809"></script>
|
||||
<script src="packets.js?v=1774374809"></script>
|
||||
<script src="map.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774374809" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+22
-14
@@ -722,27 +722,35 @@
|
||||
}
|
||||
|
||||
function toggleHeatmap(on) {
|
||||
if (heatLayer) { map.removeLayer(heatLayer); heatLayer = null; window._meshcoreHeatLayer = null; }
|
||||
if (!on || !map) return;
|
||||
if (!on || !map) {
|
||||
if (heatLayer) { map.removeLayer(heatLayer); heatLayer = null; window._meshcoreHeatLayer = null; }
|
||||
return;
|
||||
}
|
||||
const points = nodes
|
||||
.filter(n => n.lat != null && n.lon != null)
|
||||
.map(n => {
|
||||
const weight = n.advert_count || 1;
|
||||
return [n.lat, n.lon, weight];
|
||||
});
|
||||
if (points.length && typeof L.heatLayer === 'function') {
|
||||
var savedOpacity = parseFloat(localStorage.getItem('meshcore-heatmap-opacity'));
|
||||
if (isNaN(savedOpacity)) savedOpacity = 0.25;
|
||||
heatLayer = L.heatLayer(points, {
|
||||
radius: 25, blur: 15, maxZoom: 14, minOpacity: 0.05,
|
||||
gradient: { 0.2: '#0d47a1', 0.4: '#1565c0', 0.6: '#42a5f5', 0.8: '#ffca28', 1.0: '#ff5722' }
|
||||
}).addTo(map);
|
||||
// Set overall layer opacity (affects all gradient colors, not just minimum)
|
||||
heatLayer.getContainer && heatLayer.getContainer() ?
|
||||
(heatLayer.getContainer().style.opacity = savedOpacity) :
|
||||
setTimeout(function() { if (heatLayer._canvas) heatLayer._canvas.style.opacity = savedOpacity; }, 100);
|
||||
window._meshcoreHeatLayer = heatLayer;
|
||||
if (!points.length || typeof L.heatLayer !== 'function') return;
|
||||
var savedOpacity = parseFloat(localStorage.getItem('meshcore-heatmap-opacity'));
|
||||
if (isNaN(savedOpacity)) savedOpacity = 0.25;
|
||||
// Update existing layer data without recreating (avoids opacity flash)
|
||||
if (heatLayer) {
|
||||
heatLayer.setLatLngs(points);
|
||||
return;
|
||||
}
|
||||
heatLayer = L.heatLayer(points, {
|
||||
radius: 25, blur: 15, maxZoom: 14, minOpacity: 0.05,
|
||||
gradient: { 0.2: '#0d47a1', 0.4: '#1565c0', 0.6: '#42a5f5', 0.8: '#ffca28', 1.0: '#ff5722' }
|
||||
});
|
||||
// Set opacity on canvas BEFORE it's visible — hook the 'add' event
|
||||
heatLayer.on('add', function() {
|
||||
var canvas = heatLayer._canvas || (heatLayer.getContainer && heatLayer.getContainer());
|
||||
if (canvas) canvas.style.opacity = savedOpacity;
|
||||
});
|
||||
heatLayer.addTo(map);
|
||||
window._meshcoreHeatLayer = heatLayer;
|
||||
}
|
||||
|
||||
let _themeRefreshHandler = null;
|
||||
|
||||
Reference in New Issue
Block a user