feat: implement map fly-to functionality with custom events for improved navigation experience

This commit is contained in:
vergiLgood1 2025-05-05 03:20:16 +07:00
parent a2f51e6837
commit 6327d7b886
3 changed files with 95 additions and 6 deletions

View File

@ -256,19 +256,40 @@ export default function SearchTooltip({ onControlChange, activeControl, crimes =
const handleFlyToIncident = () => { const handleFlyToIncident = () => {
if (!selectedSuggestion || !selectedSuggestion.locations.latitude || !selectedSuggestion.locations.longitude) return; if (!selectedSuggestion || !selectedSuggestion.locations.latitude || !selectedSuggestion.locations.longitude) return;
const flyToEvent = new CustomEvent('incident_click', { // First, trigger a separate mapbox_fly_to event to handle the camera movement
const flyToMapEvent = new CustomEvent('mapbox_fly_to', {
detail: { detail: {
longitude: selectedSuggestion.locations.longitude, longitude: selectedSuggestion.locations.longitude,
latitude: selectedSuggestion.locations.latitude, latitude: selectedSuggestion.locations.latitude,
id: selectedSuggestion.id,
zoom: 15, zoom: 15,
description: selectedSuggestion.description, bearing: 0,
status: selectedSuggestion.status pitch: 45,
duration: 2000,
}, },
bubbles: true bubbles: true
}); });
document.dispatchEvent(flyToEvent); document.dispatchEvent(flyToMapEvent);
// Wait for the fly animation to complete before showing the popup
setTimeout(() => {
// Then trigger the incident_click event to show the popup
const incidentEvent = new CustomEvent('incident_click', {
detail: {
id: selectedSuggestion.id,
longitude: selectedSuggestion.locations.longitude,
latitude: selectedSuggestion.locations.latitude,
description: selectedSuggestion.description,
status: selectedSuggestion.status,
timestamp: selectedSuggestion.timestamp,
crime_categories: selectedSuggestion.crime_categories
},
bubbles: true
});
document.dispatchEvent(incidentEvent);
}, 2100); // Slightly longer than the fly animation duration
setShowInfoBox(false); setShowInfoBox(false);
setSelectedSuggestion(null); setSelectedSuggestion(null);
toggleSearch(); toggleSearch();

View File

@ -185,6 +185,46 @@ export default function CrimeMap() {
}; };
}, [filteredCrimes]); }, [filteredCrimes]);
// Set up event listener for fly-to map control
useEffect(() => {
const handleMapFlyTo = (e: CustomEvent) => {
if (!e.detail) {
console.error("Invalid fly-to data:", e.detail);
return;
}
const { longitude, latitude, zoom, bearing, pitch, duration } = e.detail;
// Find the map instance
const mapInstance = mapContainerRef.current?.querySelector('.mapboxgl-map');
if (!mapInstance) {
console.error("Map instance not found");
return;
}
// Create and dispatch a custom event that MapView component will listen for
const mapboxEvent = new CustomEvent('mapbox_fly', {
detail: {
center: [longitude, latitude],
zoom: zoom || 15,
bearing: bearing || 0,
pitch: pitch || 45,
duration: duration || 2000
},
bubbles: true
});
mapInstance.dispatchEvent(mapboxEvent);
};
// Add event listener
document.addEventListener('mapbox_fly_to', handleMapFlyTo as EventListener);
return () => {
document.removeEventListener('mapbox_fly_to', handleMapFlyTo as EventListener);
};
}, []);
// Handle year-month timeline change // Handle year-month timeline change
const handleTimelineChange = useCallback((year: number, month: number, progress: number) => { const handleTimelineChange = useCallback((year: number, month: number, progress: number) => {
setSelectedYear(year) setSelectedYear(year)

View File

@ -1,7 +1,7 @@
"use client" "use client"
import type React from "react" import type React from "react"
import { useState, useCallback, useRef } from "react" import { useState, useCallback, useRef, useEffect } from "react"
import { type ViewState, Map, type MapRef, NavigationControl, GeolocateControl } from "react-map-gl/mapbox" import { type ViewState, Map, type MapRef, NavigationControl, GeolocateControl } from "react-map-gl/mapbox"
import { FullscreenControl } from "react-map-gl/mapbox" import { FullscreenControl } from "react-map-gl/mapbox"
import { BASE_BEARING, BASE_LATITUDE, BASE_LONGITUDE, BASE_PITCH, BASE_ZOOM, MAPBOX_STYLES, type MapboxStyle } from "@/app/_utils/const/map" import { BASE_BEARING, BASE_LATITUDE, BASE_LONGITUDE, BASE_PITCH, BASE_ZOOM, MAPBOX_STYLES, type MapboxStyle } from "@/app/_utils/const/map"
@ -79,6 +79,34 @@ export default function MapView({
[onMoveEnd], [onMoveEnd],
) )
useEffect(() => {
if (!mapRef.current) return;
const mapElement = mapRef.current.getMap().getContainer();
// Handle fly to event
const handleMapFly = (e: CustomEvent) => {
if (!e.detail || !mapRef.current) return;
const { center, zoom, bearing, pitch, duration } = e.detail;
mapRef.current.flyTo({
center,
zoom,
bearing,
pitch,
duration,
essential: true
});
};
mapElement.addEventListener('mapbox_fly', handleMapFly as EventListener);
return () => {
mapElement.removeEventListener('mapbox_fly', handleMapFly as EventListener);
};
}, [mapRef.current]);
return ( return (
<div className={`relative ${className}`}> <div className={`relative ${className}`}>
<div className="flex h-full"> <div className="flex h-full">