improved Popup handleing

This commit is contained in:
PxlLoewe
2025-04-09 09:33:13 -07:00
parent dc55b46385
commit 9e430eeeec
6 changed files with 93 additions and 57 deletions

View File

@@ -5,26 +5,39 @@ import { Popup, useMap } from "react-leaflet";
export const ContextMenu = () => { export const ContextMenu = () => {
const map = useMap(); const map = useMap();
const { popup, setSearchElements, setPopup, setSearchPopup } = useMapStore(); const {
contextMenu,
setContextMenu,
setSearchElements,
setSearchPopup,
setOpenMissionMarker,
openMissionMarker,
} = useMapStore();
useEffect(() => { useEffect(() => {
map.on("contextmenu", (e) => { map.on("contextmenu", (e) => {
setPopup({ isOpen: true, lat: e.latlng.lat, lng: e.latlng.lng }); // setOpenMissionMarker({ open: [], close: openMissionMarker });
setSearchPopup(undefined); setContextMenu({ lat: e.latlng.lat, lng: e.latlng.lng });
// setSearchPopup(null);
}); });
}, [popup]); }, [contextMenu]);
if (!popup) return null; if (!contextMenu) return null;
return ( return (
<Popup position={[popup.lat, popup.lng]}> <Popup
position={[contextMenu.lat, contextMenu.lng]}
autoClose={false}
closeOnClick={false}
autoPan={false}
>
{/* // TODO: maske: */} {/* // TODO: maske: */}
<div className="absolute transform -translate-y-1/2 z-1000 opacity-100 pointer-events-auto p-3"> <div className="absolute transform -translate-y-1/2 z-1000 opacity-100 pointer-events-auto p-3">
<button <button
className="btn btn-sm rounded-full bg-amber-600 hover:bg-amber-700 aspect-square" className="btn btn-sm rounded-full bg-amber-600 hover:bg-amber-700 aspect-square"
onClick={async () => { onClick={async () => {
const address = await fetch( const address = await fetch(
`https://nominatim.openstreetmap.org/reverse?lat=${popup.lat}&lon=${popup.lng}&format=json`, `https://nominatim.openstreetmap.org/reverse?lat=${contextMenu.lat}&lon=${contextMenu.lng}&format=json`,
); );
const data = (await address.json()) as { const data = (await address.json()) as {
address: { address: {
@@ -62,8 +75,8 @@ export const ContextMenu = () => {
`https://overpass-api.de/api/interpreter?data=${encodeURIComponent(` `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(`
[out:json]; [out:json];
( (
way["building"](around:100, ${popup.lat}, ${popup.lng}); way["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
relation["building"](around:100, ${popup.lat}, ${popup.lng}); relation["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
); );
out body; out body;
>; >;

View File

@@ -1,4 +1,5 @@
import { MissionOptionalDefaults } from "@repo/db/zod"; import { MissionOptionalDefaults } from "@repo/db/zod";
import { useMapStore } from "_store/mapStore";
import { useMissionsStore } from "_store/missionsStore"; import { useMissionsStore } from "_store/missionsStore";
import { Icon, Marker as LMarker } from "leaflet"; import { Icon, Marker as LMarker } from "leaflet";
import { House, Route } from "lucide-react"; import { House, Route } from "lucide-react";
@@ -10,6 +11,8 @@ export const MissionMarker = ({
}: { }: {
mission: MissionOptionalDefaults; mission: MissionOptionalDefaults;
}) => { }) => {
const { openMissionMarker, setOpenMissionMarker, setSearchPopup } =
useMapStore();
const [zoom, setZoom] = useState(0); const [zoom, setZoom] = useState(0);
const map = useMap(); const map = useMap();
const markerRef = useRef<LMarker<any>>(null); const markerRef = useRef<LMarker<any>>(null);
@@ -26,8 +29,24 @@ export const MissionMarker = ({
}; };
}, [map]); }, [map]);
useEffect(() => {
const handleClick = () => {
if (mission.id) {
setOpenMissionMarker({
open: [mission.id],
close: [],
});
// setSearchPopup(null);
}
};
markerRef.current?.on("click", handleClick);
return () => {
markerRef.current?.off("click", handleClick);
};
}, [markerRef.current, mission.id, setOpenMissionMarker]);
return ( return (
<div> <>
<Marker <Marker
ref={markerRef} ref={markerRef}
position={[mission.addressLat, mission.addressLng]} position={[mission.addressLat, mission.addressLng]}
@@ -38,8 +57,14 @@ export const MissionMarker = ({
popupAnchor: [0, 0], popupAnchor: [0, 0],
}) })
} }
></Marker>
{mission.id && openMissionMarker.includes(mission.id) && (
<Popup
position={[mission.addressLat, mission.addressLng]}
autoClose={false}
closeOnClick={false}
autoPan={false}
> >
<Popup>
<div className="absolute z-1000 opacity-100 pointer-events-auto w-[500px] text-white"> <div className="absolute z-1000 opacity-100 pointer-events-auto w-[500px] text-white">
<div className="bg-amber-600 pt-2 flex gap-1"> <div className="bg-amber-600 pt-2 flex gap-1">
<div className="p-2 bg-amber-700"> <div className="p-2 bg-amber-700">
@@ -54,8 +79,8 @@ export const MissionMarker = ({
</div> </div>
</div> </div>
</Popup> </Popup>
</Marker> )}
</div> </>
); );
}; };

View File

@@ -5,7 +5,7 @@ import { Marker, Polygon, Polyline, Popup } from "react-leaflet";
import L from "leaflet"; import L from "leaflet";
export const SearchElements = () => { export const SearchElements = () => {
const { searchElements, searchPopup, setSearchPopup, setPopup } = const { searchElements, searchPopup, setSearchPopup, setContextMenu } =
useMapStore(); useMapStore();
const poppupRef = useRef<LMarker>(null); const poppupRef = useRef<LMarker>(null);
const intervalRef = useRef<NodeJS.Timeout>(null); const intervalRef = useRef<NodeJS.Timeout>(null);
@@ -13,27 +13,6 @@ export const SearchElements = () => {
(element) => element.id === searchPopup?.elementId, (element) => element.id === searchPopup?.elementId,
); );
useEffect(() => {
intervalRef.current = setInterval(() => {
if (searchPopup?.isOpen) {
poppupRef.current?.openPopup();
} else {
poppupRef.current?.closePopup();
}
}, 100);
poppupRef.current?.on("popupclose", () => {
setSearchPopup(undefined);
});
return () => {
if (poppupRef.current) {
poppupRef.current.off("popupclose");
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [searchPopup, searchPopupElement]);
const SearchElement = ({ const SearchElement = ({
element, element,
}: { }: {
@@ -45,13 +24,16 @@ export const SearchElements = () => {
if (ref.current) { if (ref.current) {
ref.current.on("click", () => { ref.current.on("click", () => {
const center = ref.current.getBounds().getCenter(); const center = ref.current.getBounds().getCenter();
if (searchPopup?.elementId !== element.id) {
setSearchPopup({ setSearchPopup({
isOpen: true,
lat: center.lat, lat: center.lat,
lng: center.lng, lng: center.lng,
elementId: element.id, elementId: element.id,
}); });
setPopup(null); } else {
setSearchPopup(null);
}
setContextMenu(null);
}); });
} }
}, []); }, []);
@@ -71,13 +53,20 @@ export const SearchElements = () => {
}: { }: {
element: (typeof searchElements)[1]; element: (typeof searchElements)[1];
}) => { }) => {
if (!searchPopup) return null;
return ( return (
<Popup> <Popup
autoPan={false}
position={[searchPopup.lat, searchPopup.lng]}
autoClose={false}
closeOnClick={false}
>
<div className="bg-base-100/70 border border-rescuetrack w-[250px] text-white pointer-events-auto p-2"> <div className="bg-base-100/70 border border-rescuetrack w-[250px] text-white pointer-events-auto p-2">
<h3 className="text-lg font-bold"> <h3 className="text-lg font-bold">
{element.tags?.building === "yes" {element.tags?.building === "yes"
? "Gebäude" ? "Gebäude"
: element.tags?.building} : element.tags?.building}
{!element.tags?.building && "unbekannt"}
</h3> </h3>
<p className=""> <p className="">
{element.tags?.["addr:street"]} {element.tags?.["addr:housenumber"]} {element.tags?.["addr:street"]} {element.tags?.["addr:housenumber"]}
@@ -108,14 +97,14 @@ export const SearchElements = () => {
icon={new L.DivIcon()} icon={new L.DivIcon()}
opacity={0} opacity={0}
> >
{searchPopupElement && (
<SearchElementPopup element={searchPopupElement} />
)}
{!searchPopupElement && ( {!searchPopupElement && (
<div className="w-20 border border-rescuetrack"></div> <div className="w-20 border border-rescuetrack"></div>
)} )}
</Marker> </Marker>
)} )}
{searchPopupElement && (
<SearchElementPopup element={searchPopupElement} />
)}
</> </>
); );
}; };

View File

@@ -50,7 +50,7 @@ export const Chat = () => {
timeout.current = setInterval(() => { timeout.current = setInterval(() => {
fetchDispatcher(); fetchDispatcher();
}, 1000); }, 1000000);
fetchDispatcher(); fetchDispatcher();
return () => { return () => {

View File

@@ -2,8 +2,7 @@ import { popup } from "leaflet";
import { create } from "zustand"; import { create } from "zustand";
interface MapStore { interface MapStore {
popup: { contextMenu: {
isOpen: boolean;
lat: number; lat: number;
lng: number; lng: number;
} | null; } | null;
@@ -11,6 +10,8 @@ interface MapStore {
center: L.LatLngExpression; center: L.LatLngExpression;
zoom: number; zoom: number;
}; };
openMissionMarker: string[];
setOpenMissionMarker: (mission: { open: string[]; close: string[] }) => void;
searchElements: { searchElements: {
id: number; id: number;
nodes: { nodes: {
@@ -29,30 +30,38 @@ interface MapStore {
type: string; type: string;
}[]; }[];
setSearchElements: (elements: MapStore["searchElements"]) => void; setSearchElements: (elements: MapStore["searchElements"]) => void;
setPopup: (popup: MapStore["popup"]) => void; setContextMenu: (popup: MapStore["contextMenu"]) => void;
searchPopup?: { searchPopup: {
isOpen: boolean;
lat: number; lat: number;
lng: number; lng: number;
elementId: number; elementId: number;
}; } | null;
setSearchPopup: (popup: MapStore["searchPopup"]) => void; setSearchPopup: (popup: MapStore["searchPopup"]) => void;
} }
export const useMapStore = create<MapStore>((set, get) => ({ export const useMapStore = create<MapStore>((set, get) => ({
openMissionMarker: [],
setOpenMissionMarker: ({ open, close }) => {
set((state) => ({
openMissionMarker: [...state.openMissionMarker, ...open].filter(
(id) => !close.includes(id),
),
}));
},
map: { map: {
center: [51.5, 10.5], center: [51.5, 10.5],
zoom: 6, zoom: 6,
}, },
searchPopup: null,
searchElements: [], searchElements: [],
setSearchPopup: (popup) => setSearchPopup: (popup) =>
set((state) => ({ set((state) => ({
searchPopup: popup, searchPopup: popup,
})), })),
popup: null, contextMenu: null,
setPopup: (popup) => setContextMenu: (contextMenu) =>
set((state) => ({ set((state) => ({
popup: popup, contextMenu,
})), })),
setSearchElements: (elements) => setSearchElements: (elements) =>
set((state) => ({ set((state) => ({

Binary file not shown.