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
### Hinzugefügt

View File

@@ -1,2 +1,2 @@
// /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
// 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 };

View File

@@ -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(
`
<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 = [
markers.forEach((markerData) => {
const marker = L.marker([markerData.lat, markerData.lng], {
contextmenu: true,
contextmenuItems: [
{
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");
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";
markerGroup.addLayer(marker);
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);
// 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],
})
);
}
});
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();
});
GMA.addLayer(marker);
oms.addMarker(marker);
});
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;

View File

@@ -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");
}
}

View File

@@ -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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
L.tileLayer(tileLayerUrl, {
attribution: "&copy; 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);
}
};

View File

@@ -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.");
}
}

View File

@@ -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");
},