diff --git a/public/index.html b/public/index.html
index d52d2437..20df1550 100644
--- a/public/index.html
+++ b/public/index.html
@@ -88,7 +88,7 @@
-
+
diff --git a/public/live.js b/public/live.js
index b9c3b5d5..d3d54dc3 100644
--- a/public/live.js
+++ b/public/live.js
@@ -12,6 +12,7 @@
let soundEnabled = false;
let showGhostHops = localStorage.getItem('live-ghost-hops') !== 'false';
let realisticPropagation = localStorage.getItem('live-realistic-propagation') === 'true';
+ let showOnlyFavorites = localStorage.getItem('live-favorites-only') === 'true';
const propagationBuffer = new Map(); // hash -> {timer, packets[]}
let _onResize = null;
let _navCleanup = null;
@@ -630,6 +631,8 @@
Show interpolated ghost markers for unknown hops
Buffer packets by hash and animate all paths simultaneously
+
+ Show only favorited and claimed nodes
@@ -775,6 +778,14 @@
localStorage.setItem('live-realistic-propagation', realisticPropagation);
});
+ const favoritesToggle = document.getElementById('liveFavoritesToggle');
+ favoritesToggle.checked = showOnlyFavorites;
+ favoritesToggle.addEventListener('change', (e) => {
+ showOnlyFavorites = e.target.checked;
+ localStorage.setItem('live-favorites-only', showOnlyFavorites);
+ applyFavoritesFilter();
+ });
+
// Feed show/hide
const feedEl = document.getElementById('liveFeed');
// Keyboard support for feed items (event delegation)
@@ -1181,6 +1192,37 @@
if (heatLayer) { map.removeLayer(heatLayer); heatLayer = null; }
}
+ function getLiveFavorites() {
+ try { return new Set(JSON.parse(localStorage.getItem('meshcore-favorites') || '[]')); } catch { return new Set(); }
+ }
+ function getLiveMyNodes() {
+ try { return new Set(JSON.parse(localStorage.getItem('meshcore-my-nodes') || '[]')); } catch { return new Set(); }
+ }
+ function isNodeFavorited(pubkey) {
+ const favs = getLiveFavorites();
+ const mine = getLiveMyNodes();
+ return favs.has(pubkey) || mine.has(pubkey);
+ }
+ function applyFavoritesFilter() {
+ Object.keys(nodeMarkers).forEach(key => {
+ const marker = nodeMarkers[key];
+ if (!marker) return;
+ const visible = !showOnlyFavorites || isNodeFavorited(key);
+ if (visible) {
+ if (!nodesLayer.hasLayer(marker)) { marker.addTo(nodesLayer); if (marker._glowMarker) marker._glowMarker.addTo(nodesLayer); }
+ } else {
+ if (nodesLayer.hasLayer(marker)) { nodesLayer.removeLayer(marker); if (marker._glowMarker) nodesLayer.removeLayer(marker._glowMarker); }
+ }
+ });
+ const _el2 = document.getElementById('liveNodeCount');
+ if (_el2) {
+ const count = showOnlyFavorites
+ ? Object.keys(nodeMarkers).filter(k => isNodeFavorited(k)).length
+ : Object.keys(nodeMarkers).length;
+ _el2.textContent = count;
+ }
+ }
+
function addNodeMarker(n) {
if (nodeMarkers[n.public_key]) return nodeMarkers[n.public_key];
const color = ROLE_COLORS[n.role] || ROLE_COLORS.unknown;
@@ -1208,6 +1250,10 @@
marker._baseColor = color;
marker._baseSize = size;
nodeMarkers[n.public_key] = marker;
+ if (showOnlyFavorites && !isNodeFavorited(n.public_key)) {
+ nodesLayer.removeLayer(marker);
+ nodesLayer.removeLayer(glow);
+ }
return marker;
}
@@ -1269,6 +1315,13 @@
playSound(typeName);
addFeedItem(icon, typeName, payload, hops, color, pkt);
+ // Favorites filter: skip animation if no involved nodes are favorited
+ if (showOnlyFavorites) {
+ const involvedKeys = hops.map(h => h.id || h.public_key).filter(Boolean);
+ if (payload.pubKey) involvedKeys.push(payload.pubKey);
+ if (!involvedKeys.some(k => isNodeFavorited(k))) return;
+ }
+
// If ADVERT, ensure node appears on map
if (typeName === 'ADVERT' && payload.pubKey) {
const key = payload.pubKey;