mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
7e563e1170
- the assistant's GL camera reads the user's own moves from the map's canvas container, which both engines have and which leaves marker clicks out, so the GL map test needs no extra mock and stays at its size - plugin-sdk host facts list the ai_assistant addon
56 lines
2.5 KiB
TypeScript
56 lines
2.5 KiB
TypeScript
import { useCallback, useEffect } from 'react';
|
|
import { MAX_ZOOM_GL, type ViewportPadding } from '../../utils/mapViewport';
|
|
import { ASSISTANT_CAMERA_MS, centreOf, coverPadding, useCameraRequest, useUserMoveClock } from './assistantCamera';
|
|
import type { AssistantCamera } from './mapViewContract';
|
|
|
|
/** The slice of the GL map the camera drives. */
|
|
export interface CameraMap {
|
|
/** Where the user drags, wheels and pinches the map; marker clicks are not moves. */
|
|
getCanvasContainer: () => HTMLElement;
|
|
getBounds: () => { contains: (lngLat: [number, number]) => boolean } | null;
|
|
fitBounds: (bounds: unknown, options: { padding: ViewportPadding; maxZoom: number; duration: number }) => unknown;
|
|
easeTo: (options: { center: [number, number]; duration: number }) => unknown;
|
|
}
|
|
|
|
/** The engine's bounds class, handed in so this module never imports mapbox-gl or maplibre-gl itself. */
|
|
export interface CameraEngine {
|
|
LngLatBounds: new () => { extend: (lngLat: [number, number]) => unknown };
|
|
}
|
|
|
|
interface AssistantCameraOptions {
|
|
mapRef: { readonly current: CameraMap | null };
|
|
mapReady: boolean;
|
|
gl: CameraEngine;
|
|
camera: AssistantCamera | null | undefined;
|
|
/** The planner's padding for its panels, in pixels per edge. */
|
|
padding: ViewportPadding;
|
|
}
|
|
|
|
/**
|
|
* Moves the GL map where the assistant asks, the twin of the Leaflet
|
|
* AssistantCameraController: `fit` frames the points, `pan` eases to their
|
|
* centre only when it is outside the view. It stops at the same scale as the
|
|
* Leaflet map (one GL zoom step is a raster step lower).
|
|
*/
|
|
export function useAssistantCameraGL({ mapRef, mapReady, gl, camera, padding }: AssistantCameraOptions): void {
|
|
const getContainer = useCallback(() => mapRef.current?.getCanvasContainer() ?? null, [mapRef]);
|
|
const userMovedAt = useUserMoveClock(getContainer, mapReady);
|
|
const request = useCameraRequest(camera, userMovedAt);
|
|
|
|
useEffect(() => {
|
|
const map = mapRef.current;
|
|
if (!map || !mapReady) return;
|
|
request((next) => {
|
|
const duration = next.animate ? ASSISTANT_CAMERA_MS : 0;
|
|
if (next.mode === 'pan') {
|
|
const [lat, lng] = centreOf(next.points);
|
|
if (!map.getBounds()?.contains([lng, lat])) map.easeTo({ center: [lng, lat], duration });
|
|
return;
|
|
}
|
|
const bounds = new gl.LngLatBounds();
|
|
next.points.forEach(([lat, lng]) => bounds.extend([lng, lat]));
|
|
map.fitBounds(bounds, { padding: coverPadding(padding, next.cover), maxZoom: MAX_ZOOM_GL, duration });
|
|
});
|
|
}, [gl, mapReady, mapRef, padding, request]);
|
|
}
|