import { useEffect, useRef, useCallback } from "react"; import { useMap } from "react-leaflet"; import L from "leaflet"; import * as Y from "yjs"; import "@geoman-io/leaflet-geoman-free"; import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css"; interface NoGoAreaLayerProps { noGoAreas: Y.Array>; doc: Y.Doc; enabled: boolean; onToggle: () => void; } const NO_GO_STYLE: L.PathOptions = { color: "#ef4444", fillColor: "#ef4444", fillOpacity: 0.2, weight: 2, }; function yMapToLatLngs(yMap: Y.Map): L.LatLng[] { const points = yMap.get("points") as Array<{ lat: number; lon: number }> | undefined; if (!points) return []; return points.map((p) => L.latLng(p.lat, p.lon)); } export function NoGoAreaLayer({ noGoAreas, doc, enabled, onToggle }: NoGoAreaLayerProps) { const map = useMap(); const layerGroupRef = useRef(L.layerGroup()); const polygonMapRef = useRef>(new Map()); const suppressObserverRef = useRef(false); // Sync Yjs → Leaflet layers const syncLayers = useCallback(() => { const lg = layerGroupRef.current; // Clear all existing polygons lg.clearLayers(); polygonMapRef.current.clear(); for (let i = 0; i < noGoAreas.length; i++) { const yMap = noGoAreas.get(i); const latLngs = yMapToLatLngs(yMap); if (latLngs.length < 3) continue; const polygon = L.polygon(latLngs, NO_GO_STYLE); polygon.on("contextmenu", (e) => { L.DomEvent.preventDefault(e.originalEvent); suppressObserverRef.current = true; doc.transact(() => noGoAreas.delete(i, 1), "local"); suppressObserverRef.current = false; syncLayers(); }); lg.addLayer(polygon); polygonMapRef.current.set(i, polygon); } }, [noGoAreas]); // Add/remove layer group from map useEffect(() => { const lg = layerGroupRef.current; lg.addTo(map); return () => { lg.removeFrom(map); }; }, [map]); // Observe Yjs changes useEffect(() => { const observer = () => { if (!suppressObserverRef.current) { syncLayers(); } }; noGoAreas.observeDeep(observer); syncLayers(); return () => noGoAreas.unobserveDeep(observer); }, [noGoAreas, syncLayers]); // Geoman drawing controls useEffect(() => { if (enabled) { map.pm.enableDraw("Polygon", { pathOptions: NO_GO_STYLE, snappable: false, }); } else { map.pm.disableDraw(); } const handleCreate = (e: { layer: L.Layer }) => { const layer = e.layer as L.Polygon; const latLngs = (layer.getLatLngs()[0] as L.LatLng[]); const points = latLngs.map((ll) => ({ lat: ll.lat, lon: ll.lng })); // Remove the geoman-created layer (we'll render from Yjs) map.removeLayer(layer); // Add to Yjs const yMap = new Y.Map(); yMap.set("points", points); doc.transact(() => { noGoAreas.push([yMap]); }, "local"); // Exit draw mode onToggle(); }; map.on("pm:create", handleCreate); return () => { map.pm.disableDraw(); map.off("pm:create", handleCreate); }; }, [map, enabled, noGoAreas, doc, onToggle]); return null; }