From d9a6d58d9321eaf94978f2cc315c12384b01e808 Mon Sep 17 00:00:00 2001 From: Kenneth Brewer Date: Wed, 29 Apr 2026 13:48:15 -0400 Subject: [PATCH] Added search box. Allows the user to fly to the location or fly to and add a marker. There are some bugs that need to be fixed. --- .../inertia/components/maps/MapComponent.tsx | 56 +++++++++- .../components/maps/MapMarkerFormPopup.tsx | 17 ++- admin/inertia/components/maps/MarkerPin.tsx | 68 ++++++++++-- admin/inertia/pages/maps.tsx | 103 ++++++++++++------ 4 files changed, 200 insertions(+), 44 deletions(-) diff --git a/admin/inertia/components/maps/MapComponent.tsx b/admin/inertia/components/maps/MapComponent.tsx index d8d6e24..201bc8e 100644 --- a/admin/inertia/components/maps/MapComponent.tsx +++ b/admin/inertia/components/maps/MapComponent.tsx @@ -23,9 +23,17 @@ import MapMarkerFormPopup from './MapMarkerFormPopup' type ScaleUnit = 'imperial' | 'metric' +type MapCommand = { + id: number + lat: number + lng: number + action: 'fly' | 'marker' +} + type MapComponentProps = { - isHoveringUI: boolean - showCoordinatesEnabled: boolean + mapCommand?: MapCommand | null + isHoveringUI?: boolean + showCoordinatesEnabled?: boolean } type MapLocationParams = { @@ -74,8 +82,9 @@ const getMapLocationParams = (): MapLocationParams | null => { } export default function MapComponent({ - isHoveringUI, - showCoordinatesEnabled, + mapCommand, + isHoveringUI = false, + showCoordinatesEnabled = true, }: MapComponentProps) { const mapRef = useRef(null) const animationFrameRef = useRef(null) @@ -138,6 +147,45 @@ export default function MapComponent({ } }, []) + useEffect(() => { + if (!mapCommand) return + + if (mapCommand.action === 'fly') { + const currentZoom = mapRef.current?.getZoom() ?? 12 + + mapRef.current?.flyTo({ + center: [mapCommand.lng, mapCommand.lat], + zoom: currentZoom, + duration: 1500, + }) + + return + } + + if (mapCommand.action === 'marker') { + if (!confirmDiscardMarkerChanges()) return + + const currentZoom = mapRef.current?.getZoom() ?? 12 + + mapRef.current?.flyTo({ + center: [mapCommand.lng, mapCommand.lat], + zoom: currentZoom, + duration: 750, + }) + + window.setTimeout(() => { + setPlacingMarker({ + lng: mapCommand.lng, + lat: mapCommand.lat, + }) + + setSelectedMarkerId(null) + setEditingMarkerId(null) + setHasUnsavedMarkerChanges(false) + }, 750) + } + }, [mapCommand, confirmDiscardMarkerChanges]) + const handleScaleUnitChange = useCallback((unit: ScaleUnit) => { setScaleUnit(unit) localStorage.setItem('nomad:map-scale-unit', unit) diff --git a/admin/inertia/components/maps/MapMarkerFormPopup.tsx b/admin/inertia/components/maps/MapMarkerFormPopup.tsx index 94ff34b..1a9c32a 100644 --- a/admin/inertia/components/maps/MapMarkerFormPopup.tsx +++ b/admin/inertia/components/maps/MapMarkerFormPopup.tsx @@ -50,6 +50,13 @@ export default function MapMarkerFormPopup({ resizeTextarea() }, [resizeTextarea]) + const nameInputRef = useRef(null) + + useLayoutEffect(() => { + nameInputRef.current?.focus() + nameInputRef.current?.select() + }, []) + const isDirty = name !== (initialMarker?.name ?? '') || notes !== (initialMarker?.notes ?? '') || @@ -85,8 +92,14 @@ export default function MapMarkerFormPopup({ onClose={onCancel} closeOnClick={false} > -
+
e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} + > -
+
))}
diff --git a/admin/inertia/components/maps/MarkerPin.tsx b/admin/inertia/components/maps/MarkerPin.tsx index 94c86af..c7ff026 100644 --- a/admin/inertia/components/maps/MarkerPin.tsx +++ b/admin/inertia/components/maps/MarkerPin.tsx @@ -1,17 +1,71 @@ -import { IconMapPinFilled } from '@tabler/icons-react' +import {IconCircleFilled} from '@tabler/icons-react' +import type { ComponentType, CSSProperties } from 'react' + +type MarkerIconProps = { + size?: number + color?: string + style?: CSSProperties + className?: string +} interface MarkerPinProps { color?: string active?: boolean + Icon?: ComponentType + iconColor?: string } -export default function MarkerPin({ color = '#a84a12', active = false }: MarkerPinProps) { +export default function MarkerPin({ + color = '#a84a12', + active = false, + Icon = IconCircleFilled, + iconColor = '#ffffff', + }: MarkerPinProps) { + const width = active ? 42 : 36 + const height = active ? 52 : 46 + const iconSize = active ? 18 : 16 + return ( -
- +
+ + +
+ +
) } diff --git a/admin/inertia/pages/maps.tsx b/admin/inertia/pages/maps.tsx index a1d8df1..fedbdbb 100644 --- a/admin/inertia/pages/maps.tsx +++ b/admin/inertia/pages/maps.tsx @@ -1,52 +1,102 @@ import { useState } from 'react' -import { Head, Link, router } from '@inertiajs/react' -import { IconArrowLeft } from '@tabler/icons-react' - import MapsLayout from '~/layouts/MapsLayout' +import { Head, Link, router } from '@inertiajs/react' import MapComponent from '~/components/maps/MapComponent' import StyledButton from '~/components/StyledButton' +import { IconArrowLeft, IconMapPin, IconPlaneTilt } from '@tabler/icons-react' +import { FileEntry } from '../../types/files' import Alert from '~/components/Alert' -import { FileEntry } from '../../types/files' +type MapCommand = { + id: number + lat: number + lng: number + action: 'fly' | 'marker' +} export default function Maps(props: { maps: { baseAssetsExist: boolean; regionFiles: FileEntry[] } }) { - const [isHoveringUI, setIsHoveringUI] = useState(false) - const [showMapCoordinates, setShowMapCoordinates] = useState(true) + const [coordinateSearch, setCoordinateSearch] = useState('') + const [mapCommand, setMapCommand] = useState(null) + + const parseCoordinates = () => { + const [latRaw, lngRaw] = coordinateSearch.split(',').map((value) => value.trim()) + const lat = Number(latRaw) + const lng = Number(lngRaw) + + if ( + !Number.isFinite(lat) || + !Number.isFinite(lng) || + lat < -90 || + lat > 90 || + lng < -180 || + lng > 180 + ) { + return null + } + + return { lat, lng } + } + + const handleCoordinateAction = (action: 'fly' | 'marker') => { + const coordinates = parseCoordinates() + if (!coordinates) return + + setMapCommand({ + id: Date.now(), + ...coordinates, + action, + }) + } const alertMessage = !props.maps.baseAssetsExist ? 'The base map assets have not been installed. Please download them first to enable map functionality.' : props.maps.regionFiles.length === 0 - ? 'No map regions have been downloaded yet. Please download some regions to enable map functionality.' - : null + ? 'No map regions have been downloaded yet. Please download some regions to enable map functionality.' + : null return ( -
- {/* Navbar */} -
setIsHoveringUI(true)} - onMouseLeave={() => setIsHoveringUI(false)} - > +

Back to Home

-
+
+ setCoordinateSearch(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') handleCoordinateAction('fly') + }} + className="w-52 rounded border border-border-default bg-surface-primary px-2 py-1 text-sm text-text-primary placeholder:text-text-muted focus:border-desert-green focus:outline-none" + /> + - + + + Manage Map Regions @@ -54,13 +104,8 @@ export default function Maps(props: {
- {/* Alert */} {alertMessage && ( -
setIsHoveringUI(true)} - onMouseLeave={() => setIsHoveringUI(false)} - > +
)} - {/* Map */}
- +