import type { RefObject } from "react"; import { createPortal } from "react-dom"; import type { Edge, Node } from "@xyflow/react"; import { useI18n } from "../../i18n"; import type { CtxMenu } from "./pageTypes"; import type { EdgeStyleData } from "./edgeStyle"; import { formatPortPairLabel } from "./linkDisplay"; import { isPlaceholderSource } from "./searchUtils"; import type { NeNodeData } from "./TopologyReactFlowView"; export type ResolvedEdgeStyle = { stroke: string; strokeWidth: number; strokeDasharray?: string; }; export type TopologyCtxMenuProps = { ctxMenu: CtxMenu; menuRef: RefObject; portalTarget: HTMLElement | null; fullscreen: boolean; selectedNodeCount: number; selectedNode: Node | null; selectedEdge: Edge | null; selectedEdgeData: EdgeStyleData; selectedEdgeResolved: ResolvedEdgeStyle | null; selectedEdgeSourceNode: Node | null; selectedEdgeTargetNode: Node | null; expandPhysicalLinks: boolean; discovering: boolean; isWorldFlatCanvas: boolean; createRegionPending: boolean; onClose: () => void; onToggleFullscreen: () => void; onRemoveSelected: () => void; onDiscoverSelected: () => void; onConnectTestSelected: () => void; selectedDiscoverableCount: number; selectedConnectableCount: number; onOpenCreateNe: (flowX: number, flowY: number) => void; onPromptNewSubRegion: () => void; onRenameSelectedNode: () => void; onDiscoverOne: (node: Node | null) => void; onOpenWebcrt: (node: Node | null) => void; onOpenNe: (node: Node | null) => void; onOpenAlarms: (node: Node | null) => void; onConnectTest: (node: Node | null) => void; connectTestBusy?: boolean; onPurgePlaceholder: (id: string) => void; onRemoveNode: (id: string) => void; onExpandPhysicalLinks: () => void; onPushHistory: () => void; onPatchSelectedEdgeStyle: ( patch: Partial, opts?: { skipHistory?: boolean }, ) => void; onOpenPortTraffic: (edge: Edge | null) => void; onRemoveEdge: (id: string) => void; }; export function TopologyCtxMenu({ ctxMenu, menuRef, portalTarget, fullscreen, selectedNodeCount, selectedNode, selectedEdge, selectedEdgeData, selectedEdgeResolved, selectedEdgeSourceNode, selectedEdgeTargetNode, expandPhysicalLinks, discovering, isWorldFlatCanvas, createRegionPending, onClose, onToggleFullscreen, onRemoveSelected, onDiscoverSelected, onConnectTestSelected, selectedDiscoverableCount, selectedConnectableCount, onOpenCreateNe, onPromptNewSubRegion, onRenameSelectedNode, onDiscoverOne, onOpenWebcrt, onOpenNe, onOpenAlarms, onConnectTest, connectTestBusy = false, onPurgePlaceholder, onRemoveNode, onExpandPhysicalLinks, onPushHistory, onPatchSelectedEdgeStyle, onOpenPortTraffic, onRemoveEdge, }: TopologyCtxMenuProps) { const { t } = useI18n(); return createPortal(
    e.preventDefault()} > {ctxMenu.kind === "selection" ? ( <>
  • {t("topology.selectionMenu")} · {selectedNodeCount}
  • ) : ctxMenu.kind === "pane" ? ( <>
  • {t("topology.paneMenu")}
  • {!isWorldFlatCanvas ? (
  • ) : null}
  • ) : ctxMenu.kind === "node" ? ( <>
  • {t("topology.nodeMenu")}
  • {selectedNode && isPlaceholderSource(selectedNode.data.managed_source, selectedNode.data.ne_ip) ? (
  • ) : null}
  • ) : ( <>
  • {t("topology.edgeMenu")}
  • e.stopPropagation()}>
    {t("topology.edgeStyle")}
    {selectedEdgeResolved ? (
    {Number(selectedEdgeData.member_count || 1) > 1 ? (
    {t("topology.linkMembers").replace( "{{count}}", String(selectedEdgeData.member_count || 0), )}
      {(selectedEdgeData.members || []).map((m) => (
    • {formatPortPairLabel(m.a_port, m.b_port, m.display_label) || m.id.slice(0, 8)}
    • ))}
    {!expandPhysicalLinks ? ( ) : null}
    ) : null}
    ) : null}
  • )}
, portalTarget || document.body, ); }