fix: Ngăn bản đồ tự động reset về trung tâm khi người dùng đã tương tác
- TourDetailPage: MapTourBounds chỉ fitBounds 1 lần cho mỗi lần dữ liệu lộ trình thay đổi thực sự (theo locKey). Nếu người dùng đã pan/zoom rồi thì không refit khi data reload. - TourNavigationPage: FitBounds chỉ chạy khi người dùng CHƯA tương tác (hasInteractedRef). Sau khi người dùng pan/zoom/drag, bản đồ giữ nguyên vị trí. - Cả hai bản đồ đều tôn trọng vị trí người dùng đã chọn, không reset về mặc định khi có thay đổi dữ liệu phụ.
This commit is contained in:
@@ -148,20 +148,29 @@ const combineSegmentRoutes = (segmentRoutes: OSRMRoute[][], selectedIndices: num
|
|||||||
// Component Helper để tự động điều chỉnh khung nhìn bản đồ bao phủ toàn bộ lộ trình
|
// Component Helper để tự động điều chỉnh khung nhìn bản đồ bao phủ toàn bộ lộ trình
|
||||||
const MapTourBounds = ({ locations }: { locations: any[] }) => {
|
const MapTourBounds = ({ locations }: { locations: any[] }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
// Tạo một key dựa trên giá trị tọa độ để tránh chạy lại khi chỉ thay đổi tham chiếu mảng
|
const shouldFitRef = useRef(true);
|
||||||
const locKey = useMemo(() => JSON.stringify(locations.map(l => [l.latitude, l.longitude])), [locations]);
|
const prevLocKeyRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
const locKey = useMemo(() => JSON.stringify(locations.map((l: any) => [l.latitude, l.longitude])), [locations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (locations.length > 0) {
|
if (locations.length === 0) return;
|
||||||
const bounds = L.latLngBounds(locations.map(l => [l.latitude, l.longitude]));
|
|
||||||
if (locations.length === 1) {
|
if (prevLocKeyRef.current !== null && prevLocKeyRef.current !== locKey) {
|
||||||
// Chỉ thực hiện nếu bản đồ chưa ở đúng vị trí (tránh trigger moveend liên tục)
|
shouldFitRef.current = true;
|
||||||
map.setView([locations[0].latitude, locations[0].longitude], 15, { animate: true });
|
|
||||||
} else {
|
|
||||||
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [locKey, map]);
|
prevLocKeyRef.current = locKey;
|
||||||
|
|
||||||
|
if (!shouldFitRef.current) return;
|
||||||
|
|
||||||
|
const bounds = L.latLngBounds(locations.map((l: any) => [l.latitude, l.longitude]));
|
||||||
|
if (locations.length === 1) {
|
||||||
|
map.setView([locations[0].latitude, locations[0].longitude], 15, { animate: true });
|
||||||
|
} else {
|
||||||
|
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15 });
|
||||||
|
}
|
||||||
|
shouldFitRef.current = false;
|
||||||
|
}, [locKey, map, locations.length]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ interface TourNavigationPageProps {
|
|||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FitBounds = ({ coords, canFitRef }: { coords: [number, number][], canFitRef: React.MutableRefObject<boolean> }) => {
|
const FitBounds = ({ coords, destination, hasInteractedRef }: { coords: [number, number][], destination: { lat: number; lng: number; name: string } | null, hasInteractedRef: React.MutableRefObject<boolean> }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (coords.length > 0 && canFitRef.current) {
|
if (coords.length > 0 && !hasInteractedRef.current && destination) {
|
||||||
canFitRef.current = false;
|
|
||||||
map.fitBounds(coords, { padding: [60, 60], maxZoom: 15 });
|
map.fitBounds(coords, { padding: [60, 60], maxZoom: 15 });
|
||||||
}
|
}
|
||||||
}, [map, coords, canFitRef]);
|
}, [map, coords, destination, hasInteractedRef]);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,14 +57,16 @@ const MapRotationHandler = ({ rotation }: { rotation: number }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CompassInteractionDetector = ({
|
const CompassInteractionDetector = ({
|
||||||
onUserInteraction
|
onUserInteraction,
|
||||||
|
hasInteractedRef
|
||||||
}: {
|
}: {
|
||||||
onUserInteraction: () => void;
|
onUserInteraction: () => void;
|
||||||
|
hasInteractedRef: React.MutableRefObject<boolean>;
|
||||||
}) => {
|
}) => {
|
||||||
useMapEvents({
|
useMapEvents({
|
||||||
movestart: onUserInteraction,
|
movestart: () => { hasInteractedRef.current = true; },
|
||||||
zoomstart: onUserInteraction,
|
zoomstart: () => { hasInteractedRef.current = true; },
|
||||||
dragstart: onUserInteraction,
|
dragstart: () => { hasInteractedRef.current = true; },
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -78,14 +79,16 @@ const MapRefSetter = ({ mapRef }: { mapRef: React.MutableRefObject<L.Map | null>
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MapInteractionWatcher = () => {
|
const MapInteractionWatcher = ({ hasInteractedRef }: { hasInteractedRef: React.MutableRefObject<boolean> }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onZoomEnd = () => {
|
const onZoomEnd = () => {
|
||||||
map._userZoomLevel = map.getZoom();
|
map._userZoomLevel = map.getZoom();
|
||||||
|
hasInteractedRef.current = true;
|
||||||
};
|
};
|
||||||
const onMoveEnd = () => {
|
const onMoveEnd = () => {
|
||||||
map._userCenter = map.getCenter();
|
map._userCenter = map.getCenter();
|
||||||
|
hasInteractedRef.current = true;
|
||||||
};
|
};
|
||||||
map.on('zoomend', onZoomEnd);
|
map.on('zoomend', onZoomEnd);
|
||||||
map.on('moveend', onMoveEnd);
|
map.on('moveend', onMoveEnd);
|
||||||
@@ -93,7 +96,7 @@ const MapInteractionWatcher = () => {
|
|||||||
map.off('zoomend', onZoomEnd);
|
map.off('zoomend', onZoomEnd);
|
||||||
map.off('moveend', onMoveEnd);
|
map.off('moveend', onMoveEnd);
|
||||||
};
|
};
|
||||||
}, [map]);
|
}, [map, hasInteractedRef]);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,7 +144,7 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
|
|||||||
const mapRef = useRef<L.Map | null>(null);
|
const mapRef = useRef<L.Map | null>(null);
|
||||||
const watchIdRef = useRef<number | null>(null);
|
const watchIdRef = useRef<number | null>(null);
|
||||||
const fetchLockRef = useRef(false);
|
const fetchLockRef = useRef(false);
|
||||||
const canFitRef = useRef(true);
|
const hasInteractedRef = useRef(false);
|
||||||
|
|
||||||
const originLat = routeData?.origin?.lat;
|
const originLat = routeData?.origin?.lat;
|
||||||
const originLng = routeData?.origin?.lng;
|
const originLng = routeData?.origin?.lng;
|
||||||
@@ -226,6 +229,7 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
|
|||||||
}, [routeData, originLat, originLng, destLat, destLng]);
|
}, [routeData, originLat, originLng, destLat, destLng]);
|
||||||
|
|
||||||
const handleUserInteraction = useCallback(() => {
|
const handleUserInteraction = useCallback(() => {
|
||||||
|
hasInteractedRef.current = true;
|
||||||
if (isCompassActive) {
|
if (isCompassActive) {
|
||||||
setIsCompassActive(false);
|
setIsCompassActive(false);
|
||||||
}
|
}
|
||||||
@@ -346,14 +350,14 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
|
|||||||
>
|
>
|
||||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="" />
|
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="" />
|
||||||
<MapRefSetter mapRef={mapRef} />
|
<MapRefSetter mapRef={mapRef} />
|
||||||
<MapInteractionWatcher />
|
<MapInteractionWatcher hasInteractedRef={hasInteractedRef} />
|
||||||
{/* <FitBounds coords={routeGeometry || []} canFitRef={canFitRef} /> */}
|
<FitBounds coords={routeGeometry || []} destination={routeData?.destination || null} hasInteractedRef={hasInteractedRef} />
|
||||||
{routeGeometry && (
|
{routeGeometry && (
|
||||||
<Polyline positions={routeGeometry} color="#2563eb" weight={6} opacity={0.9} smoothFactor={1} />
|
<Polyline positions={routeGeometry} color="#2563eb" weight={6} opacity={0.9} smoothFactor={1} />
|
||||||
)}
|
)}
|
||||||
<MapSizeHandler mapRef={mapRef} />
|
<MapSizeHandler mapRef={mapRef} />
|
||||||
<MapRotationHandler rotation={currentHeading} />
|
<MapRotationHandler rotation={currentHeading} />
|
||||||
<CompassInteractionDetector onUserInteraction={handleUserInteraction} />
|
<CompassInteractionDetector onUserInteraction={handleUserInteraction} hasInteractedRef={hasInteractedRef} />
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
|
|
||||||
{/* Locate User Button */}
|
{/* Locate User Button */}
|
||||||
|
|||||||
Reference in New Issue
Block a user