improved Popup handleing
This commit is contained in:
@@ -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;
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -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>
|
||||||
<Popup>
|
{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="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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
setSearchPopup({
|
if (searchPopup?.elementId !== element.id) {
|
||||||
isOpen: true,
|
setSearchPopup({
|
||||||
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} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const Chat = () => {
|
|||||||
|
|
||||||
timeout.current = setInterval(() => {
|
timeout.current = setInterval(() => {
|
||||||
fetchDispatcher();
|
fetchDispatcher();
|
||||||
}, 1000);
|
}, 1000000);
|
||||||
fetchDispatcher();
|
fetchDispatcher();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -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.
Reference in New Issue
Block a user