import Map, { FullscreenControl, NavigationControl, ScaleControl, Marker, 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 MarkerPin from './MarkerPin' import MarkerPanel from './MarkerPanel' import ViewMapMarkerPopup from './ViewMapMarkerPopup' import MapMarkerFormPopup from './MapMarkerFormPopup' import ScaleUnitToggle from './ScaleUnitToggle' type ScaleUnit = 'imperial' | 'metric' export default function MapComponent() { const mapRef = useRef(null) const { markers, addMarker, updateMarker, deleteMarker } = useMapMarkers() const [placingMarker, setPlacingMarker] = useState<{ lng: number; lat: number } | null>(null) const [selectedMarkerId, setSelectedMarkerId] = useState(null) const [editingMarkerId, setEditingMarkerId] = useState(null) const [hasUnsavedMarkerChanges, setHasUnsavedMarkerChanges] = useState(false) const [scaleUnit, setScaleUnit] = useState( () => (localStorage.getItem('nomad:map-scale-unit') as ScaleUnit) || 'metric' ) const confirmDiscardMarkerChanges = useCallback(() => { if (!hasUnsavedMarkerChanges) return true return window.confirm('Discard unsaved marker changes?') }, [hasUnsavedMarkerChanges]) useEffect(() => { const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) return () => { maplibregl.removeProtocol('pmtiles') } }, []) const handleScaleUnitChange = useCallback((unit: ScaleUnit) => { setScaleUnit(unit) localStorage.setItem('nomad:map-scale-unit', unit) }, []) const handleMapClick = useCallback( (e: MapLayerMouseEvent) => { if (!confirmDiscardMarkerChanges()) return setPlacingMarker({ lng: e.lngLat.lng, lat: e.lngLat.lat }) setSelectedMarkerId(null) setEditingMarkerId(null) setHasUnsavedMarkerChanges(false) }, [confirmDiscardMarkerChanges] ) 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) } if (editingMarkerId === id) { setEditingMarkerId(null) } deleteMarker(id) }, [selectedMarkerId, editingMarkerId, deleteMarker] ) const selectedMarker = selectedMarkerId ? markers.find((marker) => marker.id === selectedMarkerId) : null return ( {markers.map((marker) => ( { e.originalEvent.stopPropagation() if (!confirmDiscardMarkerChanges()) return setSelectedMarkerId(marker.id === selectedMarkerId ? null : marker.id) setPlacingMarker(null) setEditingMarkerId(null) setHasUnsavedMarkerChanges(false) }} > color.id === marker.color)?.hex} active={marker.id === selectedMarkerId} /> ))} {placingMarker && ( { await addMarker(name, placingMarker.lng, placingMarker.lat, color, notes || undefined) setPlacingMarker(null) }} onCancel={() => { if (!confirmDiscardMarkerChanges()) return setPlacingMarker(null) setEditingMarkerId(null) setHasUnsavedMarkerChanges(false) }} /> )} {selectedMarker && editingMarkerId !== selectedMarker.id && ( setSelectedMarkerId(null)} onEdit={() => setEditingMarkerId(selectedMarker.id)} /> )} {selectedMarker && editingMarkerId === selectedMarker.id && ( { if (!id) return await updateMarker(id, { name, notes: notes || null, color, }) setEditingMarkerId(null) setHasUnsavedMarkerChanges(false) }} onCancel={() => setEditingMarkerId(null)} /> )} ) }