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;