diff --git a/web/src/components/LeafletMap.tsx b/web/src/components/LeafletMap.tsx index 6cffccee6..083c387af 100644 --- a/web/src/components/LeafletMap.tsx +++ b/web/src/components/LeafletMap.tsx @@ -1,8 +1,10 @@ -import { DivIcon, LatLng } from "leaflet"; -import { MapPinIcon } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import L, { DivIcon, LatLng } from "leaflet"; +import { ExternalLinkIcon, MapPinIcon, MinusIcon, PlusIcon } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { createRoot } from "react-dom/client"; import ReactDOMServer from "react-dom/server"; import { MapContainer, Marker, TileLayer, useMap, useMapEvents } from "react-leaflet"; +import { cn } from "@/lib/utils"; const markerIcon = new DivIcon({ className: "relative border-none", @@ -54,6 +56,146 @@ const LocationMarker = (props: MarkerProps) => { return position === undefined ? null : ; }; +// Reusable glass-style button component +interface GlassButtonProps { + icon: ReactNode; + onClick: () => void; + ariaLabel: string; + title: string; +} + +const GlassButton = ({ icon, onClick, ariaLabel, title }: GlassButtonProps) => { + return ( + + ); +}; + +// Container for all map control buttons +interface ControlButtonsProps { + position: LatLng | undefined; + onZoomIn: () => void; + onZoomOut: () => void; + onOpenGoogleMaps: () => void; +} + +const ControlButtons = ({ position, onZoomIn, onZoomOut, onOpenGoogleMaps }: ControlButtonsProps) => { + return ( +
+ {position && ( + } + onClick={onOpenGoogleMaps} + ariaLabel="Open location in Google Maps" + title="Open in Google Maps" + /> + )} + } onClick={onZoomIn} ariaLabel="Zoom in" title="Zoom in" /> + } onClick={onZoomOut} ariaLabel="Zoom out" title="Zoom out" /> +
+ ); +}; + +// Custom Leaflet Control class +class MapControlsContainer extends L.Control { + private container: HTMLDivElement | null = null; + + onAdd() { + this.container = L.DomUtil.create("div", ""); + this.container.style.pointerEvents = "auto"; + + // Prevent map interactions when clicking controls + L.DomEvent.disableClickPropagation(this.container); + L.DomEvent.disableScrollPropagation(this.container); + + return this.container; + } + + onRemove() { + this.container = null; + } + + getContainer() { + return this.container; + } +} + +interface MapControlsProps { + position: LatLng | undefined; +} + +const MapControls = ({ position }: MapControlsProps) => { + const map = useMap(); + const controlRef = useRef(null); + const rootRef = useRef | null>(null); + + const handleOpenInGoogleMaps = () => { + if (!position) return; + const url = `https://www.google.com/maps?q=${position.lat},${position.lng}`; + window.open(url, "_blank", "noopener,noreferrer"); + }; + + const handleZoomIn = () => { + map.zoomIn(); + }; + + const handleZoomOut = () => { + map.zoomOut(); + }; + + useEffect(() => { + // Create custom Leaflet control + const control = new MapControlsContainer({ position: "topright" }); + controlRef.current = control; + control.addTo(map); + + // Get container and render React component into it + const container = control.getContainer(); + if (container) { + rootRef.current = createRoot(container); + rootRef.current.render( + , + ); + } + + return () => { + // Cleanup: unmount React component and remove control + if (rootRef.current) { + rootRef.current.unmount(); + rootRef.current = null; + } + if (controlRef.current) { + controlRef.current.remove(); + controlRef.current = null; + } + }; + }, [map]); + + // Update rendered content when position changes + useEffect(() => { + if (rootRef.current) { + rootRef.current.render( + , + ); + } + }, [position]); + + return null; +}; + const MapCleanup = () => { const map = useMap(); @@ -86,9 +228,10 @@ const DEFAULT_CENTER_LAT_LNG = new LatLng(48.8584, 2.2945); const LeafletMap = (props: MapProps) => { const position = props.latlng || DEFAULT_CENTER_LAT_LNG; return ( - + {}} /> + ); diff --git a/web/src/components/memo-metadata/LocationDisplay.tsx b/web/src/components/memo-metadata/LocationDisplay.tsx index 50bc7f98f..480d5f271 100644 --- a/web/src/components/memo-metadata/LocationDisplay.tsx +++ b/web/src/components/memo-metadata/LocationDisplay.tsx @@ -26,7 +26,7 @@ const LocationDisplay = ({ location, mode, onRemove, className }: LocationDispla
+ + [{location.latitude.toFixed(2)}°, {location.longitude.toFixed(2)}°] + {displayText} {onRemove && (