Skip to content

Commit a36bf79

Browse files
committed
added the recenter-route feature control file
1 parent d9daf27 commit a36bf79

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

src/components/map/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { MapInfoPopup } from './parts/map-info-popup';
4646
import { MapContextMenu } from './parts/map-context-menu';
4747
import { RouteHoverPopup } from './parts/route-hover-popup';
4848
import { TilesInfoPopup } from './parts/tiles-info-popup';
49+
import { RecenterControl } from './parts/recenter-control';
4950
import {
5051
VALHALLA_EDGES_LAYER_ID,
5152
VALHALLA_NODES_LAYER_ID,
@@ -905,6 +906,8 @@ export const MapComponent = () => {
905906

906907
<BrandLogos />
907908

909+
<RecenterControl />
910+
908911
<Button
909912
className="absolute bottom-10 right-3 z-10"
910913
id="osm-button"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useMap } from 'react-map-gl/maplibre';
2+
import { Crosshair } from 'lucide-react';
3+
import { useCommonStore } from '@/stores/common-store';
4+
import { useDirectionsStore } from '@/stores/directions-store';
5+
import { CustomControl, ControlButton } from '../custom-control';
6+
7+
export function RecenterControl() {
8+
// Check if a route exists. successful turns false when user resets waypoints.
9+
const hasRoute = useDirectionsStore((state) => state.successful);
10+
11+
// useMap has to be called here, before the early return below.
12+
const { current: map } = useMap();
13+
14+
// This hides the button when no route is present.
15+
if (!hasRoute) return null;
16+
17+
const handleRecenter = () => {
18+
if (!map) return;
19+
// Read latest store values at click time instead of using stale closures.
20+
const state = useCommonStore.getState();
21+
const { coordinates } = state;
22+
const dpOpen = state.directionsPanelOpen;
23+
const spOpen = state.settingsPanelOpen;
24+
25+
if (!coordinates || coordinates.length === 0) return;
26+
27+
const firstCoord = coordinates[0];
28+
if (!firstCoord || !firstCoord[0] || !firstCoord[1]) return;
29+
30+
// Walk every route point and grow a bounding box around all of them.
31+
const bounds: [[number, number], [number, number]] = coordinates.reduce<
32+
[[number, number], [number, number]]
33+
>(
34+
(acc, coord) => {
35+
if (!coord || !coord[0] || !coord[1]) return acc;
36+
return [
37+
[Math.min(acc[0][0], coord[1]), Math.min(acc[0][1], coord[0])],
38+
[Math.max(acc[1][0], coord[1]), Math.max(acc[1][1], coord[0])],
39+
];
40+
},
41+
[
42+
[firstCoord[1], firstCoord[0]],
43+
[firstCoord[1], firstCoord[0]],
44+
]
45+
);
46+
47+
const paddingTopLeft = [screen.width < 550 ? 50 : dpOpen ? 420 : 50, 50];
48+
const paddingBottomRight = [
49+
screen.width < 550 ? 50 : spOpen ? 420 : 50,
50+
50,
51+
];
52+
53+
map.fitBounds(bounds, {
54+
padding: {
55+
top: paddingTopLeft[1] as number,
56+
bottom: paddingBottomRight[1] as number,
57+
left: paddingTopLeft[0] as number,
58+
right: paddingBottomRight[0] as number,
59+
},
60+
maxZoom: coordinates.length === 1 ? 11 : 18,
61+
duration: 800,
62+
});
63+
};
64+
65+
// Render inside the MapLibre top-right control group so it sits with the zoom buttons.
66+
return (
67+
<CustomControl position="topRight">
68+
<ControlButton
69+
title="Recenter to route"
70+
icon={<Crosshair size={15} />}
71+
onClick={handleRecenter}
72+
data-testid="recenter-button"
73+
/>
74+
</CustomControl>
75+
);
76+
}

0 commit comments

Comments
 (0)