feat: Kontextmenü-Link für Marker, Linien und GMA vereinheitlicht – openInNewTab verwendet, Port entfernt, /devices/ ergänzt

This commit is contained in:
ISA
2025-06-02 14:27:45 +02:00
parent 0289656b08
commit d887c49d4f
9 changed files with 92 additions and 329 deletions

View File

@@ -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 ## [1.1.211] 2025-06-02
### Hinzugefügt ### Hinzugefügt

View File

@@ -1,2 +1,2 @@
// /config/appVersion // /config/appVersion
export const APP_VERSION = "1.1.216"; export const APP_VERSION = "1.1.217";

View File

@@ -1,23 +1,15 @@
// /config/urls.js // /config/urls.js
// Dynamische Bestimmung der URLs basierend auf window.location.origin ohne Port // Dynamische Bestimmung der URLs basierend auf window.location.origin ohne Port
let BASE_URL, SERVER_URL, PROXY_TARGET, OFFLINE_TILE_LAYER, MAP_TILES_LAYER; let BASE_URL, SERVER_URL;
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; const basePath = process.env.NEXT_PUBLIC_BASE_PATH;
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// Client-seitige Logik
const url = new URL(window.location.origin); 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 BASE_URL = `${originWithoutPort}/api`;
SERVER_URL = originWithoutPort; // Dynamisch ermittelt, ohne Port SERVER_URL = originWithoutPort;
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
} }
// Export der dynamischen Werte export { BASE_URL, SERVER_URL };
export { BASE_URL, SERVER_URL, PROXY_TARGET, OFFLINE_TILE_LAYER, MAP_TILES_LAYER };

View File

