turash/bugulma/frontend/components/map/MapBoundsTracker.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

120 lines
3.4 KiB
TypeScript

import { LatLngBounds } from 'leaflet';
import { useCallback, useEffect, useRef } from 'react';
import { useMap, useMapEvents } from 'react-leaflet';
import { useMapActions } from '@/contexts/MapContexts.tsx';
/**
* Component to track map bounds and update context
* This enables viewport-based lazy loading of organizations
* Optimized with better debouncing and bounds comparison
*/
const MapBoundsTracker = () => {
const { setMapBounds } = useMapActions();
const boundsRef = useRef<LatLngBounds | null>(null);
const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isUpdatingRef = useRef(false);
/**
* Check if bounds have changed significantly
* Uses a more efficient comparison that considers both area and position
*/
const hasBoundsChanged = useCallback(
(newBounds: LatLngBounds, oldBounds: LatLngBounds | null): boolean => {
if (!oldBounds) return true;
// Compare center position
const newCenter = newBounds.getCenter();
const oldCenter = oldBounds.getCenter();
const centerDiff =
Math.abs(newCenter.lat - oldCenter.lat) + Math.abs(newCenter.lng - oldCenter.lng);
// Compare bounds size (north-south and east-west spans)
const newSize = {
lat: newBounds.getNorth() - newBounds.getSouth(),
lng: newBounds.getEast() - newBounds.getWest(),
};
const oldSize = {
lat: oldBounds.getNorth() - oldBounds.getSouth(),
lng: oldBounds.getEast() - oldBounds.getWest(),
};
const sizeDiff = Math.abs(newSize.lat - oldSize.lat) + Math.abs(newSize.lng - oldSize.lng);
// Update if center moved significantly (>0.01 degrees) or size changed (>5%)
return centerDiff > 0.01 || sizeDiff > Math.max(newSize.lat, newSize.lng) * 0.05;
},
[]
);
const map = useMap();
/**
* Update bounds with debouncing
*/
const updateBounds = useCallback(
(immediate: boolean = false) => {
if (updateTimeoutRef.current) {
clearTimeout(updateTimeoutRef.current);
}
const update = () => {
if (isUpdatingRef.current) return;
const bounds = map.getBounds();
if (hasBoundsChanged(bounds, boundsRef.current)) {
boundsRef.current = bounds;
isUpdatingRef.current = true;
// Use requestAnimationFrame for smooth updates
requestAnimationFrame(() => {
setMapBounds(bounds);
// Reset flag after a short delay
setTimeout(() => {
isUpdatingRef.current = false;
}, 100);
});
}
};
if (immediate) {
update();
} else {
updateTimeoutRef.current = setTimeout(update, 300);
}
},
[map, setMapBounds, hasBoundsChanged]
);
useMapEvents({
moveend: () => {
updateBounds(false);
},
zoomend: () => {
// Faster update on zoom as it's a discrete action
updateBounds(false);
},
move: () => {
// Throttle during active movement to reduce updates
updateBounds(false);
},
});
// Initialize bounds on mount
useEffect(() => {
// Wait for map to be fully initialized
const timer = setTimeout(() => {
updateBounds(true);
}, 100);
return () => {
clearTimeout(timer);
if (updateTimeoutRef.current) {
clearTimeout(updateTimeoutRef.current);
}
};
}, [updateBounds]);
return null;
};
export default MapBoundsTracker;