mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
feat(map): include the day's route in the map fit (#1128)
Selecting a day already fits the map to that day's destinations; this also folds the route polyline into the bounds. BoundsController fits the destinations immediately, then re-fits once — when the day's route finishes computing asynchronously — to destinations + the full route, so a route that bulges past its stops (a detour or ferry) stays in view. One-shot per day selection, so later route-profile toggles don't re-zoom.
This commit is contained in:
@@ -244,4 +244,22 @@ describe('MapView', () => {
|
||||
rerender(<MapView places={places} fitKey={2} />)
|
||||
expect(mapMock.fitBounds.mock.calls.length).toBeGreaterThan(afterFirst)
|
||||
})
|
||||
|
||||
it('FE-COMP-MAPVIEW-020: a day fit expands to include the route once it arrives (#1128)', async () => {
|
||||
const L = ((await import('leaflet')).default) as unknown as { latLngBounds: ReturnType<typeof vi.fn> }
|
||||
const dayPlaces = [
|
||||
buildMapPlace({ id: 1, lat: 48.0, lng: 2.0 }),
|
||||
buildMapPlace({ id: 2, lat: 48.1, lng: 2.1 }),
|
||||
]
|
||||
// Day selected, route not computed yet → first fit is the two destinations.
|
||||
const { rerender } = render(<MapView places={dayPlaces} dayPlaces={dayPlaces} route={[]} fitKey={5} />)
|
||||
const lastBounds = () => { const c = L.latLngBounds.mock.calls; return c[c.length - 1][0] }
|
||||
expect(lastBounds()).toHaveLength(2)
|
||||
|
||||
// The day's route arrives → one-shot re-fit including the 3 route points.
|
||||
L.latLngBounds.mockClear()
|
||||
rerender(<MapView places={dayPlaces} dayPlaces={dayPlaces} route={[[[47.9, 1.9], [48.05, 2.05], [48.2, 2.2]]]} fitKey={5} />)
|
||||
expect(L.latLngBounds).toHaveBeenCalled()
|
||||
expect(lastBounds()).toHaveLength(5) // 2 destinations + 3 route points
|
||||
})
|
||||
})
|
||||
|
||||
@@ -212,24 +212,27 @@ function MapController({ center, zoom }: MapControllerProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Fit bounds when places change (fitKey triggers re-fit)
|
||||
// Fit bounds when places change (fitKey triggers re-fit). On a day selection we
|
||||
// fit to that day's destinations immediately, then — once the day's route has
|
||||
// finished computing asynchronously — re-fit once more to include the full route
|
||||
// polyline, so a route that bulges past its stops stays in view (#1128).
|
||||
interface BoundsControllerProps {
|
||||
hasDayDetail?: boolean
|
||||
places: Place[]
|
||||
routeCoords: [number, number][]
|
||||
fitKey: number
|
||||
paddingOpts: L.FitBoundsOptions
|
||||
}
|
||||
|
||||
function BoundsController({ places, fitKey, paddingOpts, hasDayDetail }: BoundsControllerProps) {
|
||||
function BoundsController({ places, routeCoords, fitKey, paddingOpts, hasDayDetail }: BoundsControllerProps) {
|
||||
const map = useMap()
|
||||
const prevFitKey = useRef(-1)
|
||||
const awaitingRoute = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (fitKey === prevFitKey.current) return
|
||||
prevFitKey.current = fitKey
|
||||
if (places.length === 0) return
|
||||
const fitTo = useCallback((coords: [number, number][]) => {
|
||||
if (coords.length === 0) return
|
||||
try {
|
||||
const bounds = L.latLngBounds(places.map(p => [p.lat, p.lng]))
|
||||
const bounds = L.latLngBounds(coords)
|
||||
if (bounds.isValid()) {
|
||||
map.fitBounds(bounds, { ...paddingOpts, maxZoom: 16, animate: true })
|
||||
if (hasDayDetail) {
|
||||
@@ -237,8 +240,27 @@ function BoundsController({ places, fitKey, paddingOpts, hasDayDetail }: BoundsC
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}, [map, paddingOpts, hasDayDetail])
|
||||
|
||||
// New fitKey (initial trip fit or a day selection): fit to the destinations now
|
||||
// and arm a one-shot re-fit for when the route arrives.
|
||||
useEffect(() => {
|
||||
if (fitKey === prevFitKey.current) return
|
||||
prevFitKey.current = fitKey
|
||||
awaitingRoute.current = false
|
||||
if (places.length === 0) return
|
||||
fitTo(places.map(p => [p.lat, p.lng] as [number, number]))
|
||||
awaitingRoute.current = true
|
||||
}, [fitKey]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Once the just-selected day's route is ready, expand the fit to include it.
|
||||
// One-shot per day-fit, so later route-profile toggles don't re-zoom the map.
|
||||
useEffect(() => {
|
||||
if (!awaitingRoute.current || routeCoords.length === 0) return
|
||||
awaitingRoute.current = false
|
||||
fitTo([...places.map(p => [p.lat, p.lng] as [number, number]), ...routeCoords])
|
||||
}, [routeCoords]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -463,6 +485,9 @@ export const MapView = memo(function MapView({
|
||||
const thumbRafRef = useRef<number | null>(null)
|
||||
|
||||
const placeIds = useMemo(() => places.map(p => p.id).join(','), [places])
|
||||
// Flattened [lat,lng] points of the selected day's route, so the bounds fit can
|
||||
// include the full polyline once it has been computed.
|
||||
const routeCoords = useMemo<[number, number][]>(() => (route || []).flat() as [number, number][], [route])
|
||||
useEffect(() => {
|
||||
if (!places || places.length === 0 || !placesPhotosEnabled) return
|
||||
const cleanups: (() => void)[] = []
|
||||
@@ -597,7 +622,7 @@ export const MapView = memo(function MapView({
|
||||
/>
|
||||
|
||||
<MapController center={center} zoom={zoom} />
|
||||
<BoundsController places={dayPlaces.length > 0 ? dayPlaces : places} fitKey={fitKey} paddingOpts={paddingOpts} hasDayDetail={hasDayDetail} />
|
||||
<BoundsController places={dayPlaces.length > 0 ? dayPlaces : places} routeCoords={dayPlaces.length > 0 ? routeCoords : []} fitKey={fitKey} paddingOpts={paddingOpts} hasDayDetail={hasDayDetail} />
|
||||
<SelectionController places={places} selectedPlaceId={selectedPlaceId} dayPlaces={dayPlaces} paddingOpts={paddingOpts} />
|
||||
<MapClickHandler onClick={onMapClick} />
|
||||
<MapContextMenuHandler onContextMenu={onMapContextMenu} />
|
||||
|
||||
Reference in New Issue
Block a user