import { NodeDetail } from "../../components/PageComponents/Map/NodeDetail.tsx"; import { Avatar } from "../../components/UI/Avatar.tsx"; import { PageLayout } from "@components/PageLayout.tsx"; import { Sidebar } from "@components/Sidebar.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; import type { Protobuf } from "@meshtastic/core"; import { bbox, lineString } from "@turf/turf"; import { FunnelIcon, MapPinIcon } from "lucide-react"; import { useCallback, useDeferredValue, useMemo, useState } from "react"; import { Marker, Popup, useMap } from "react-map-gl/maplibre"; import { Map } from "@components/Map.tsx"; import { type FilterState, useFilterNode, } from "@components/generic/Filter/useFilterNode.ts"; import { FilterControl } from "@components/generic/Filter/FilterControl.tsx"; import { cn } from "@core/utils/cn.ts"; type NodePosition = { latitude: number; longitude: number; }; const convertToLatLng = (position?: { latitudeI?: number; longitudeI?: number; }): NodePosition => ({ latitude: (position?.latitudeI ?? 0) / 1e7, longitude: (position?.longitudeI ?? 0) / 1e7, }); const MapPage = () => { const { getNodes, waypoints, hasNodeError } = useDevice(); const { nodeFilter, defaultFilterValues, isFilterDirty } = useFilterNode(); const { default: map } = useMap(); const [selectedNode, setSelectedNode] = useState< Protobuf.Mesh.NodeInfo | null >(null); const validNodes = useMemo( () => getNodes( (node): node is Protobuf.Mesh.NodeInfo => Boolean(node.position?.latitudeI), ), [getNodes], ); const [filterState, setFilterState] = useState(() => defaultFilterValues ); const deferredFilterState = useDeferredValue(filterState); const filteredNodes = useMemo( () => validNodes.filter((node) => nodeFilter(node, deferredFilterState)), [validNodes, deferredFilterState], ); const handleMarkerClick = useCallback( (node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => { event?.originalEvent?.stopPropagation(); setSelectedNode(node); if (map) { const position = convertToLatLng(node.position); map.easeTo({ center: [position.longitude, position.latitude], zoom: map?.getZoom(), }); } }, [map], ); // Get the bounds of the map based on the nodes furtherest away from center const getMapBounds = useCallback(() => { if (!map || validNodes.length === 0) return; if (validNodes.length === 1) { map.easeTo({ zoom: map.getZoom(), center: [ (validNodes[0].position?.longitudeI ?? 0) / 1e7, (validNodes[0].position?.latitudeI ?? 0) / 1e7, ], }); return; } const line = lineString( validNodes.map((n) => [ (n.position?.latitudeI ?? 0) / 1e7, (n.position?.longitudeI ?? 0) / 1e7, ]), ); const bounds = bbox(line); const center = map.cameraForBounds( [ [bounds[1], bounds[0]], [bounds[3], bounds[2]], ], { padding: { top: 10, bottom: 10, left: 10, right: 10 } }, ); if (center) { map.easeTo(center); } }, [map, validNodes]); // Generate all markers const markers = useMemo( () => filteredNodes.map((node) => { const position = convertToLatLng(node.position); return ( handleMarkerClick(node, e)} > ); }), [filteredNodes, handleMarkerClick], ); return ( <> }> {waypoints.map((wp) => (
))} {markers} {selectedNode && (() => { const position = convertToLatLng(selectedNode.position); return ( setSelectedNode(null)} className="w-full" > ); })()}
, showTextSearch: true, }} />
); }; export default MapPage;