From 63188c3b2c3cf6b5e7f37aba3f48e62090a3ca7e Mon Sep 17 00:00:00 2001 From: ajvpot <553597+ajvpot@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:45:26 +0200 Subject: [PATCH] track markers better so the popup doesnt get closed when the data reloads --- src/components/MapView.tsx | 185 ++++++++++++++++++++++++------------- 1 file changed, 123 insertions(+), 62 deletions(-) diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx index b2af0b3..a5739f5 100644 --- a/src/components/MapView.tsx +++ b/src/components/MapView.tsx @@ -31,76 +31,137 @@ type NodePosition = { }; type ClusteredMarkersProps = { nodes: NodePosition[] }; -function ClusteredMarkers({ nodes }: ClusteredMarkersProps) { + +// Individual marker component +function IndividualMarker({ node, showNodeNames }: { node: NodePosition; showNodeNames: boolean }) { const map = useMap(); - const configResult = useConfig(); - const config = configResult?.config; + const markerRef = useRef(null); + useEffect(() => { if (!map) return; - // Remove any previous layers - map.eachLayer((layer: any) => { - if (layer && layer._isClusterLayer) { - map.removeLayer(layer); - } + + const icon = L.divIcon({ + className: 'custom-node-marker-container', + iconSize: [16, 32], + iconAnchor: [8, 8], + html: renderToString(), }); - if (config?.clustering === false) { - // Add markers individually - const markerLayers: any[] = []; - nodes.forEach((node: NodePosition) => { - const icon = L.divIcon({ - className: 'custom-node-marker-container', - iconSize: [16, 32], - iconAnchor: [8, 8], - html: renderToString(), - }); - const marker = L.marker([node.latitude, node.longitude], { icon }); - (marker as any).options.nodeData = node; - marker.bindPopup(renderToString()); - marker.addTo(map); - markerLayers.push(marker); + + const marker = L.marker([node.latitude, node.longitude], { icon }); + (marker as any).options.nodeData = node; + marker.bindPopup(renderToString()); + marker.addTo(map); + markerRef.current = marker; + + return () => { + if (markerRef.current && map.hasLayer(markerRef.current)) { + map.removeLayer(markerRef.current); + } + }; + }, [map, node.latitude, node.longitude, node.node_id, showNodeNames]); + + // Update marker when node data changes + useEffect(() => { + if (markerRef.current) { + const currentPos = markerRef.current.getLatLng(); + if (currentPos.lat !== node.latitude || currentPos.lng !== node.longitude) { + markerRef.current.setLatLng([node.latitude, node.longitude]); + } + + // Update icon and popup + const icon = L.divIcon({ + className: 'custom-node-marker-container', + iconSize: [16, 32], + iconAnchor: [8, 8], + html: renderToString(), }); - // Mark for cleanup - markerLayers.forEach(layer => { layer._isClusterLayer = true; }); - return () => { - markerLayers.forEach(layer => map.removeLayer(layer)); - }; - } else { - // Clustered mode (existing logic) - const iconCreateFunction = (cluster: any) => { - const children = cluster.getAllChildMarkers(); - return L.divIcon({ - html: renderToString({children}), - className: 'custom-cluster-icon', - iconSize: [40, 40], - iconAnchor: [20, 20], - }); - }; - const markers = (L as any).markerClusterGroup({ - iconCreateFunction, - maxClusterRadius: 40, - }); - nodes.forEach((node: NodePosition) => { - const icon = L.divIcon({ - className: 'custom-node-marker-container', - iconSize: [16, 32], - iconAnchor: [8, 8], - html: renderToString(), - }); - const marker = L.marker([node.latitude, node.longitude], { icon }); - (marker as any).options.nodeData = node; - marker.bindPopup(renderToString()); - markers.addLayer(marker); - }); - markers._isClusterLayer = true; - map.addLayer(markers); - return () => { - map.removeLayer(markers); - }; + markerRef.current.setIcon(icon); + markerRef.current.getPopup()?.setContent(renderToString()); } - }, [map, nodes, config?.clustering, config?.showNodeNames]); + }, [node, showNodeNames]); + return null; } +// Clustered markers component +function ClusteredMarkersGroup({ nodes, showNodeNames }: { nodes: NodePosition[]; showNodeNames: boolean }) { + const map = useMap(); + const clusterGroupRef = useRef(null); + + useEffect(() => { + if (!map) return; + + const iconCreateFunction = (cluster: any) => { + const children = cluster.getAllChildMarkers(); + return L.divIcon({ + html: renderToString({children}), + className: 'custom-cluster-icon', + iconSize: [40, 40], + iconAnchor: [20, 20], + }); + }; + + const markers = (L as any).markerClusterGroup({ + iconCreateFunction, + maxClusterRadius: 40, + }); + + nodes.forEach((node: NodePosition) => { + const icon = L.divIcon({ + className: 'custom-node-marker-container', + iconSize: [16, 32], + iconAnchor: [8, 8], + html: renderToString(), + }); + const marker = L.marker([node.latitude, node.longitude], { icon }); + (marker as any).options.nodeData = node; + marker.bindPopup(renderToString()); + markers.addLayer(marker); + }); + + markers._isClusterLayer = true; + map.addLayer(markers); + clusterGroupRef.current = markers; + + return () => { + if (clusterGroupRef.current && map.hasLayer(clusterGroupRef.current)) { + map.removeLayer(clusterGroupRef.current); + } + }; + }, [map, nodes, showNodeNames]); + + return null; +} + +function ClusteredMarkers({ nodes }: ClusteredMarkersProps) { + const configResult = useConfig(); + const config = configResult?.config; + const showNodeNames = config?.showNodeNames !== false; + + if (config?.clustering === false) { + // Render individual marker components + return ( + <> + {nodes.map((node) => ( + + ))} + + ); + } else { + // Render clustered markers + return ( + + ); + } +} + export default function MapView() { const [nodePositions, setNodePositions] = useState([]); const [bounds, setBounds] = useState<[[number, number], [number, number]] | null>(null); @@ -306,7 +367,7 @@ export default function MapView() { opacity={0.7} /> )} - + );