@@ -1,210 +1,54 @@
import { useEffect } from "react"; import { useEffect } from "react";
// [x]: test Chaeckbox für TODO-Tree (TODOs) import { store } from "../redux/store";
//FIXME: test FIXME für TODO-Tree (TODOs) import L from "leaflet";
import "leaflet-contextmenu";
// BUG: test BUG für TODO-Tree (TODOs) import { openInNewTab } from "./openInNewTab";
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);
};
const useGmaMarkersLayer = (map, markers, selectedMarkerId) => {
useEffect(() => { useEffect(() => {
if (!map || !isVisible) return; if (!map || !markers || markers.length === 0) return;
GMA.clearLayers(); const markerGroup = L.layerGroup();
markers.forEach((marker) => { markers.forEach((markerData) => {
const areaName = marker.options.areaName; const marker = L.marker([markerData.lat, markerData.lng], {
contextmenu: true,
const relevantMeasurements = GisStationsMeasurements.filter((m) => m.Area_Name === areaName); contextmenuItems: [
{
let measurements = {}; text: "Station öffnen (Tab)",
relevantMeasurements.forEach((m) => { icon: "/img/screen_new.png",
measurements[m.Na] = m.Val; callback: () =>
}); openInNewTab(null, {
options: {
const lt = measurements["LT"] || "-"; link: markerData.link,
const fbt = measurements["FBT"] || "-";
const gt = measurements["GT"] || "-";
const rlf = measurements["RLF"] || "-";
// Tooltip erstellen
marker.bindTooltip(
`
<div class="p-0 rounded-lg bg-white bg-opacity-90 tooltip-content">
<div class="font-bold text-sm text-black"><span>${areaName}</span></div>
<div class="font-bold text-xxs text-blue-700"><span>LT: ${lt} °C</span></div>
<div class="font-bold text-xxs text-red-700"><span>FBT: ${fbt} °C</span></div>
<div class="font-bold text-xxs text-yellow-500"><span>GT: ${gt} °C</span></div>
<div class="font-bold text-xxs text-green-700"><span>RLF: ${rlf} %</span></div>
</div>
`,
{
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");
}, },
}, }),
{ 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", () => { markerGroup.addLayer(marker);
map.off("mousemove", mouseMoveHandler);
closeContextMenu();
});
GMA.addLayer(marker); // Optional: highlight selected marker
oms.addMarker(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 () => { return () => {
GMA.clearLayers(); markerGroup.clearLayers();
map.removeLayer(GMA); map.removeLayer(markerGroup);
closeContextMenu();
}; };
}, [map, markers, GisStationsMeasurements, GMA, oms, isVisible]); }, [map, markers, selectedMarkerId]);
return null;
}; };
export default useGmaMarkersLayer; export default useGmaMarkersLayer;

View File

@@ -1,6 +1,7 @@
// utils/contextMenuUtils.js // utils/contextMenuUtils.js
import { BASE_URL } from "../config/urls"; import { BASE_URL } from "../config/paths";
import { store } from "../redux/store"; // Redux-Store importieren import { store } from "../redux/store"; // Redux-Store importieren
import { openInNewTab } from "./openInNewTab";
export function addContextMenuToMarker(marker) { export function addContextMenuToMarker(marker) {
marker.unbindContextMenu(); // Entferne das Kontextmenü, um Duplikate zu vermeiden marker.unbindContextMenu(); // Entferne das Kontextmenü, um Duplikate zu vermeiden
@@ -30,15 +31,3 @@ export function addContextMenuToMarker(marker) {
contextmenuItems: contextMenuItems, 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");
}
}

View File

@@ -3,14 +3,11 @@ import L from "leaflet";
import "leaflet-contextmenu"; import "leaflet-contextmenu";
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import "leaflet-contextmenu/dist/leaflet.contextmenu.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"; import "overlapping-marker-spiderfier-leaflet";
export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled) => { 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) { if (!mapRef.current) {
console.error("❌ Fehler: mapRef.current ist nicht definiert."); console.error("❌ Fehler: mapRef.current ist nicht definiert.");
return; return;
@@ -18,10 +15,13 @@ export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItems
if (mapRef.current._leaflet_id) { if (mapRef.current._leaflet_id) {
console.log("⚠️ Karte bereits initialisiert"); 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, { const initMap = L.map(mapRef.current, {
center: [53.111111, 8.4625], center: [53.111111, 8.4625],
zoom: 12, zoom: 12,
@@ -29,91 +29,29 @@ export const initializeMap = (mapRef, setMap, setOms, setMenuItemAdded, addItems
maxZoom: 15, maxZoom: 15,
zoomControl: false, zoomControl: false,
dragging: true, dragging: true,
contextmenu: true, // ✅ Sicherstellen, dass Kontextmenü aktiviert ist contextmenu: true,
layers: [ 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,
],
}); });
initMap.dragging.enable(); initMap.dragging.enable();
// 🌍 **🚀 Kontextmenü sicherstellen** L.tileLayer(tileLayerUrl, {
if (!initMap.contextmenu) { attribution: "&copy; Eigene Kartenquelle (offline)",
console.error("❌ `contextmenu` ist nicht verfügbar."); tileSize: 256,
return; minZoom: 5,
} maxZoom: 15,
noWrap: true,
// **Kontextmenü für Geräte aktualisieren** errorTileUrl: "/img/empty-tile.png", // Optional
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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(initMap); }).addTo(initMap);
// Initialisiere OverlappingMarkerSpiderfier
const overlappingMarkerSpiderfier = new OverlappingMarkerSpiderfier(initMap, { const overlappingMarkerSpiderfier = new OverlappingMarkerSpiderfier(initMap, {
nearbyDistance: 20, nearbyDistance: 20,
}); });
// Setze die Map und OMS in den State
setMap(initMap); setMap(initMap);
setOms(overlappingMarkerSpiderfier); setOms(overlappingMarkerSpiderfier);
// Falls Rechte geladen sind, füge das Kontextmenü hinzu if (typeof addItemsToMapContextMenu === "function") {
if (hasRights && !setMenuItemAdded) {
addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled); addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled);
} }
}; };

View File

@@ -1,43 +1,36 @@
// utils/openInNewTab.js // utils/openInNewTab.js
export function openInNewTab(e, target) { export function openInNewTab(e, target) {
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 || "";
const url = new URL(window.location.origin); 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; 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) { if (target instanceof L.Marker && target.options.link) {
link = `${originWithoutPort}${basePath}/devices/${target.options.link}`; link = `${originWithoutPort}${basePath}/devices/${target.options.link}`;
console.log("Link des Markers", link);
} else if (target instanceof L.Polyline) { } else if (target instanceof L.Polyline) {
const idLD = target.options.idLD; const idLD = target.options.idLD;
console.log("idLD der Linie", idLD);
if (idLD) { if (idLD) {
link = `${originWithoutPort}${basePath}/devices/cpl.aspx?id=${idLD}`; link = `${originWithoutPort}${basePath}/devices/cpl.aspx?id=${idLD}`;
} else { } else {
console.error("Keine gültige 'idLD' für die Linie gefunden."); console.error("Keine gültige 'idLD' für die Linie gefunden.");
return; return;
} }
} else if (!link) { } else {
console.error("Fehler: Es wurde kein gültiger Link gefunden."); const lastElementType = localStorage.getItem("lastElementType");
return; if (lastElementType === "polyline") {
const savedLink = localStorage.getItem("polylineLink");
if (savedLink) {
link = `${originWithoutPort}${basePath}/devices/${savedLink}`;
}
}
} }
// Öffne den Link in einem neuen Tab
if (link) { if (link) {
console.log("🟢 Öffne Link:", link);
window.open(link, "_blank"); window.open(link, "_blank");
} else { } else {
console.error("Fehler: Es wurde kein gültiger Link gefunden."); console.error("❌ Kein gültiger Link gefunden.");
} }
} }

View File

@@ -11,6 +11,7 @@ import { openPolylineContextMenu, closePolylineContextMenu } from "../../redux/s
import { monitorContextMenu } from "./monitorContextMenu"; import { monitorContextMenu } from "./monitorContextMenu";
import { forceCloseContextMenu } from "../../redux/slices/database/polylines/polylineContextMenuSlice"; import { forceCloseContextMenu } from "../../redux/slices/database/polylines/polylineContextMenuSlice";
import { updatePolylineCoordinatesThunk } from "../../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; 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) => { 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)", text: "Station öffnen (Tab)",
icon: "/img/screen_new.png", icon: "/img/screen_new.png",
callback: (e) => { callback: (e) => openInNewTab(e, lineData),
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");
},
}, },
{ separator: true }, { 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 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"); window.open(link, "_blank");
}, },