import Map, { FullscreenControl, NavigationControl, ScaleControl, Marker, Popup, MapProvider, } from 'react-map-gl/maplibre' import type { MapRef, MapLayerMouseEvent } from 'react-map-gl/maplibre' import maplibregl from 'maplibre-gl' import 'maplibre-gl/dist/maplibre-gl.css' import { Protocol } from 'pmtiles' import { useEffect, useRef, useState, useCallback } from 'react' import { useMapMarkers, PIN_COLORS } from '~/hooks/useMapMarkers' import type { PinColorId } from '~/hooks/useMapMarkers' import MarkerPin from './MarkerPin' import MarkerPanel from './MarkerPanel' import CoordinateOverlay from './CoordinateOverlay' import ScaleUnitToggle from './ScaleUnitToggle' type ScaleUnit = 'imperial' | 'metric' type MapComponentProps = { isHoveringUI: boolean showCoordinatesEnabled: boolean } export default function MapComponent({ isHoveringUI, showCoordinatesEnabled, }: MapComponentProps) { const mapRef = useRef(null) const animationFrameRef = useRef(null) const { markers, addMarker, deleteMarker } = useMapMarkers() const [isDraggingMap, setIsDraggingMap] = useState(false) const [placingMarker, setPlacingMarker] = useState<{ lng: number; lat: number } | null>(null) const [markerName, setMarkerName] = useState('') const [markerColor, setMarkerColor] = useState('orange') const [selectedMarkerId, setSelectedMarkerId] = useState(null) const [scaleUnit, setScaleUnit] = useState( () => (localStorage.getItem('nomad:map-scale-unit') as ScaleUnit) || 'metric' ) const [cursorLngLat, setCursorLngLat] = useState<{ lng: number lat: number x: number y: number } | null>(null) const [showCoordinates, setShowCoordinates] = useState(false) useEffect(() => { const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) return () => { maplibregl.removeProtocol('pmtiles') } }, []) useEffect(() => { return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current) } } }, []) const hideCoordinates = useCallback(() => { setShowCoordinates(false) setCursorLngLat(null) }, []) const handleScaleUnitChange = useCallback((unit: ScaleUnit) => { setScaleUnit(unit) localStorage.setItem('nomad:map-scale-unit', unit) }, []) const handleMouseMove = useCallback( (e: MapLayerMouseEvent) => { const target = e.originalEvent.target as HTMLElement | null if ( !showCoordinatesEnabled || isHoveringUI || isDraggingMap || target?.closest('.maplibregl-control-container, .maplibregl-ctrl') ) { hideCoordinates() return } if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current) } animationFrameRef.current = requestAnimationFrame(() => { setShowCoordinates(true) setCursorLngLat({ lng: e.lngLat.lng, lat: e.lngLat.lat, x: e.point.x, y: e.point.y, }) }) }, [hideCoordinates, isHoveringUI, isDraggingMap, showCoordinatesEnabled] ) const handleMapClick = useCallback((e: MapLayerMouseEvent) => { setPlacingMarker({ lng: e.lngLat.lng, lat: e.lngLat.lat }) setMarkerName('') setMarkerColor('orange') setSelectedMarkerId(null) }, []) const handleSaveMarker = useCallback(() => { if (placingMarker && markerName.trim()) { addMarker(markerName.trim(), placingMarker.lng, placingMarker.lat, markerColor) setPlacingMarker(null) setMarkerName('') setMarkerColor('orange') } }, [placingMarker, markerName, markerColor, addMarker]) const handleFlyTo = useCallback((longitude: number, latitude: number) => { mapRef.current?.flyTo({ center: [longitude, latitude], zoom: 12, duration: 1500 }) }, []) const handleDeleteMarker = useCallback( (id: number) => { if (selectedMarkerId === id) setSelectedMarkerId(null) deleteMarker(id) }, [selectedMarkerId, deleteMarker] ) const selectedMarker = selectedMarkerId ? markers.find((m) => m.id === selectedMarkerId) : null return (
{ setIsDraggingMap(false) hideCoordinates() }} onMouseMoveCapture={(e) => { const target = e.target as HTMLElement | null if ( target?.closest( '.maplibregl-control-container, .maplibregl-ctrl, .maplibregl-ctrl-group, .maplibregl-ctrl-scale' ) ) { hideCoordinates() } }} > { setIsDraggingMap(true) hideCoordinates() }} onMouseUp={() => { setIsDraggingMap(false) }} onDragStart={() => { setIsDraggingMap(true) hideCoordinates() }} onDragEnd={() => { setIsDraggingMap(false) hideCoordinates() }} onClick={handleMapClick} onMouseMove={handleMouseMove} onMouseLeave={hideCoordinates} > {showCoordinates && cursorLngLat && ( )} {markers.map((marker) => ( { e.originalEvent.stopPropagation() setSelectedMarkerId(marker.id === selectedMarkerId ? null : marker.id) setPlacingMarker(null) }} > c.id === marker.color)?.hex} active={marker.id === selectedMarkerId} /> ))} {selectedMarker && ( setSelectedMarkerId(null)} closeOnClick={false} >
{selectedMarker.name}
)} {placingMarker && ( setPlacingMarker(null)} closeOnClick={false} >
setMarkerName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSaveMarker() if (e.key === 'Escape') setPlacingMarker(null) }} className="block w-full rounded border border-gray-300 px-2 py-1 text-sm placeholder:text-gray-400 focus:outline-none focus:border-gray-500" />
{PIN_COLORS.map((c) => ( ))}
)}
) }