From 9ddbbce1397509058b256ac89756e7de92ddb9ab Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Fri, 11 Apr 2025 21:54:06 -0700 Subject: [PATCH] added Collision handling for open popups --- .../_components/map/AircraftMarker.tsx | 449 +++++++++++------- .../_components/map/ContextMenu.tsx | 25 +- .../_components/map/MissionMarkers.tsx | 12 +- grafana/grafana.db | Bin 1122304 -> 1122304 bytes 4 files changed, 303 insertions(+), 183 deletions(-) diff --git a/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx b/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx index 7107c7da..5512161e 100644 --- a/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx +++ b/apps/dispatch/app/(dispatch)/_components/map/AircraftMarker.tsx @@ -2,8 +2,9 @@ import { Aircraft, useAircraftsStore } from "_store/aircraftsStore"; import { Marker, Popup, useMap } from "react-leaflet"; import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet"; import { useMapStore } from "_store/mapStore"; -import { Fragment, useEffect, useRef, useState } from "react"; +import { Fragment, useCallback, useEffect, useRef, useState } from "react"; import { cn } from "helpers/cn"; +import { House, Route } from "lucide-react"; export const FMS_STATUS_COLORS: { [key: string]: string } = { "0": "rgb(126,0,5)", @@ -31,191 +32,293 @@ export const FMS_STATUS_TEXT_COLORS: { [key: string]: string } = { "9": "rgb(42,217,42)", }; -export const AircraftMarker = (props: any) => { - const { openAircraftMarker, setOpenAircraftMarker } = useMapStore( - (state) => state, - ); +const AircraftLabel = ({ aircraft }: { aircraft: Aircraft }) => { const aircrafts = useAircraftsStore((state) => state.aircrafts); const map = useMap(); + const markerRef = useRef(null); + const popupRef = useRef(null); + + const { openAircraftMarker, setOpenAircraftMarker } = useMapStore( + (store) => store, + ); + + useEffect(() => { + const handleClick = () => { + const open = openAircraftMarker.includes(aircraft.id); + if (open) { + setOpenAircraftMarker({ + open: [], + close: [aircraft.id], + }); + } else { + setOpenAircraftMarker({ + open: [aircraft.id], + close: [], + }); + } + }; + markerRef.current?.on("click", handleClick); + return () => { + markerRef.current?.off("click", handleClick); + }; + }, [markerRef.current, aircraft.id, openAircraftMarker]); + + const [anchor, setAnchor] = useState< + "topleft" | "topright" | "bottomleft" | "bottomright" + >("topleft"); + + const handleLabelConflict = useCallback(() => { + const otherMarkers = document.querySelectorAll(".aircraft-collision"); + // get markers and check if they are overlapping + const ownMarker = openAircraftMarker.includes(aircraft.id) + ? document.querySelector(`#aircraft-popup-${aircraft.id}`) + : document.querySelector(`#aircraft-marker-${aircraft.id}`); + + if (!otherMarkers || !ownMarker) return; + + const marksersInCluster = Array.from(otherMarkers).filter((marker) => { + if ( + openAircraftMarker.includes(aircraft.id) && + marker.id === `aircraft-marker-${aircraft.id}` + ) + return false; + + const rect1 = (marker as HTMLElement).getBoundingClientRect(); + const rect2 = (ownMarker as HTMLElement).getBoundingClientRect(); + + return !( + rect1.right < rect2.left || + rect1.left > rect2.right || + rect1.bottom < rect2.top || + rect1.top > rect2.bottom + ); + }); + + // get the center of all overlapping markers + const markersPosition = marksersInCluster.map((marker) => { + const rect = (marker as HTMLElement).getBoundingClientRect(); + return { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + }); + const ownMarkerBounds = (ownMarker as HTMLElement).getBoundingClientRect(); + const ownMarkerPosition = { + x: ownMarkerBounds.left + ownMarkerBounds.width / 2, + y: ownMarkerBounds.top + ownMarkerBounds.height / 2, + }; + + const centerOfOverlappingMarkers = markersPosition.reduce( + (acc, pos) => { + if (acc.x === 0 && acc.y === 0) return pos; + return { + x: (acc.x + pos.x) / 2, + y: (acc.y + pos.y) / 2, + }; + }, + { + x: 0, + y: 0, + }, + ); + if (marksersInCluster.length > 1) { + if (centerOfOverlappingMarkers.y < ownMarkerPosition.y) { + if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { + setAnchor("topright"); + } else { + setAnchor("topleft"); + } + } else { + if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { + setAnchor("bottomright"); + } else { + setAnchor("bottomleft"); + } + } + } else { + // Default + setAnchor("topleft"); + } + }, [openAircraftMarker, aircraft.id]); + + useEffect(() => { + handleLabelConflict(); + }, [aircrafts, openAircraftMarker]); + + useEffect(() => {}); + + useEffect(() => { + handleLabelConflict(); + + map.on("zoom", handleLabelConflict); + return () => { + map.off("zoom", handleLabelConflict); + }; + }, [map, openAircraftMarker]); const getMarkerHTML = ( aircraft: Aircraft, anchor: "topleft" | "topright" | "bottomleft" | "bottomright", ) => { return `
- -
- - ${aircraft.fmsStatus} - - - ${aircraft.bosName} - -
-
`; + style=" + background-color: ${FMS_STATUS_COLORS[aircraft.fmsStatus]}; + ${openAircraftMarker.includes(aircraft.id) ? "opacity: 0; pointer-events: none;" : ""} + "> + +
+ + ${aircraft.fmsStatus} + + + ${aircraft.bosName} + +
+ `; }; - return aircrafts.map((aircraft) => { - const markerRef = useRef(null); - const popupRef = useRef(null); - - useEffect(() => { - const handleClick = () => { - const open = openAircraftMarker.includes(aircraft.id); - if (open) { - setOpenAircraftMarker({ - open: [], - close: [aircraft.id], - }); - } else { - setOpenAircraftMarker({ - open: [aircraft.id], - close: [], - }); + return ( + + { - markerRef.current?.off("click", handleClick); - }; - }, [markerRef.current, aircraft.id, openAircraftMarker]); - - // TODO: Get Overlapping Markers and make them opientate away from the center - const [anchor, setAnchor] = useState< - "topleft" | "topright" | "bottomleft" | "bottomright" - >("topleft"); - - useEffect(() => { - const handleZoom = () => { - const otherMarkers = document.querySelectorAll(".aircraft-marker"); - // get markers and check if they are overlapping - const ownMarker = document.querySelector( - `#aircraft-marker-${aircraft.id}`, - ); - - if (!otherMarkers || !ownMarker) return; - - const marksersInCluster = Array.from(otherMarkers).filter((marker) => { - // if (marker.id === `aircraft-marker-${aircraft.id}`) return false; - const rect1 = (marker as HTMLElement).getBoundingClientRect(); - const rect2 = (ownMarker as HTMLElement).getBoundingClientRect(); - - return !( - rect1.right < rect2.left || - rect1.left > rect2.right || - rect1.bottom < rect2.top || - rect1.top > rect2.bottom - ); - }); - // get the center of all overlapping markers - const markersPosition = marksersInCluster.map((marker) => { - const rect = (marker as HTMLElement).getBoundingClientRect(); - return { - x: rect.left + rect.width / 2, - y: rect.top + rect.height / 2, - }; - }); - const ownMarkerBounds = ( - ownMarker as HTMLElement - ).getBoundingClientRect(); - const ownMarkerPosition = { - x: ownMarkerBounds.left + ownMarkerBounds.width / 2, - y: ownMarkerBounds.top + ownMarkerBounds.height / 2, - }; - - const centerOfOverlappingMarkers = markersPosition.reduce( - (acc, pos) => { - if (acc.x === 0 && acc.y === 0) return pos; - return { - x: (acc.x + pos.x) / 2, - y: (acc.y + pos.y) / 2, - }; - }, - { - x: 0, - y: 0, - }, - ); - if (marksersInCluster.length > 1) { - if (centerOfOverlappingMarkers.y < ownMarkerPosition.y) { - if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { - setAnchor("topright"); - } else { - setAnchor("topleft"); - } - } else { - if (centerOfOverlappingMarkers.x > ownMarkerPosition.x) { - setAnchor("bottomright"); - } else { - setAnchor("bottomleft"); - } - } - } else { - // Default - setAnchor("topleft"); - } - }; - map.on("zoom", handleZoom); - - return () => { - map.off("zoom", handleZoom); - }; - }, [map]); - return ( - - + {openAircraftMarker.includes(aircraft.id) && ( + - {openAircraftMarker.includes(aircraft.id) && ( - +
-
Alla
- - )} - - ); - }); +
+
+
+
+
+ +
+
+ +
+
+ {aircraft.fmsStatus} +
+
+ Einsatz 250411 +
+
+
+
+ + )} + + ); +}; + +export const AircraftMarker = (props: any) => { + const { openAircraftMarker, setOpenAircraftMarker } = useMapStore( + (state) => state, + ); + + const aircrafts = useAircraftsStore((state) => state.aircrafts); + const map = useMap(); + + return aircrafts.map((aircraft) => ( + + )); }; diff --git a/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx b/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx index d82c1964..831e5408 100644 --- a/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx +++ b/apps/dispatch/app/(dispatch)/_components/map/ContextMenu.tsx @@ -5,14 +5,25 @@ import { Popup, useMap } from "react-leaflet"; export const ContextMenu = () => { const map = useMap(); - const { contextMenu, setContextMenu, setSearchElements } = useMapStore(); + const { contextMenu, setContextMenu, setSearchElements, setSearchPopup } = + useMapStore(); useEffect(() => { - map.on("contextmenu", (e) => { - // setOpenMissionMarker({ open: [], close: openMissionMarker }); + const handleContextMenu = (e: any) => { setContextMenu({ lat: e.latlng.lat, lng: e.latlng.lng }); - // setSearchPopup(null); - }); + }; + const handleClick = (e: any) => { + setContextMenu(null); + setSearchPopup(null); + }; + + map.on("contextmenu", handleContextMenu); + map.on("click", handleClick); + + return () => { + map.off("contextmenu", handleContextMenu); + map.off("click", handleClick); + }; }, [contextMenu]); if (!contextMenu) return null; @@ -27,7 +38,7 @@ export const ContextMenu = () => { {/* // TODO: maske: */}