2025-09-03 23:17:35 +03:00
|
|
|
import { useEffect, useRef, useState } from "preact/hooks";
|
2025-09-04 21:37:08 +03:00
|
|
|
import L, { LatLng, Layer, LeafletMouseEvent } from "leaflet";
|
2025-09-03 23:17:35 +03:00
|
|
|
import "leaflet/dist/leaflet.css";
|
2025-09-03 23:35:29 +03:00
|
|
|
import { MAP_LAYERS } from "./map_layer";
|
2025-09-04 15:47:56 +03:00
|
|
|
import { ComponentChildren, createContext } from "preact";
|
2025-09-04 21:37:08 +03:00
|
|
|
import { map } from "jquery";
|
2025-09-04 15:47:56 +03:00
|
|
|
|
|
|
|
|
export const ParentMap = createContext<L.Map | null>(null);
|
2025-09-03 23:17:35 +03:00
|
|
|
|
|
|
|
|
interface MapProps {
|
|
|
|
|
coordinates: LatLng | [number, number];
|
|
|
|
|
zoom: number;
|
2025-09-03 23:35:29 +03:00
|
|
|
layerName: string;
|
2025-09-04 14:26:29 +03:00
|
|
|
viewportChanged: (coordinates: LatLng, zoom: number) => void;
|
2025-09-04 15:47:56 +03:00
|
|
|
children: ComponentChildren;
|
2025-09-04 21:46:05 +03:00
|
|
|
onClick?: (e: LeafletMouseEvent) => void;
|
|
|
|
|
onContextMenu?: (e: LeafletMouseEvent) => void;
|
2025-09-03 23:17:35 +03:00
|
|
|
}
|
|
|
|
|
|
2025-09-04 21:46:05 +03:00
|
|
|
export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu }: MapProps) {
|
2025-09-03 23:17:35 +03:00
|
|
|
const mapRef = useRef<L.Map>(null);
|
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!containerRef.current) return;
|
|
|
|
|
mapRef.current = L.map(containerRef.current, {
|
|
|
|
|
worldCopyJump: true
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Load the layer asynchronously.
|
|
|
|
|
const [ layer, setLayer ] = useState<Layer>();
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
async function load() {
|
2025-09-03 23:35:29 +03:00
|
|
|
const layerData = MAP_LAYERS[layerName];
|
2025-09-03 23:17:35 +03:00
|
|
|
|
|
|
|
|
if (layerData.type === "vector") {
|
|
|
|
|
const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
|
|
|
|
|
await import("@maplibre/maplibre-gl-leaflet");
|
|
|
|
|
|
|
|
|
|
setLayer(L.maplibreGL({
|
|
|
|
|
style: style as any
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
setLayer(L.tileLayer(layerData.url, {
|
|
|
|
|
attribution: layerData.attribution,
|
|
|
|
|
detectRetina: true
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
load();
|
|
|
|
|
}, [ layerName ]);
|
|
|
|
|
|
|
|
|
|
// Attach layer to the map.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const map = mapRef.current;
|
|
|
|
|
const layerToAdd = layer;
|
|
|
|
|
console.log("Add layer ", map, layerToAdd);
|
|
|
|
|
if (!map || !layerToAdd) return;
|
|
|
|
|
layerToAdd.addTo(map);
|
|
|
|
|
return () => layerToAdd.removeFrom(map);
|
|
|
|
|
}, [ mapRef, layer ]);
|
|
|
|
|
|
|
|
|
|
// React to coordinate changes.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!mapRef.current) return;
|
|
|
|
|
mapRef.current.setView(coordinates, zoom);
|
|
|
|
|
}, [ mapRef, coordinates, zoom ]);
|
|
|
|
|
|
2025-09-04 14:26:29 +03:00
|
|
|
// Viewport callback.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const map = mapRef.current;
|
|
|
|
|
if (!map) return;
|
|
|
|
|
|
|
|
|
|
const updateFn = () => viewportChanged(map.getBounds().getCenter(), map.getZoom());
|
|
|
|
|
map.on("moveend", updateFn);
|
|
|
|
|
map.on("zoomend", updateFn);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
map.off("moveend", updateFn);
|
|
|
|
|
map.off("zoomend", updateFn);
|
|
|
|
|
};
|
|
|
|
|
}, [ mapRef, viewportChanged ]);
|
|
|
|
|
|
2025-09-04 21:46:05 +03:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (onClick && mapRef.current) {
|
|
|
|
|
mapRef.current.on("click", onClick);
|
|
|
|
|
return () => mapRef.current?.off("click", onClick);
|
|
|
|
|
}
|
|
|
|
|
}, [ mapRef, onClick ]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (onContextMenu && mapRef.current) {
|
|
|
|
|
mapRef.current.on("contextmenu", onContextMenu);
|
|
|
|
|
return () => mapRef.current?.off("contextmenu", onContextMenu);
|
|
|
|
|
}
|
|
|
|
|
}, [ mapRef, onContextMenu ]);
|
2025-09-04 21:37:08 +03:00
|
|
|
|
2025-09-04 21:39:50 +03:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
className={`geo-map-container ${MAP_LAYERS[layerName].isDarkTheme ? "dark" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
<ParentMap.Provider value={mapRef.current}>
|
|
|
|
|
{children}
|
|
|
|
|
</ParentMap.Provider>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-09-03 23:17:35 +03:00
|
|
|
}
|