add mapbox gl option, gps location, journey reorder + polish

- Mapbox GL provider alongside Leaflet for trip and journey maps (opt-in in
  settings with token, style presets incl. 3D on satellite, quality mode,
  experimental badge).
- GPS "blue dot" with heading cone on mobile; three-state FAB (off / show /
  follow), geodesic accuracy circle, desktop-hidden since browser IP geo is
  too coarse for navigation.
- Marker drift fix: outer wrap no longer carries inline position/transform,
  so mapbox's translate keeps the pin pinned at every zoom and pitch.
- Journey map popup (mapbox-gl): Apple-Maps-style tooltip on marker
  highlight/click showing entry title + location / date subline.
- Journey feed reorder: up/down controls to the left of each entry reorder
  sort_order within a day. Server endpoint, optimistic store update, rollback
  on failure.
- Journey entry editor: desktop modal now centers over the feed column only,
  backdrop still blurs the whole page (map included).
- Scroll-sync guard on journey: marker click locks the sync so smooth-scroll
  can't steer the highlight to a neighbouring entry mid-animation.
- Misc: map top-padding aligned with hero, live/synced badges replaced by a
  compact back-button in the hero, skeleton entries no longer pollute the
  journey map, journey detail no longer shows map on mobile path when
  combined view is active.
This commit is contained in:
Maurice
2026-04-19 01:41:02 +02:00
parent d07b508a77
commit 25bdf56d16
24 changed files with 2677 additions and 333 deletions
@@ -0,0 +1,55 @@
import { forwardRef, useImperativeHandle, useRef } from 'react'
import { useSettingsStore } from '../../store/settingsStore'
import JourneyMap, { type JourneyMapHandle } from './JourneyMap'
import JourneyMapGL, { type JourneyMapGLHandle } from './JourneyMapGL'
// Unified handle — both providers expose the same three methods.
export type JourneyMapAutoHandle = JourneyMapHandle
interface MapEntry {
id: string
lat: number
lng: number
title?: string | null
location_name?: string | null
mood?: string | null
entry_date: string
}
interface Props {
checkins: unknown[]
entries: MapEntry[]
trail?: { lat: number; lng: number }[]
height?: number
dark?: boolean
activeMarkerId?: string | null
onMarkerClick?: (id: string, type?: string) => void
fullScreen?: boolean
paddingBottom?: number
}
const JourneyMapAuto = forwardRef<JourneyMapAutoHandle, Props>(function JourneyMapAuto(props, ref) {
const provider = useSettingsStore(s => s.settings.map_provider)
const token = useSettingsStore(s => s.settings.mapbox_access_token)
const leafletRef = useRef<JourneyMapHandle>(null)
const glRef = useRef<JourneyMapGLHandle>(null)
// Fall back to Leaflet when the user selected Mapbox GL but hasn't
// supplied a token yet — otherwise the map would just show a stub.
const useGL = provider === 'mapbox-gl' && !!token
useImperativeHandle(ref, () => ({
highlightMarker: (id) => (useGL ? glRef.current : leafletRef.current)?.highlightMarker(id),
focusMarker: (id) => (useGL ? glRef.current : leafletRef.current)?.focusMarker(id),
invalidateSize: () => (useGL ? glRef.current : leafletRef.current)?.invalidateSize(),
}), [useGL])
if (useGL) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return <JourneyMapGL ref={glRef} {...(props as any)} />
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return <JourneyMap ref={leafletRef} {...(props as any)} />
})
export default JourneyMapAuto