+
{mapId ? (
{
- dirtyRef.current = true;
+ if (changes.some((c) => c.type !== "select")) dirtyRef.current = true;
onNodesChange(changes);
}}
onEdgesChange={(changes) => {
- dirtyRef.current = true;
+ if (changes.some((c) => c.type !== "select")) dirtyRef.current = true;
onEdgesChange(changes);
}}
onConnect={onConnect}
onNodeClick={(_e, node) => {
- setSelectedEdgeId(null);
- setSelectedNodeId(node.id);
+ setCtxMenu(null);
+ selectNodeOnly(node.id);
}}
onEdgeClick={(_e, edge) => {
- setSelectedNodeId(null);
- setSelectedEdgeId(edge.id);
+ setCtxMenu(null);
+ selectEdgeOnly(edge.id);
}}
onPaneClick={() => {
- setSelectedNodeId(null);
- setSelectedEdgeId(null);
+ setCtxMenu(null);
+ clearSelection();
}}
+ onNodeContextMenu={(e, node) => {
+ e.preventDefault();
+ selectNodeOnly(node.id);
+ const pos = placeCtxMenu(e.clientX, e.clientY);
+ setCtxMenu({ kind: "node", id: node.id, ...pos });
+ }}
+ onEdgeContextMenu={(e, edge) => {
+ e.preventDefault();
+ selectEdgeOnly(edge.id);
+ const pos = placeCtxMenu(e.clientX, e.clientY);
+ setCtxMenu({ kind: "edge", id: edge.id, ...pos });
+ }}
+ onMoveStart={closeCtxMenu}
onInit={(inst) => {
rfRef.current = inst;
}}
@@ -1086,7 +1180,16 @@ export function TopologyPage() {
elementsSelectable
>
-
+
+ void toggleFullscreen()}
+ title={fullscreen ? t("topology.exitFullscreen") : t("topology.fullscreen")}
+ aria-label={fullscreen ? t("topology.exitFullscreen") : t("topology.fullscreen")}
+ >
+
+
+
@@ -1095,6 +1198,73 @@ export function TopologyPage() {
)}
+
+ {ctxMenu ? (
+
e.preventDefault()}
+ >
+ {ctxMenu.kind === "node" ? (
+ <>
+ -
+
+
+ -
+
+
+ -
+
+
+
+ -
+
+
+ >
+ ) : (
+ -
+
+
+ )}
+
+ ) : null}