mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-27 00:47:52 +00:00
feat: split "Mesh live area" into a geo-filter toggle and a selected-area outline
The old single "Mesh live area" checkbox actually showed the homeArea-linked geo_filter boundary, which was confusing paired with the already-existing "Area: X" dropdown (AreaFilter) that filters which nodes show on the map -- picking an area there drew nothing. Now two independent checkboxes on both Map and Live: - "Geo filter boundary" -- unchanged behavior, the geo_filter/homeArea polygon (with buffer zone if configured). - "Selected area outline" -- new, draws whichever area is currently picked via the Area dropdown (falls back to a box when the area has no polygon), checked by default whenever an area is selected.
This commit is contained in:
+71
-3
@@ -17,7 +17,7 @@
|
||||
function cssVar(name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); }
|
||||
function statusGreen() { return cssVar('--status-green') || '#22c55e'; }
|
||||
|
||||
let map, ws, nodesLayer, pathsLayer, animLayer, heatLayer, geoFilterLayer, clickablePathsLayer;
|
||||
let map, ws, nodesLayer, pathsLayer, animLayer, heatLayer, geoFilterLayer, selectedAreaLayer, clickablePathsLayer;
|
||||
// New animation canvas
|
||||
let animCanvas, animCtx;
|
||||
let _dprMedia = null;
|
||||
@@ -1151,7 +1151,8 @@
|
||||
<span id="favDesc" class="sr-only">Show only favorited and claimed nodes</span>
|
||||
<label><input type="checkbox" id="liveMultibyteToggle" aria-describedby="multibyteDesc"> Multibyte only</label>
|
||||
<span id="multibyteDesc" class="sr-only">Show only multibyte (≥2-byte path-hash) packets; hide unreliable single-byte traffic</span>
|
||||
<label id="liveGeoFilterLabel" style="display:none"><input type="checkbox" id="liveGeoFilterToggle"> Mesh live area</label>
|
||||
<label id="liveGeoFilterLabel" style="display:none"><input type="checkbox" id="liveGeoFilterToggle"> Geo filter boundary</label>
|
||||
<label id="liveSelectedAreaLabel" style="display:none"><input type="checkbox" id="liveSelectedAreaToggle"> Selected area outline</label>
|
||||
</div>
|
||||
<div class="live-toggles">
|
||||
<div class="live-node-filter-wrap" style="position:relative">
|
||||
@@ -1856,6 +1857,73 @@
|
||||
} catch (e) { /* no geo filter configured */ }
|
||||
})();
|
||||
|
||||
// Selected area outline — distinct from the geo filter boundary above:
|
||||
// this draws whichever area is currently picked via the "Area: X ▾"
|
||||
// dropdown (AreaFilter, shared with Nodes/Map/Analytics), not
|
||||
// necessarily the homeArea-linked geo_filter. Falls back to a plain
|
||||
// box when the area has no polygon. Only one area can be selected at
|
||||
// a time (AreaFilter is single-select), so at most one outline shows.
|
||||
(function () {
|
||||
var areaColor = cssVar('--geo-filter-color') || '#3b82f6';
|
||||
var areaPolygonsCache = null;
|
||||
async function fetchAreaPolygons() {
|
||||
if (areaPolygonsCache) return areaPolygonsCache;
|
||||
try {
|
||||
areaPolygonsCache = await api('/config/areas/polygons', { ttl: 3600 });
|
||||
} catch (e) {
|
||||
areaPolygonsCache = [];
|
||||
}
|
||||
return areaPolygonsCache;
|
||||
}
|
||||
function latLngsFor(entry) {
|
||||
if (entry.polygon && entry.polygon.length >= 3) {
|
||||
return entry.polygon.map(function (p) { return [p[0], p[1]]; });
|
||||
}
|
||||
if (entry.latMin != null && entry.latMax != null && entry.lonMin != null && entry.lonMax != null) {
|
||||
return [
|
||||
[entry.latMin, entry.lonMin], [entry.latMin, entry.lonMax],
|
||||
[entry.latMax, entry.lonMax], [entry.latMax, entry.lonMin],
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async function refresh() {
|
||||
var label = document.getElementById('liveSelectedAreaLabel');
|
||||
var checkbox = document.getElementById('liveSelectedAreaToggle');
|
||||
if (selectedAreaLayer) { map.removeLayer(selectedAreaLayer); selectedAreaLayer = null; }
|
||||
var key = AreaFilter.getSelected();
|
||||
if (!key) {
|
||||
if (label) label.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
var areas = await fetchAreaPolygons();
|
||||
var entry = areas.find(function (a) { return a.key === key; });
|
||||
var latlngs = entry && latLngsFor(entry);
|
||||
if (!latlngs) {
|
||||
if (label) label.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
selectedAreaLayer = L.polygon(latlngs, {
|
||||
color: areaColor, weight: 2, opacity: 0.8, dashArray: '2 4',
|
||||
fillColor: areaColor, fillOpacity: 0.06
|
||||
});
|
||||
if (label) label.style.display = '';
|
||||
var saved = localStorage.getItem('meshcore-map-selected-area-outline') !== 'false';
|
||||
if (checkbox) checkbox.checked = saved;
|
||||
if (saved) selectedAreaLayer.addTo(map);
|
||||
}
|
||||
var checkbox = document.getElementById('liveSelectedAreaToggle');
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', function (e) {
|
||||
localStorage.setItem('meshcore-map-selected-area-outline', e.target.checked);
|
||||
if (!selectedAreaLayer) return;
|
||||
if (e.target.checked) { selectedAreaLayer.addTo(map); } else { map.removeLayer(selectedAreaLayer); }
|
||||
});
|
||||
}
|
||||
AreaFilter.onChange(refresh);
|
||||
refresh();
|
||||
})();
|
||||
|
||||
const matrixToggle = document.getElementById('liveMatrixToggle');
|
||||
matrixToggle.checked = matrixMode;
|
||||
matrixToggle.addEventListener('change', (e) => {
|
||||
@@ -4546,7 +4614,7 @@
|
||||
}
|
||||
_navCleanup = null;
|
||||
}
|
||||
nodesLayer = pathsLayer = animLayer = heatLayer = geoFilterLayer = clickablePathsLayer = null;
|
||||
nodesLayer = pathsLayer = animLayer = heatLayer = geoFilterLayer = selectedAreaLayer = clickablePathsLayer = null;
|
||||
clickablePaths = [];
|
||||
stopMatrixRain();
|
||||
// #1572 — clear body.live-fullscreen on route exit. The class hides
|
||||
|
||||
+70
-1
@@ -15,6 +15,7 @@
|
||||
let wsHandler = null;
|
||||
let heatLayer = null;
|
||||
let geoFilterLayer = null;
|
||||
let selectedAreaLayer = null;
|
||||
let affinityLayer = null;
|
||||
let affinityData = null;
|
||||
let userHasMoved = false;
|
||||
@@ -204,7 +205,8 @@
|
||||
<label for="mcHeatmap"><input type="checkbox" id="mcHeatmap"> Heat map</label>
|
||||
<label for="mcHashLabels"><input type="checkbox" id="mcHashLabels"> Hash prefix labels</label>
|
||||
<label for="mcMultiByte"><input type="checkbox" id="mcMultiByte"> Multi-byte support</label>
|
||||
<label id="mcGeoFilterLabel" for="mcGeoFilter" style="display:none"><input type="checkbox" id="mcGeoFilter"> Mesh live area</label>
|
||||
<label id="mcGeoFilterLabel" for="mcGeoFilter" style="display:none"><input type="checkbox" id="mcGeoFilter"> Geo filter boundary</label>
|
||||
<label id="mcSelectedAreaLabel" for="mcSelectedArea" style="display:none"><input type="checkbox" id="mcSelectedArea"> Selected area outline</label>
|
||||
</fieldset>
|
||||
<div id="mapAreaFilter"></div>
|
||||
<fieldset class="mc-section">
|
||||
@@ -588,6 +590,73 @@
|
||||
} catch (e) { /* no geo filter configured */ }
|
||||
})();
|
||||
|
||||
// Selected area outline — distinct from the geo filter boundary above:
|
||||
// this draws whichever area is currently picked via the "Area: X ▾"
|
||||
// dropdown (AreaFilter, shared with Nodes/Live/Analytics), not
|
||||
// necessarily the homeArea-linked geo_filter. Falls back to a plain
|
||||
// box when the area has no polygon. Only one area can be selected at
|
||||
// a time (AreaFilter is single-select), so at most one outline shows.
|
||||
(function () {
|
||||
var areaColor = getComputedStyle(document.documentElement).getPropertyValue('--geo-filter-color').trim() || '#3b82f6';
|
||||
var areaPolygonsCache = null;
|
||||
async function fetchAreaPolygons() {
|
||||
if (areaPolygonsCache) return areaPolygonsCache;
|
||||
try {
|
||||
areaPolygonsCache = await api('/config/areas/polygons', { ttl: 3600 });
|
||||
} catch (e) {
|
||||
areaPolygonsCache = [];
|
||||
}
|
||||
return areaPolygonsCache;
|
||||
}
|
||||
function latLngsFor(entry) {
|
||||
if (entry.polygon && entry.polygon.length >= 3) {
|
||||
return entry.polygon.map(function (p) { return [p[0], p[1]]; });
|
||||
}
|
||||
if (entry.latMin != null && entry.latMax != null && entry.lonMin != null && entry.lonMax != null) {
|
||||
return [
|
||||
[entry.latMin, entry.lonMin], [entry.latMin, entry.lonMax],
|
||||
[entry.latMax, entry.lonMax], [entry.latMax, entry.lonMin],
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async function refresh() {
|
||||
var label = document.getElementById('mcSelectedAreaLabel');
|
||||
var checkbox = document.getElementById('mcSelectedArea');
|
||||
if (selectedAreaLayer) { map.removeLayer(selectedAreaLayer); selectedAreaLayer = null; }
|
||||
var key = AreaFilter.getSelected();
|
||||
if (!key) {
|
||||
if (label) label.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
var areas = await fetchAreaPolygons();
|
||||
var entry = areas.find(function (a) { return a.key === key; });
|
||||
var latlngs = entry && latLngsFor(entry);
|
||||
if (!latlngs) {
|
||||
if (label) label.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
selectedAreaLayer = L.polygon(latlngs, {
|
||||
color: areaColor, weight: 2, opacity: 0.8, dashArray: '2 4',
|
||||
fillColor: areaColor, fillOpacity: 0.06
|
||||
});
|
||||
if (label) label.style.display = '';
|
||||
var saved = localStorage.getItem('meshcore-map-selected-area-outline') !== 'false';
|
||||
if (checkbox) checkbox.checked = saved;
|
||||
if (saved) selectedAreaLayer.addTo(map);
|
||||
}
|
||||
var checkbox = document.getElementById('mcSelectedArea');
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('change', function (e) {
|
||||
localStorage.setItem('meshcore-map-selected-area-outline', e.target.checked);
|
||||
if (!selectedAreaLayer) return;
|
||||
if (e.target.checked) { selectedAreaLayer.addTo(map); } else { map.removeLayer(selectedAreaLayer); }
|
||||
});
|
||||
}
|
||||
AreaFilter.onChange(refresh);
|
||||
refresh();
|
||||
})();
|
||||
|
||||
// WS for live advert updates
|
||||
wsHandler = debouncedOnWS(function (msgs) {
|
||||
if (msgs.some(function (m) { return m.type === 'packet' && m.data?.decoded?.header?.payloadTypeName === 'ADVERT'; })) {
|
||||
|
||||
Reference in New Issue
Block a user