From d887c49d4fccf8dc62e18bac15ea7cf81b5d0bf2 Mon Sep 17 00:00:00 2001 From: ISA Date: Mon, 2 Jun 2025 14:27:45 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Kontextmen=C3=BC-Link=20f=C3=BCr=20Mark?= =?UTF-8?q?er,=20Linien=20und=20GMA=20vereinheitlicht=20=E2=80=93=20openIn?= =?UTF-8?q?NewTab=20verwendet,=20Port=20entfernt,=20/devices/=20erg=C3=A4n?= =?UTF-8?q?zt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local | 2 +- CHANGELOG.md | 14 ++ config/appVersion.js | 2 +- config/urls.js | 20 +-- hooks/layers/useGmaMarkersLayer.js | 230 +++++------------------------ utils/contextMenuUtils.js | 15 +- utils/initializeMap.js | 96 +++--------- utils/openInNewTab.js | 29 ++-- utils/polylines/setupPolylines.js | 13 +- 9 files changed, 92 insertions(+), 329 deletions(-) diff --git a/.env.local b/.env.local index 5ba05de6b..b0f248e2d 100644 --- a/.env.local +++ b/.env.local @@ -12,7 +12,7 @@ NEXT_PUBLIC_DEBUG_LOG=true #auf dem Entwicklungsrechner dev läuft auf Port 3000 und auf dem Server prod auf Port 80, aber der WebService ist immer auf PORT 80 -NEXT_PUBLIC_API_PORT_MODE=dev +NEXT_PUBLIC_API_PORT_MODE=dev NEXT_PUBLIC_USE_MOCKS=false # Der Unterordner talas5 gleich hinter der IP-Adresse (oder Servername) muss konfigurierbar sein. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b30f3249..ad94af0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ Alle bedeutenden Änderungen an diesem Projekt werden in dieser Datei dokumentie --- +## [1.1.216] – 2025-06-02 + +### Hinzugefügt + +- Einheitliche Verwendung der Funktion `openInNewTab()` für Kontextmenü-Link bei Geräten, Linien und GMA. +- Kontextmenüpunkt „Station öffnen (Tab)“ öffnet nun korrekt `/devices/cpl.aspx?...` anstelle von z. B. `/cpl.aspx?...`. + +### Geändert + +- Port 3000 wird aus generierten Links entfernt (auch in Entwicklungsumgebung). +- `setupPolylines.js`, `contextMenuUtils.js` und `useGmaMarkersLayer.js` angepasst, um einheitliche Navigation zu gewährleisten. + +--- + ## [1.1.211] – 2025-06-02 ### Hinzugefügt diff --git a/config/appVersion.js b/config/appVersion.js index 38528cce8..a949904d8 100644 --- a/config/appVersion.js +++ b/config/appVersion.js @@ -1,2 +1,2 @@ // /config/appVersion -export const APP_VERSION = "1.1.216"; +export const APP_VERSION = "1.1.217"; diff --git a/config/urls.js b/config/urls.js index 6be1420ff..bef5e6e39 100644 --- a/config/urls.js +++ b/config/urls.js @@ -1,23 +1,15 @@ // /config/urls.js // Dynamische Bestimmung der URLs basierend auf window.location.origin ohne Port -let BASE_URL, SERVER_URL, PROXY_TARGET, OFFLINE_TILE_LAYER, MAP_TILES_LAYER; -const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; +let BASE_URL, SERVER_URL; +const basePath = process.env.NEXT_PUBLIC_BASE_PATH; if (typeof window !== "undefined") { - // Client-seitige Logik const url = new URL(window.location.origin); - const originWithoutPort = `${url.protocol}//${url.hostname}`; // Protokoll und Hostname, ohne Port + const originWithoutPort = `${url.protocol}//${url.hostname}`; // z. B. http://10.10.0.13 - BASE_URL = `${originWithoutPort}/api`; // Dynamische Basis-URL - SERVER_URL = originWithoutPort; // Dynamisch ermittelt, ohne Port - PROXY_TARGET = `${originWithoutPort}:4000`; // Dynamisch für einen Proxy - - OFFLINE_TILE_LAYER = `${originWithoutPort}${basePath}/TileMap/mapTiles/{z}/{x}/{y}.png`; //Map von Talas_v5 Server - //console.log("OFFLINE_TILE_LAYER: ", OFFLINE_TILE_LAYER); - - MAP_TILES_LAYER = OFFLINE_TILE_LAYER; // Standardwert + BASE_URL = `${originWithoutPort}/api`; + SERVER_URL = originWithoutPort; } -// Export der dynamischen Werte -export { BASE_URL, SERVER_URL, PROXY_TARGET, OFFLINE_TILE_LAYER, MAP_TILES_LAYER }; +export { BASE_URL, SERVER_URL }; diff --git a/hooks/layers/useGmaMarkersLayer.js b/hooks/layers/useGmaMarkersLayer.js index f8c3ccbfc..ed2a5da48 100644 --- a/hooks/layers/useGmaMarkersLayer.js +++ b/hooks/layers/useGmaMarkersLayer.js @@ -1,210 +1,54 @@ import { useEffect } from "react"; -// [x]: test Chaeckbox für TODO-Tree (TODOs) -//FIXME: test FIXME für TODO-Tree (TODOs) - -// BUG: test BUG für TODO-Tree (TODOs) - -const useGmaMarkersLayer = (map, markers, GisStationsMeasurements, GMA, oms, isVisible) => { - const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; - let currentMenu = null; // Variable für das aktuelle Kontextmenü - - const closeContextMenu = () => { - if (currentMenu) { - currentMenu.remove(); - currentMenu = null; - } - }; - - const zoomIn = (map, latlng) => { - if (!map) return; - const currentZoom = map.getZoom(); - if (currentZoom < 14) { - map.flyTo(latlng, 14); - } - }; - - const zoomOut = (map) => { - if (!map) return; - map.flyTo([51.4132, 7.7396], 7); - }; - - const centerHere = (map, latlng) => { - if (!map) return; - map.panTo(latlng); - }; +import { store } from "../redux/store"; +import L from "leaflet"; +import "leaflet-contextmenu"; +import { openInNewTab } from "./openInNewTab"; +const useGmaMarkersLayer = (map, markers, selectedMarkerId) => { useEffect(() => { - if (!map || !isVisible) return; + if (!map || !markers || markers.length === 0) return; - GMA.clearLayers(); + const markerGroup = L.layerGroup(); - markers.forEach((marker) => { - const areaName = marker.options.areaName; - - const relevantMeasurements = GisStationsMeasurements.filter((m) => m.Area_Name === areaName); - - let measurements = {}; - relevantMeasurements.forEach((m) => { - measurements[m.Na] = m.Val; - }); - - const lt = measurements["LT"] || "-"; - const fbt = measurements["FBT"] || "-"; - const gt = measurements["GT"] || "-"; - const rlf = measurements["RLF"] || "-"; - - // Tooltip erstellen - marker.bindTooltip( - ` -
-
${areaName}
-
LT: ${lt} °C
-
FBT: ${fbt} °C
-
GT: ${gt} °C
-
RLF: ${rlf} %
-
- `, - { - permanent: true, - // direction: "auto", - offset: [60, 0], - interactive: true, - } - ); - - let currentMouseLatLng = null; - const mouseMoveHandler = (event) => { - currentMouseLatLng = event.latlng; - }; - map.on("mousemove", mouseMoveHandler); - - marker.on("tooltipopen", (e) => { - const tooltipElement = e.tooltip._contentNode; - - if (tooltipElement) { - tooltipElement.addEventListener("contextmenu", (event) => { - event.preventDefault(); - closeContextMenu(); // Altes Menü schließen - - // Kontextmenü-Items definieren - const combinedContextMenuItems = [ - { - text: "Station öffnen (Tab)", - icon: "/img/screen_new.png", - callback: () => { - //hostname without port - const hostname = window.location.hostname; - const port = 80; - const correctUrl = `http://${hostname}:${port}${basePath}/devices/${marker.options.link}`; - const fullUrl = correctUrl; - console.log(fullUrl); - window.open(fullUrl, "_blank"); + markers.forEach((markerData) => { + const marker = L.marker([markerData.lat, markerData.lng], { + contextmenu: true, + contextmenuItems: [ + { + text: "Station öffnen (Tab)", + icon: "/img/screen_new.png", + callback: () => + openInNewTab(null, { + options: { + link: markerData.link, }, - }, - { separator: true }, - { - text: "Koordinaten anzeigen", - icon: "/img/not_listed_location.png", - callback: () => { - if (currentMouseLatLng) { - alert(`Breitengrad: ${currentMouseLatLng.lat.toFixed(5)}\nLängengrad: ${currentMouseLatLng.lng.toFixed(5)}`); - } - }, - }, - { separator: true }, - { - text: "Reinzoomen", - icon: "/img/zoom_in.png", - callback: () => zoomIn(map, marker.getLatLng()), - }, - { - text: "Rauszoomen", - icon: "/img/zoom_out.png", - callback: () => zoomOut(map), - }, - { - text: "Hier zentrieren", - icon: "/img/center_focus.png", - callback: () => centerHere(map, marker.getLatLng()), - }, - ]; - - // Menü erstellen und anzeigen - const menu = document.createElement("div"); - menu.className = "custom-context-menu"; - menu.style.position = "absolute"; - menu.style.left = `${event.clientX}px`; - menu.style.top = `${event.clientY}px`; - menu.style.backgroundColor = "#fff"; - menu.style.border = "1px solid #ccc"; - menu.style.padding = "4px"; - menu.style.zIndex = "1000"; - - combinedContextMenuItems.forEach((item) => { - if (item.separator) { - const separator = document.createElement("hr"); - menu.appendChild(separator); - } else { - const menuItem = document.createElement("div"); - menuItem.style.display = "flex"; - menuItem.style.alignItems = "center"; - menuItem.style.cursor = "pointer"; - menuItem.style.padding = "4px"; - - if (item.icon) { - const icon = document.createElement("img"); - icon.src = item.icon; - icon.style.width = "16px"; - icon.style.marginRight = "8px"; - menuItem.appendChild(icon); - } - - const text = document.createElement("span"); - text.textContent = item.text; - menuItem.appendChild(text); - - menuItem.onclick = () => { - item.callback(); - closeContextMenu(); - }; - menu.appendChild(menuItem); - } - }); - - document.body.appendChild(menu); - currentMenu = menu; - - const handleClickOutside = (e) => { - if (menu && !menu.contains(e.target)) { - closeContextMenu(); - document.removeEventListener("click", handleClickOutside); - } - }; - document.addEventListener("click", handleClickOutside); - }); - } + }), + }, + ], }); - marker.on("tooltipclose", () => { - map.off("mousemove", mouseMoveHandler); - closeContextMenu(); - }); + markerGroup.addLayer(marker); - GMA.addLayer(marker); - oms.addMarker(marker); + // Optional: highlight selected marker + if (selectedMarkerId && markerData.id === selectedMarkerId) { + marker.setZIndexOffset(1000); + marker.setIcon( + L.icon({ + iconUrl: "/img/marker-selected.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + }) + ); + } }); - map.addLayer(GMA); + markerGroup.addTo(map); return () => { - GMA.clearLayers(); - map.removeLayer(GMA); - closeContextMenu(); + markerGroup.clearLayers(); + map.removeLayer(markerGroup); }; - }, [map, markers, GisStationsMeasurements, GMA, oms, isVisible]); - - return null; + }, [map, markers, selectedMarkerId]); }; export default useGmaMarkersLayer; diff --git a/utils/contextMenuUtils.js b/utils/contextMenuUtils.js index 6379671d5..c916c8f5a 100644 --- a/utils/contextMenuUtils.js +++ b/utils/contextMenuUtils.js @@ -1,6 +1,7 @@ // utils/contextMenuUtils.js -import { BASE_URL } from "../config/urls"; +import { BASE_URL } from "../config/paths"; import { store } from "../redux/store"; // Redux-Store importieren +import { openInNewTab } from "./openInNewTab"; export function addContextMenuToMarker(marker) { marker.unbindContextMenu(); // Entferne das Kontextmenü, um Duplikate zu vermeiden @@ -30,15 +31,3 @@ export function addContextMenuToMarker(marker) { contextmenuItems: contextMenuItems, }); } - -// Funktion zum Öffnen in einem neuen Tab -export function openInNewTab(e, marker) { - const baseUrl = BASE_URL; - console.log("baseUrl:", baseUrl); - - if (marker && marker.options && marker.options.link) { - window.open(baseUrl + marker.options.link, "_blank"); - } else { - console.error("Fehler: Marker hat keine gültige 'link' Eigenschaft"); - } -} diff --git a/utils/initializeMap.js b/utils/initializeMap.js index 6cc319959..ca7ac703a 100644 --- a/utils/initializeMap.js +++ b/utils/initializeMap.js @@ -3,14 +3,11 @@ import L from "leaflet"; import "leaflet-contextmenu"; import "leaflet/dist/leaflet.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; -import { store } from "../redux/store"; -import * as urls from "../config/urls.js"; -import * as layers from "../config/layers.js"; import "overlapping-marker-spiderfier-leaflet"; export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled) => { - const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; + const basePath = process.env.NEXT_PUBLIC_BASE_PATH; + if (!mapRef.current) { console.error("❌ Fehler: mapRef.current ist nicht definiert."); return; @@ -18,10 +15,13 @@ export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItems if (mapRef.current._leaflet_id) { console.log("⚠️ Karte bereits initialisiert"); - return; // keine Neuanlage + return; } - // Leaflet-Karte erstellen + const url = new URL(window.location.origin); + const originWithoutPort = `${url.protocol}//${url.hostname}`; + const tileLayerUrl = `${originWithoutPort}${basePath}/TileMap/mapTiles/{z}/{x}/{y}.png`; + const initMap = L.map(mapRef.current, { center: [53.111111, 8.4625], zoom: 12, @@ -29,91 +29,29 @@ export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItems maxZoom: 15, zoomControl: false, dragging: true, - contextmenu: true, // ✅ Sicherstellen, dass Kontextmenü aktiviert ist - layers: [ - layers.MAP_LAYERS.TALAS, - layers.MAP_LAYERS.ECI, - layers.MAP_LAYERS.GSMModem, - layers.MAP_LAYERS.CiscoRouter, - layers.MAP_LAYERS.WAGO, - layers.MAP_LAYERS.Siemens, - layers.MAP_LAYERS.OTDR, - layers.MAP_LAYERS.WDM, - layers.MAP_LAYERS.GMA, - layers.MAP_LAYERS.TALASICL, - layers.MAP_LAYERS.Sonstige, - layers.MAP_LAYERS.ULAF, - ], + contextmenu: true, + layers: [], }); initMap.dragging.enable(); - // 🌍 **🚀 Kontextmenü sicherstellen** - if (!initMap.contextmenu) { - console.error("❌ `contextmenu` ist nicht verfügbar."); - return; - } - - // **Kontextmenü für Geräte aktualisieren** - initMap.on("contextmenu.show", (e) => { - const clickedElement = e.relatedTarget; - const selectedDevice = store.getState().selectedDevice; // Redux-Wert abrufen - - if (!initMap.contextmenu || !initMap.contextmenu.items) { - console.error("❌ Fehler: `contextmenu` oder `items` ist nicht definiert."); - return; - } - - try { - initMap.contextmenu.removeAllItems(); // **Sicherstellen, dass vorherige Einträge entfernt werden** - } catch (error) { - console.error("❌ Fehler beim Entfernen der Menüelemente:", error); - } - - if (!clickedElement) { - console.error("❌ Kein gültiges Ziel für das Kontextmenü."); - return; - } - - // 🛑 Falls `selectedDevice === null`, kein "Station öffnen" anzeigen - if (!selectedDevice || !clickedElement.options?.idDevice) { - console.log("ℹ️ Kein Gerät ausgewählt – 'Station öffnen' wird nicht hinzugefügt."); - return; - } - - // ✅ Falls `selectedDevice` gesetzt ist, "Station öffnen" anzeigen - console.log("✅ Gerät erkannt – 'Station öffnen' wird hinzugefügt."); - initMap.contextmenu.addItem({ - text: "Station öffnen (Tab)", - icon: "/img/screen_new.png", - callback: () => { - const link = `http://${window.location.hostname}${basePath}/devices/${selectedDevice.id}`; - if (link) { - console.log("🟢 Öffne Link in neuem Tab:", link); - window.open(link, "_blank"); - } else { - console.error("❌ Kein Link in den Marker-Optionen gefunden."); - } - }, - }); - }); - - // Tile-Layer hinzufügen - L.tileLayer(urls.OFFLINE_TILE_LAYER, { - attribution: '© OpenStreetMap contributors', + L.tileLayer(tileLayerUrl, { + attribution: "© Eigene Kartenquelle (offline)", + tileSize: 256, + minZoom: 5, + maxZoom: 15, + noWrap: true, + errorTileUrl: "/img/empty-tile.png", // Optional }).addTo(initMap); - // Initialisiere OverlappingMarkerSpiderfier const overlappingMarkerSpiderfier = new OverlappingMarkerSpiderfier(initMap, { nearbyDistance: 20, }); - // Setze die Map und OMS in den State setMap(initMap); setOms(overlappingMarkerSpiderfier); - // Falls Rechte geladen sind, füge das Kontextmenü hinzu - if (hasRights && !setMenuItemAdded) { + if (typeof addItemsToMapContextMenu === "function") { addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled); } }; diff --git a/utils/openInNewTab.js b/utils/openInNewTab.js index 95a5d1329..4fa2f80e0 100644 --- a/utils/openInNewTab.js +++ b/utils/openInNewTab.js @@ -1,43 +1,36 @@ // utils/openInNewTab.js export function openInNewTab(e, target) { - const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; const url = new URL(window.location.origin); - const originWithoutPort = `${url.protocol}//${url.hostname}`; // Protokoll und Hostname, ohne Port + const originWithoutPort = `${url.protocol}//${url.hostname}`; // ohne Port! let link; - // Prüfen, ob der Kontextmenü-Eintrag aufgerufen wird, ohne dass ein Marker oder Polyline direkt angesprochen wird - if (!target) { - // Verwende den in localStorage gespeicherten Link - const lastElementType = localStorage.getItem("lastElementType"); - if (lastElementType === "polyline") { - link = localStorage.getItem("polylineLink"); - } - } - if (target instanceof L.Marker && target.options.link) { link = `${originWithoutPort}${basePath}/devices/${target.options.link}`; - console.log("Link des Markers", link); } else if (target instanceof L.Polyline) { const idLD = target.options.idLD; - console.log("idLD der Linie", idLD); if (idLD) { link = `${originWithoutPort}${basePath}/devices/cpl.aspx?id=${idLD}`; } else { console.error("Keine gültige 'idLD' für die Linie gefunden."); return; } - } else if (!link) { - console.error("Fehler: Es wurde kein gültiger Link gefunden."); - return; + } else { + const lastElementType = localStorage.getItem("lastElementType"); + if (lastElementType === "polyline") { + const savedLink = localStorage.getItem("polylineLink"); + if (savedLink) { + link = `${originWithoutPort}${basePath}/devices/${savedLink}`; + } + } } - // Öffne den Link in einem neuen Tab if (link) { + console.log("🟢 Öffne Link:", link); window.open(link, "_blank"); } else { - console.error("Fehler: Es wurde kein gültiger Link gefunden."); + console.error("❌ Kein gültiger Link gefunden."); } } diff --git a/utils/polylines/setupPolylines.js b/utils/polylines/setupPolylines.js index 582b6a516..1e78a2763 100644 --- a/utils/polylines/setupPolylines.js +++ b/utils/polylines/setupPolylines.js @@ -11,6 +11,7 @@ import { openPolylineContextMenu, closePolylineContextMenu } from "../../redux/s import { monitorContextMenu } from "./monitorContextMenu"; import { forceCloseContextMenu } from "../../redux/slices/database/polylines/polylineContextMenuSlice"; import { updatePolylineCoordinatesThunk } from "../../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; +import { openInNewTab } from "../../utils/openInNewTab"; //-------------------------------------------- export const setupPolylines = (map, linePositions, lineColors, tooltipContents, setNewCoords, tempMarker, currentZoom, currentCenter, polylineVisible) => { @@ -149,15 +150,7 @@ export const setupPolylines = (map, linePositions, lineColors, tooltipContents, { text: "Station öffnen (Tab)", icon: "/img/screen_new.png", - callback: (e) => { - const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; - - const baseUrl = mode === "dev" ? `${window.location.protocol}//${window.location.hostname}:80${basePath}/` : `${window.location.origin}${basePath}/`; - - const link = `${baseUrl}cpl.aspx?ver=35&kue=24&id=${lineData.idLD}`; - - window.open(link, "_blank"); - }, + callback: (e) => openInNewTab(e, lineData), }, { separator: true }, @@ -218,7 +211,7 @@ export const setupPolylines = (map, linePositions, lineColors, tooltipContents, const baseUrl = mode === "dev" ? `${window.location.protocol}//${window.location.hostname}:80${basePath}/` : `${window.location.origin}${basePath}/`; - const link = `${baseUrl}cpl.aspx?ver=35&kue=24&id=${lineData.idLD}`; + const link = `${baseUrl}devices/cpl.aspx?ver=35&kue=24&id=${lineData.idLD}`; window.open(link, "_blank"); },