mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
fbba8f96b7
The three planner view switches lived in sessionStorage only, so a new tab, another browser or another device opened every trip on the plain view again. They are now kept per trip in one user setting, planner_trip_views, validated against a contract in @trek/shared. The server keeps only switches that are on and only trips the user can still open, so a deleted trip or one the user left drops out on the next write. The tab copy in sessionStorage stays and answers until the settings have loaded. Refs #2639
106 lines
5.3 KiB
TypeScript
106 lines
5.3 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import { useDawarichTrail } from '../../components/Map/useDawarichTrail'
|
|
import { usePlannerTripView } from './usePlannerTripView'
|
|
import type { TripStoreState } from '../../store/tripStore'
|
|
import type { Place } from '../../types'
|
|
import type { PlannerBase } from './plannerTypes'
|
|
|
|
interface PlannerMapViewOptions extends Pick<PlannerBase, 'tripId' | 'trip'> {
|
|
places: Place[]
|
|
selectedDayId: TripStoreState['selectedDayId']
|
|
isMobile: boolean
|
|
}
|
|
|
|
/**
|
|
* How the planner's map is shown: the day route toggle and its travel profile, the
|
|
* whole-trip overview, the map lock, the recorded Dawarich trail, and the fit key
|
|
* with the one automatic fit each trip gets when it first has places to show.
|
|
*/
|
|
export function usePlannerMapView(options: PlannerMapViewOptions) {
|
|
const { tripId, trip, places, selectedDayId, isMobile } = options
|
|
// Manual route planning: off by default, toggled from the day-plan footer. Mode
|
|
// is per-session and selects which travel time the connectors show: either a
|
|
// built-in OSRM profile or a plugin route profile ('plugin:<id>/<profile>').
|
|
// Per-trip route visibility. `null` = the user has never said anything, which
|
|
// is what lets the mobile map switch it on by default; an explicit false has to
|
|
// survive every later map entry, and it used to be clobbered on each one (#2003).
|
|
const routeStorageKey = tripId ? `trek:day-route:${tripId}` : null
|
|
const [routeChoice, setRouteChoice] = useState<boolean | null>(() => {
|
|
if (typeof window === 'undefined' || !routeStorageKey) return null
|
|
const raw = window.localStorage.getItem(routeStorageKey)
|
|
return raw === 'true' ? true : raw === 'false' ? false : null
|
|
})
|
|
const routeShown = routeChoice === true
|
|
const setRouteShown = useCallback((v: boolean | ((prev: boolean) => boolean)) => {
|
|
setRouteChoice(prev => {
|
|
const next = typeof v === 'function' ? v(prev === true) : v
|
|
if (routeStorageKey && typeof window !== 'undefined') {
|
|
window.localStorage.setItem(routeStorageKey, String(next))
|
|
}
|
|
return next
|
|
})
|
|
}, [routeStorageKey])
|
|
// The mobile map opens with the day's route drawn. That is a default, not a choice,
|
|
// so it never overwrites an explicit off and is never written to storage itself.
|
|
const autoShowRoute = useCallback(() => {
|
|
setRouteChoice(prev => (prev === null ? true : prev))
|
|
}, [])
|
|
// What the planner maps actually draw. The persisted toggle can rehydrate as
|
|
// true while no day is selected yet (trip re-entry resets the selection, and
|
|
// a second click on the day header clears it). Without a day context the
|
|
// per-day transit filter is off, so the map would draw every automated
|
|
// transport in the trip (#2019).
|
|
const transitRoutesShown = routeShown && selectedDayId != null
|
|
const [routeProfile, setRouteProfile] = useState<string>('driving')
|
|
// Whole-trip route overview (#1736): every day's route at once, each in its own
|
|
// colour. Per trip like road trip mode, and remembered on the account the same
|
|
// way (#2639): it answers "what does the whole thing look like", which is a
|
|
// question you ask of one trip, not a preference.
|
|
const [overviewShown, toggleOverview] = usePlannerTripView(tripId, 'overview')
|
|
// A locked map stays where the traveller put it (#2010): picking a day or a place no
|
|
// longer zooms or pans it. Remembered per browser, like the other view toggles; the
|
|
// first frame of a trip still fits, or the map would open on nothing.
|
|
const [mapLocked, setMapLocked] = useState<boolean>(() => {
|
|
try { return localStorage.getItem('trek:map-locked') === '1' } catch { return false }
|
|
})
|
|
const mapLockedRef = useRef(mapLocked)
|
|
mapLockedRef.current = mapLocked
|
|
const isMobileRef = useRef(isMobile)
|
|
isMobileRef.current = isMobile
|
|
const toggleMapLocked = useCallback(() => {
|
|
setMapLocked(prev => {
|
|
const next = !prev
|
|
try { localStorage.setItem('trek:map-locked', next ? '1' : '0') } catch { /* private mode keeps it for the session */ }
|
|
return next
|
|
})
|
|
}, [])
|
|
// The recorded route from Dawarich (#2279), per trip and remembered on the
|
|
// account for the same reason as the overview above: it answers "what
|
|
// actually happened on this trip", a question about one trip rather than a
|
|
// preference.
|
|
const [dawarichTrailShown, toggleDawarichTrail] = usePlannerTripView(tripId, 'dawarich')
|
|
// Fetched here rather than in MapViewAuto so the desktop and the phone share
|
|
// one request, and so the pill that toggles it can show why there is no line.
|
|
const dawarichTrail = useDawarichTrail(tripId, dawarichTrailShown)
|
|
|
|
const [fitKey, setFitKey] = useState<number>(0)
|
|
const initialFitTripId = useRef<number | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!trip) return
|
|
if (initialFitTripId.current === trip.id) return
|
|
const hasGeoPlaces = places.some(p => p.lat != null && p.lng != null)
|
|
if (!hasGeoPlaces) return
|
|
initialFitTripId.current = trip.id
|
|
setFitKey(k => k + 1)
|
|
}, [trip, places])
|
|
|
|
return {
|
|
routeShown, setRouteShown, autoShowRoute, transitRoutesShown, routeProfile, setRouteProfile,
|
|
overviewShown, toggleOverview, mapLocked, toggleMapLocked, mapLockedRef, isMobileRef,
|
|
dawarichTrailShown, toggleDawarichTrail, dawarichTrail, fitKey, setFitKey,
|
|
}
|
|
}
|
|
|
|
export type PlannerMapView = ReturnType<typeof usePlannerMapView>
|