Added a temporary marker when the user flies to a location. It will disappear when the add map marker button is clicked.

This commit is contained in:
Kenneth Brewer 2026-04-30 03:21:40 -04:00
parent d9a6d58d93
commit b4d23bf91b
4 changed files with 121 additions and 49 deletions

View File

@ -88,9 +88,11 @@ export default function MapComponent({
}: MapComponentProps) {
const mapRef = useRef<MapRef>(null)
const animationFrameRef = useRef<number | null>(null)
const handledMapCommandIdRef = useRef<number | null>(null)
const { markers, addMarker, updateMarker, deleteMarker } = useMapMarkers()
const [targetIndicator, setTargetIndicator] = useState<{ lng: number; lat: number } | null>(null)
const [isDraggingMap, setIsDraggingMap] = useState(false)
const [placingMarker, setPlacingMarker] = useState<{ lng: number; lat: number } | null>(null)
const [selectedMarkerId, setSelectedMarkerId] = useState<number | null>(null)
@ -149,10 +151,18 @@ export default function MapComponent({
useEffect(() => {
if (!mapCommand) return
if (handledMapCommandIdRef.current === mapCommand.id) return
handledMapCommandIdRef.current = mapCommand.id
if (mapCommand.action === 'fly') {
const currentZoom = mapRef.current?.getZoom() ?? 12
setTargetIndicator({
lng: mapCommand.lng,
lat: mapCommand.lat,
})
mapRef.current?.flyTo({
center: [mapCommand.lng, mapCommand.lat],
zoom: currentZoom,
@ -165,6 +175,8 @@ export default function MapComponent({
if (mapCommand.action === 'marker') {
if (!confirmDiscardMarkerChanges()) return
setTargetIndicator(null)
const currentZoom = mapRef.current?.getZoom() ?? 12
mapRef.current?.flyTo({
@ -203,6 +215,7 @@ export default function MapComponent({
setSelectedMarkerId(null)
setEditingMarkerId(null)
setHasUnsavedMarkerChanges(false)
setTargetIndicator(null)
},
[confirmDiscardMarkerChanges]
)
@ -239,6 +252,7 @@ export default function MapComponent({
)
const handleFlyTo = useCallback((longitude: number, latitude: number) => {
setTargetIndicator(null)
mapRef.current?.flyTo({ center: [longitude, latitude], zoom: 12, duration: 1500 })
}, [])
@ -321,6 +335,20 @@ export default function MapComponent({
/>
)}
{targetIndicator && (
<Marker longitude={targetIndicator.lng} latitude={targetIndicator.lat} anchor="center">
<div
className="pointer-events-none flex h-9 w-9 items-center justify-center rounded-full border-2 border-desert-orange bg-surface-primary/70 shadow-lg"
aria-hidden="true"
>
<div className="relative h-5 w-5">
<div className="absolute left-1/2 top-0 h-full w-[2px] -translate-x-1/2 bg-desert-orange" />
<div className="absolute left-0 top-1/2 h-[2px] w-full -translate-y-1/2 bg-desert-orange" />
</div>
</div>
</Marker>
)}
<ScaleUnitToggle
scaleUnit={scaleUnit}
onChange={handleScaleUnitChange}
@ -342,6 +370,7 @@ export default function MapComponent({
setPlacingMarker(null)
setEditingMarkerId(null)
setHasUnsavedMarkerChanges(false)
setTargetIndicator(null)
}}
>
<MarkerPin
@ -360,6 +389,7 @@ export default function MapComponent({
await addMarker(name, placingMarker.lng, placingMarker.lat, color, notes || undefined)
setPlacingMarker(null)
setHasUnsavedMarkerChanges(false)
setTargetIndicator(null)
}}
onCancel={() => {
if (!confirmDiscardMarkerChanges()) return
@ -367,6 +397,7 @@ export default function MapComponent({
setPlacingMarker(null)
setEditingMarkerId(null)
setHasUnsavedMarkerChanges(false)
setTargetIndicator(null)
}}
/>
)}
@ -376,6 +407,7 @@ export default function MapComponent({
marker={selectedMarker}
onClose={() => setSelectedMarkerId(null)}
onEdit={() => setEditingMarkerId(selectedMarker.id)}
onMouseEnter={hideCoordinates}
/>
)}
@ -385,6 +417,7 @@ export default function MapComponent({
latitude={selectedMarker.latitude}
initialMarker={selectedMarker}
onDirtyChange={setHasUnsavedMarkerChanges}
onMouseEnter={hideCoordinates}
onSave={async ({ id, name, notes, color }) => {
if (!id) return

View File

@ -21,6 +21,7 @@ type MapMarkerFormPopupProps = {
}) => Promise<void> | void
onCancel: () => void
onDirtyChange?: (dirty: boolean) => void
onMouseEnter?: () => void
}
export default function MapMarkerFormPopup({
@ -30,6 +31,7 @@ export default function MapMarkerFormPopup({
onSave,
onCancel,
onDirtyChange,
onMouseEnter,
}: MapMarkerFormPopupProps) {
const [name, setName] = useState(initialMarker?.name ?? '')
const [notes, setNotes] = useState(initialMarker?.notes ?? '')
@ -91,12 +93,14 @@ export default function MapMarkerFormPopup({
offset={[0, -36] as [number, number]}
onClose={onCancel}
closeOnClick={false}
closeButton={false}
>
<div
className="p-1"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onMouseEnter={onMouseEnter}
>
<input
ref={nameInputRef}
@ -153,7 +157,7 @@ export default function MapMarkerFormPopup({
type="button"
onClick={onCancel}
disabled={isSaving}
className="text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded transition-colors disabled:opacity-40"
className="text-xs bg-[#424420] text-white rounded px-2.5 py-1 hover:bg-[#525530] disabled:opacity-40 transition-colors"
>
Cancel
</button>

View File

@ -8,59 +8,76 @@ type ViewMapMarkerPopupProps = {
marker: MapMarker
onClose: () => void
onEdit: () => void
onMouseEnter?: () => void
}
export default function ViewMapMarkerPopup({
marker,
onClose,
onEdit,
onMouseEnter,
}: ViewMapMarkerPopupProps) {
return (
<div className="max-w-[260px]">
<Popup
longitude={marker.longitude}
latitude={marker.latitude}
anchor="bottom"
offset={[0, -36] as [number, number]}
onClose={onClose}
closeOnClick={false}
>
<div className="text-sm font-medium">{marker.name}</div>
<Popup
longitude={marker.longitude}
latitude={marker.latitude}
anchor="bottom"
offset={[0, -36] as [number, number]}
onClose={onClose}
closeOnClick={false}
closeButton={false}
>
<div
className="max-w-[260px]"
onMouseEnter={onMouseEnter}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<div className="text-sm font-medium break-words">{marker.name}</div>
{marker.notes && (
<div className="mt-1 max-w-[240px] break-all whitespace-pre-wrap text-xs text-gray-500">
{/* react-markdown is intentionally used without rehypeRaw.
Do not enable raw HTML rendering unless notes are sanitized first. */}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
a: ({href, children}) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="break-all text-blue-600 underline hover:text-blue-700"
>
{children}
</a>
),
}}
>
{marker.notes}
</ReactMarkdown>
</div>
)}
<div className="mt-2 flex justify-end">
<button
type="button"
onClick={onEdit}
className="rounded bg-[#424420] px-2.5 py-1 text-xs text-white hover:bg-[#525530]"
{marker.notes && (
<div className="mt-1 max-w-[240px] break-all whitespace-pre-wrap text-xs text-gray-500">
{/* react-markdown is intentionally used without rehypeRaw.
Do not enable raw HTML rendering unless notes are sanitized first. */}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="break-all text-blue-600 underline hover:text-blue-700"
>
Edit
</button>
</div>
</Popup>
{children}
</a>
),
}}
>
{marker.notes}
</ReactMarkdown>
</div>
)}
<div className="mt-2 flex justify-end gap-1.5">
<button
type="button"
onClick={onClose}
className="w-16 rounded bg-[#424420] px-2.5 py-1 text-xs text-white hover:bg-[#525530] border-none outline-none"
>
Close
</button>
<button
type="button"
onClick={onEdit}
className="w-16 rounded bg-[#424420] px-2.5 py-1 text-xs text-white hover:bg-[#525530] border-none outline-none"
>
Edit
</button>
</div>
</div>
)
}
</Popup>
)
}

View File

@ -6,6 +6,7 @@ import StyledButton from '~/components/StyledButton'
import { IconArrowLeft, IconMapPin, IconPlaneTilt } from '@tabler/icons-react'
import { FileEntry } from '../../types/files'
import Alert from '~/components/Alert'
import { IconCrosshair } from '@tabler/icons-react'
type MapCommand = {
id: number
@ -19,6 +20,7 @@ export default function Maps(props: {
}) {
const [coordinateSearch, setCoordinateSearch] = useState('')
const [mapCommand, setMapCommand] = useState<MapCommand | null>(null)
const [showCoordinatesEnabled, setShowCoordinatesEnabled] = useState(true)
const parseCoordinates = () => {
const [latRaw, lngRaw] = coordinateSearch.split(',').map((value) => value.trim())
@ -84,7 +86,7 @@ export default function Maps(props: {
className="rounded border border-border-default bg-surface-primary p-2 text-text-secondary hover:bg-surface-secondary"
title="Fly to coordinates"
>
<IconPlaneTilt size={18} />
<IconPlaneTilt size={18}/>
</button>
<button
@ -93,7 +95,20 @@ export default function Maps(props: {
className="rounded border border-border-default bg-surface-primary p-2 text-text-secondary hover:bg-surface-secondary"
title="Add marker at coordinates"
>
<IconMapPin size={18} />
<IconMapPin size={18}/>
</button>
<button
type="button"
onClick={() => setShowCoordinatesEnabled((prev) => !prev)}
className={`rounded border border-border-default p-2 transition-colors ${
showCoordinatesEnabled
? 'bg-desert-green text-white'
: 'bg-surface-primary text-text-secondary hover:bg-surface-secondary'
}`}
title={showCoordinatesEnabled ? 'Hide coordinates' : 'Show coordinates'}
>
<IconCrosshair size={18}/>
</button>
<Link href="/settings/maps" className="mr-4">
@ -122,7 +137,10 @@ export default function Maps(props: {
)}
<div className="absolute inset-0">
<MapComponent mapCommand={mapCommand} />
<MapComponent
mapCommand={mapCommand}
showCoordinatesEnabled={showCoordinatesEnabled}
/>
</div>
</div>
</MapsLayout>