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

View File

@@ -1,4 +1,5 @@
import { MissionOptionalDefaults } from "@repo/db/zod";
import { useMapStore } from "_store/mapStore";
import { useMissionsStore } from "_store/missionsStore";
import { Icon, Marker as LMarker } from "leaflet";
import { House, Route } from "lucide-react";
@@ -10,6 +11,8 @@ export const MissionMarker = ({
}: {
mission: MissionOptionalDefaults;
}) => {
const { openMissionMarker, setOpenMissionMarker, setSearchPopup } =
useMapStore();
const [zoom, setZoom] = useState(0);
const map = useMap();
const markerRef = useRef<LMarker<any>>(null);
@@ -26,8 +29,24 @@ export const MissionMarker = ({
};
}, [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 (
<div>
<>
<Marker
ref={markerRef}
position={[mission.addressLat, mission.addressLng]}
@@ -38,8 +57,14 @@ export const MissionMarker = ({
popupAnchor: [0, 0],
})
}
>
<Popup>
></Marker>
{mission.id && openMissionMarker.includes(mission.id) && (
<Popup
position={[mission.addressLat, mission.addressLng]}
autoClose={false}
closeOnClick={false}
autoPan={false}
>
<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="p-2 bg-amber-700">
@@ -54,8 +79,8 @@ export const MissionMarker = ({
</div>
</div>
</Popup>
</Marker>
</div>
)}
</>
);
};

View File

@@ -5,7 +5,7 @@ import { Marker, Polygon, Polyline, Popup } from "react-leaflet";
import L from "leaflet";
export const SearchElements = () => {
const { searchElements, searchPopup, setSearchPopup, setPopup } =
const { searchElements, searchPopup, setSearchPopup, setContextMenu } =
useMapStore();
const poppupRef = useRef<LMarker>(null);
const intervalRef = useRef<NodeJS.Timeout>(null);
@@ -13,27 +13,6 @@ export const SearchElements = () => {
(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 = ({
element,
}: {
@@ -45,13 +24,16 @@ export const SearchElements = () => {
if (ref.current) {
ref.current.on("click", () => {
const center = ref.current.getBounds().getCenter();
setSearchPopup({
isOpen: true,
lat: center.lat,
lng: center.lng,
elementId: element.id,
});
setPopup(null);
if (searchPopup?.elementId !== element.id) {
setSearchPopup({
lat: center.lat,
lng: center.lng,
elementId: element.id,
});
} else {
setSearchPopup(null);
}
setContextMenu(null);
});
}
}, []);
@@ -71,13 +53,20 @@ export const SearchElements = () => {
}: {
element: (typeof searchElements)[1];
}) => {
if (!searchPopup) return null;
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">
<h3 className="text-lg font-bold">
{element.tags?.building === "yes"
? "Gebäude"
: element.tags?.building}
{!element.tags?.building && "unbekannt"}
</h3>
<p className="">
{element.tags?.["addr:street"]} {element.tags?.["addr:housenumber"]}
@@ -108,14 +97,14 @@ export const SearchElements = () => {
icon={new L.DivIcon()}
opacity={0}
>
{searchPopupElement && (
<SearchElementPopup element={searchPopupElement} />
)}
{!searchPopupElement && (
<div className="w-20 border border-rescuetrack"></div>
)}
</Marker>
)}
{searchPopupElement && (
<SearchElementPopup element={searchPopupElement} />
)}
</>
);
};

View File

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

View File

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