perf(monitor): isolate follow viewport events
This commit is contained in:
@@ -14,7 +14,7 @@ const markerSetMap = vi.fn();
|
|||||||
const markerSetPosition = vi.fn();
|
const markerSetPosition = vi.fn();
|
||||||
const labelSetPosition = vi.fn();
|
const labelSetPosition = vi.fn();
|
||||||
const setZoomAndCenter = vi.fn();
|
const setZoomAndCenter = vi.fn();
|
||||||
const setCenter = vi.fn();
|
const setCenter = vi.fn(() => mapHandlers.get('moveend')?.({}));
|
||||||
const panTo = vi.fn();
|
const panTo = vi.fn();
|
||||||
const mapOff = vi.fn();
|
const mapOff = vi.fn();
|
||||||
const mapDestroy = vi.fn();
|
const mapDestroy = vi.fn();
|
||||||
@@ -521,6 +521,24 @@ test('renders one selected plate and smoothly follows it until the map is dragge
|
|||||||
expect((hiddenFloatingLabels[0].options.text as { content: string }).content).toBe('粤A12345');
|
expect((hiddenFloatingLabels[0].options.text as { content: string }).content).toBe('粤A12345');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('does not treat intermediate programmatic follow frames as user viewport changes', async () => {
|
||||||
|
useFastAnimationFrames();
|
||||||
|
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||||
|
window.AMapLoader = { load: vi.fn(async () => amapMock()) };
|
||||||
|
const onViewportChange = vi.fn();
|
||||||
|
const view = render(<FleetMap vehicles={[]} monitorMap={pointMap} selectedVin="LTEST000000000001" onSelect={() => undefined} onViewportChange={onViewportChange} />);
|
||||||
|
await waitFor(() => expect(onViewportChange).toHaveBeenCalled());
|
||||||
|
onViewportChange.mockReset();
|
||||||
|
|
||||||
|
view.rerender(<FleetMap vehicles={[]} monitorMap={{
|
||||||
|
...pointMap,
|
||||||
|
points: [{ ...pointMap.points[0], longitude: 113.27, latitude: 23.14 }, pointMap.points[1]]
|
||||||
|
}} selectedVin="LTEST000000000001" onSelect={() => undefined} onViewportChange={onViewportChange} />);
|
||||||
|
|
||||||
|
await waitFor(() => expect(setCenter).toHaveBeenLastCalledWith(wgs84ToGcj02(113.27, 23.14)), { timeout: 1_800 });
|
||||||
|
await waitFor(() => expect(onViewportChange).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
test('renders every nearby plate with staggered positions at maximum zoom', async () => {
|
test('renders every nearby plate with staggered positions at maximum zoom', async () => {
|
||||||
getZoom.mockReturnValue(20);
|
getZoom.mockReturnValue(20);
|
||||||
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
const selectionPositionRef = useRef('');
|
const selectionPositionRef = useRef('');
|
||||||
const centeredVinRef = useRef('');
|
const centeredVinRef = useRef('');
|
||||||
const followSelectedRef = useRef(true);
|
const followSelectedRef = useRef(true);
|
||||||
|
const programmaticFollowRef = useRef(false);
|
||||||
const selectedVinRef = useRef(selectedVin);
|
const selectedVinRef = useRef(selectedVin);
|
||||||
const [state, setState] = useState<'loading' | 'ready' | 'fallback' | 'error'>('loading');
|
const [state, setState] = useState<'loading' | 'ready' | 'fallback' | 'error'>('loading');
|
||||||
const [loadAttempt, setLoadAttempt] = useState(0);
|
const [loadAttempt, setLoadAttempt] = useState(0);
|
||||||
@@ -389,6 +390,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
const denseLabels = AMap.LabelsLayer ? new AMap.LabelsLayer({ zooms: [19, 20], zIndex: 110, collision: false, allowCollision: true }) : null;
|
const denseLabels = AMap.LabelsLayer ? new AMap.LabelsLayer({ zooms: [19, 20], zIndex: 110, collision: false, allowCollision: true }) : null;
|
||||||
labels?.setMap(map);
|
labels?.setMap(map);
|
||||||
notifyViewport = () => {
|
notifyViewport = () => {
|
||||||
|
if (programmaticFollowRef.current) return;
|
||||||
setMapZoom(map.getZoom?.() ?? 5);
|
setMapZoom(map.getZoom?.() ?? 5);
|
||||||
window.clearTimeout(viewportTimerRef.current);
|
window.clearTimeout(viewportTimerRef.current);
|
||||||
viewportTimerRef.current = window.setTimeout(() => {
|
viewportTimerRef.current = window.setTimeout(() => {
|
||||||
@@ -400,6 +402,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
map.on?.('zoomend', notifyViewport);
|
map.on?.('zoomend', notifyViewport);
|
||||||
stopFollowing = () => {
|
stopFollowing = () => {
|
||||||
if (!centeredVinRef.current) return;
|
if (!centeredVinRef.current) return;
|
||||||
|
programmaticFollowRef.current = false;
|
||||||
followSelectedRef.current = false;
|
followSelectedRef.current = false;
|
||||||
setFollowSelected(false);
|
setFollowSelected(false);
|
||||||
};
|
};
|
||||||
@@ -431,6 +434,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
pointAnimationFrameRef.current = undefined;
|
pointAnimationFrameRef.current = undefined;
|
||||||
pointAnimationTimerRef.current = undefined;
|
pointAnimationTimerRef.current = undefined;
|
||||||
movingPointIDsRef.current.clear();
|
movingPointIDsRef.current.clear();
|
||||||
|
programmaticFollowRef.current = false;
|
||||||
if (selectMassPoint) massInstance?.off?.('click', selectMassPoint);
|
if (selectMassPoint) massInstance?.off?.('click', selectMassPoint);
|
||||||
if (notifyViewport) {
|
if (notifyViewport) {
|
||||||
mapInstance?.off?.('moveend', notifyViewport);
|
mapInstance?.off?.('moveend', notifyViewport);
|
||||||
@@ -493,6 +497,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
window.clearTimeout(pointAnimationTimerRef.current);
|
window.clearTimeout(pointAnimationTimerRef.current);
|
||||||
pointAnimationFrameRef.current = undefined;
|
pointAnimationFrameRef.current = undefined;
|
||||||
pointAnimationTimerRef.current = undefined;
|
pointAnimationTimerRef.current = undefined;
|
||||||
|
programmaticFollowRef.current = false;
|
||||||
const previousData = renderedMassDataRef.current;
|
const previousData = renderedMassDataRef.current;
|
||||||
const previousDataByID = new Map(previousData.map((point) => [point.id, point]));
|
const previousDataByID = new Map(previousData.map((point) => [point.id, point]));
|
||||||
const vehiclePointIDs = new Set(monitorMode ? mapPoints.map((point) => point.vin) : fallbackPoints.map((point) => point.vin));
|
const vehiclePointIDs = new Set(monitorMode ? mapPoints.map((point) => point.vin) : fallbackPoints.map((point) => point.vin));
|
||||||
@@ -531,6 +536,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
|||||||
selectionRef.current?.setPosition?.(motion.point.lnglat);
|
selectionRef.current?.setPosition?.(motion.point.lnglat);
|
||||||
const motionComplete = elapsed >= motion.durationMs;
|
const motionComplete = elapsed >= motion.durationMs;
|
||||||
if (followSelectedRef.current && !selectedFollowComplete && (elapsed - lastFollowAt >= MAP_FOLLOW_FRAME_MS || motionComplete)) {
|
if (followSelectedRef.current && !selectedFollowComplete && (elapsed - lastFollowAt >= MAP_FOLLOW_FRAME_MS || motionComplete)) {
|
||||||
|
programmaticFollowRef.current = !motionComplete;
|
||||||
mapRef.current?.setCenter?.(motion.point.lnglat);
|
mapRef.current?.setCenter?.(motion.point.lnglat);
|
||||||
lastFollowAt = elapsed;
|
lastFollowAt = elapsed;
|
||||||
selectedFollowComplete = motionComplete;
|
selectedFollowComplete = motionComplete;
|
||||||
|
|||||||
Reference in New Issue
Block a user