fix(journey): frame the first map view around the entries and the plan, and never hand a removed map the basemap it waited for

This commit is contained in:
Maurice
2026-10-21 20:33:12 +02:00
parent 5dc945145f
commit 1797e1c70d
2 changed files with 35 additions and 35 deletions
+20 -19
View File
@@ -224,8 +224,6 @@ function JourneyMap(
const tileLayerRef = useRef<L.TileLayer | null>(null)
// GL layer or the raster stand-in a browser without WebGL gets instead (#2288).
const glLayerRef = useRef<BasemapLayer | null>(null)
// The vector basemap loads async; a map torn down before it lands must not get one.
const cancelledRef = useRef(false)
const containerRef = useRef<HTMLDivElement>(null)
const mapRef = useRef<L.Map | null>(null)
const markersRef = useRef<Map<string, L.Marker>>(new Map())
@@ -239,9 +237,10 @@ function JourneyMap(
const markLayerRef = useRef<L.LayerGroup | null>(null)
const onMarksPickRef = useRef(onMarksPick)
const marksRef = useRef(marks)
// A journey with nothing located of its own opens on the world until its
// plan or stays arrive; those get the first view unless the reader moved it.
const awaitingMarksRef = useRef(false)
// The plan and the stays load after the entries, so the first view is framed
// again once they arrive, around both, unless the reader has moved it by then.
const entryCoordsRef = useRef<L.LatLngTuple[]>([])
const marksFramedRef = useRef(false)
const readerMovedRef = useRef(false)
// What each switchable layer drew, and which of them are on the map right now.
const drawnRef = useRef<DrawnLayers>(NOTHING_DRAWN())
@@ -307,8 +306,11 @@ function JourneyMap(
})
L.control.attribution({ position: 'bottomright', prefix: false }).addTo(map)
mapRef.current = map
cancelledRef.current = false
awaitingMarksRef.current = false
// The vector basemap loads async; a map torn down before it lands must not get
// one. Per build, not a shared ref: the next build would reset a shared flag and
// hand the old map, already removed, the layer it was still waiting for.
let cancelled = false
marksFramedRef.current = false
readerMovedRef.current = false
const readerMoved = () => { readerMovedRef.current = true }
map.on('dragstart', readerMoved)
@@ -317,7 +319,7 @@ function JourneyMap(
// The basemap is a vector style unless the user brought their own raster
// template, so which layer draws it is decided per template rather than once.
if (isVectorStyle(tileUrlRef.current)) {
void attachVectorBasemap(map, tileUrlRef.current, glLayerRef, () => cancelledRef.current)
void attachVectorBasemap(map, tileUrlRef.current, glLayerRef, () => cancelled)
} else {
const tiles = L.tileLayer(tileUrlRef.current, {
maxZoom: 18,
@@ -455,13 +457,12 @@ function JourneyMap(
map.invalidateSize()
// Tracks only get a say when the journey has nothing located of its own —
// a world view would be worse than framing the one thing on the map.
const fitCoords = allCoords.length > 0 ? allCoords : trackCoords.length > 0 ? trackCoords : markCoords(marksRef.current)
if (fitCoords.length > 0) {
map.fitBounds(L.latLngBounds(fitCoords), fitOptions(paddingBottom))
} else {
map.setView([30, 0], 2)
awaitingMarksRef.current = true
}
entryCoordsRef.current = allCoords
const pointCoords = [...allCoords, ...markCoords(marksRef.current)]
const fitCoords = pointCoords.length > 0 ? pointCoords : trackCoords
marksFramedRef.current = (marksRef.current?.length ?? 0) > 0
if (fitCoords.length > 0) map.fitBounds(L.latLngBounds(fitCoords), fitOptions(paddingBottom))
else map.setView([30, 0], 2)
} catch {}
})
@@ -471,7 +472,7 @@ function JourneyMap(
const container = containerRef.current
return () => {
cancelledRef.current = true
cancelled = true
container.removeEventListener('wheel', readerMoved)
map.remove()
mapRef.current = null
@@ -573,9 +574,9 @@ function JourneyMap(
}
const coords = markCoords(marks)
if (awaitingMarksRef.current && coords.length > 0) {
awaitingMarksRef.current = false
if (!readerMovedRef.current) map.fitBounds(L.latLngBounds(coords), fitOptions(paddingBottom))
if (!marksFramedRef.current && coords.length > 0) {
marksFramedRef.current = true
if (!readerMovedRef.current) map.fitBounds(L.latLngBounds([...entryCoordsRef.current, ...coords]), fitOptions(paddingBottom))
}
draw()
+15 -16
View File
@@ -148,8 +148,9 @@ const EMPTY_TRACKS: JourneyTrack[] = []
/** Fallback when a track carries no colour of its own, matching the planner's default. */
const TRACK_FALLBACK_COLOR = '#4f46e5'
function markBounds(gl: Props['gl'], marks: JourneyMapMark[]) {
function markBounds(gl: Props['gl'], marks: JourneyMapMark[], points: [number, number][] = []) {
const bounds = new gl.LngLatBounds()
points.forEach(point => bounds.extend(point))
marks.forEach(mark => bounds.extend([mark.lng, mark.lat]))
return bounds
}
@@ -200,9 +201,10 @@ function JourneyMapGL(
const markMarkersRef = useRef<{ remove: () => void }[]>([])
const onMarksPickRef = useRef(onMarksPick)
const marksRef = useRef(marks)
// As on the Leaflet map: nothing located of its own, so the plan or the
// stays get the first view once they arrive, unless the reader moved it.
const awaitingMarksRef = useRef(false)
// As on the Leaflet map: the plan and the stays arrive after the entries, so
// the first view is framed again around both, unless the reader moved it.
const entryPointsRef = useRef<[number, number][]>([])
const marksFramedRef = useRef(false)
const readerMovedRef = useRef(false)
const visibilityRef = useRef(layerVisibility)
const shownRef = useRef<JourneyMapLayerVisibility>(layerVisibility)
@@ -364,7 +366,8 @@ function JourneyMapGL(
const map = new gl.Map(mapOptions as any)
mapRef.current = map
awaitingMarksRef.current = false
marksFramedRef.current = false
entryPointsRef.current = [...items.map(i => [i.lng, i.lat] as [number, number]), ...stableTrail.map(p => [p.lng, p.lat] as [number, number])]
readerMovedRef.current = false
map.on('movestart', (ev: { originalEvent?: unknown }) => {
if (ev.originalEvent) readerMovedRef.current = true
@@ -496,16 +499,12 @@ function JourneyMapGL(
})
// fit bounds to all points
if (hasPoints) {
const known = marksRef.current ?? []
marksFramedRef.current = known.length > 0
if (hasPoints || known.length > 0) {
try {
map.fitBounds(bounds, fitOptions(paddingBottom, enableMapbox3d && fullScreen))
map.fitBounds(markBounds(gl, known, entryPointsRef.current), fitOptions(paddingBottom, enableMapbox3d && fullScreen))
} catch { /* empty bounds */ }
} else if (marksRef.current?.length) {
try {
map.fitBounds(markBounds(gl, marksRef.current), fitOptions(paddingBottom, enableMapbox3d && fullScreen))
} catch { /* empty bounds */ }
} else {
awaitingMarksRef.current = true
}
})
@@ -597,11 +596,11 @@ function JourneyMapGL(
const draw = () => {
clear()
if (!marks?.length) return
if (awaitingMarksRef.current) {
awaitingMarksRef.current = false
if (!marksFramedRef.current) {
marksFramedRef.current = true
if (!readerMovedRef.current) {
try {
map.fitBounds(markBounds(gl, marks), fitOptions(paddingBottom, enableMapbox3d && fullScreen))
map.fitBounds(markBounds(gl, marks, entryPointsRef.current), fitOptions(paddingBottom, enableMapbox3d && fullScreen))
} catch { /* empty bounds */ }
}
}