Context Menus not working in VUE

  <section class="im-p-8 im-flex im-flex-col im-w-full im-gap-6">
    <PageIndicator item1="Home" item2="Submission Settings" item3="Key Mapping Documents" item4="Form I 589" />
    <Card :title="title"
      subtitle="On this screen, the user can configure the fields of the uploaded document and map them to their corresponding sources."
      button1="Save" @handleBtn1="handleSave">
      <div class="im-flex im-p-8 im-self-stretch im-items-stretch im-h-full h-screen">
        <div id="treeMapperDiv" class="im-w-full im-h-screen  im-bg-primary-neutral-200 im-rounded-4"></div>
        <ul id="contextMenu" class="menu">
          <li id="rules" class="menu-item">Rules</li>
        </ul>
      </div>
    </Card>
  </section>
</template>
<script setup lang="ts">
import type { LinkData, MappingData, NodeData, TreeMapperParams } from "~/@types/imigrateTypes";
const route = useRoute();
const packageName = ref();
const title: any = route.query.t;
packageName.value = route.query.pi;
const { $treeMapperApi, } = useNuxtApp();
definePageMeta({
  layout: 'home'
});
import go, {
  Binding,
  Diagram,
  DiagramEvent,
  GraphObject,
  Group,
  Link,
  Margin,
  Node,
  Panel,
  Placeholder,
  Point,
  Routing,
  Shape,
  Spot,
  TextBlock,
  TreeAlignment,
  TreeCompaction,
  TreeLayout
} from 'gojs';
const handleSave = async () => {
  if (mappingLinks.length > 0) {
    try {
      await $treeMapperApi.saveMappingLinks(mappingLinks);
      alert('Mapping saved successfully');
      mappingLinks.splice(0, mappingLinks.length);
    } catch (error) {
      console.error('Error saving mapping links:', error);
      alert('There was an error saving the mapping');
    }
  } else {
    alert('There are no links to save');
  }
};
const staticNodeDataArray: NodeData[] = [
  {
    isGroup: true,
    key: 'Form_I589',
    text: 'Form i589',
    xy: '1120 -500',
    width: 150
  },
];
const staticLinkDataArray: LinkData[] = [];
const nodeDataArray = ref<NodeData[]>(staticNodeDataArray);
const linkDataArray = ref<LinkData[]>(staticLinkDataArray);
const formDestination = 'Form_I589';
const configId = 'CRD001';
const params: TreeMapperParams = {
  d: formDestination,
  c: configId
}
const mappingLinks: MappingData[] = [];
const $ = GraphObject.make;
let ROUTINGSTYLE = 'ToNode';
const getDestination = async () => {
  const res: any = await $treeMapperApi.nodeDataLink(formDestination);
  const newNodeDataArray: NodeData[] = res.nodeDataArray;
  const newLinkDataArray: LinkData[] = res.linkDataArray;
  nodeDataArray.value.push(...newNodeDataArray);
  linkDataArray.value.push(...newLinkDataArray);
  diagram.model = new go.GraphLinksModel(nodeDataArray.value, linkDataArray.value);
};
const getOrigins = async () => {
  const res: any = await $treeMapperApi.originGroups();
  const newNodeDataOrigin: NodeData[] = res.nodes;
  const newLinkDataOrigin: LinkData[] = res.links;
  nodeDataArray.value.push(...newNodeDataOrigin);
  linkDataArray.value.push(...newLinkDataOrigin);
  diagram.model = new go.GraphLinksModel(nodeDataArray.value, linkDataArray.value);
}
const getMappingLinks = async () => {
  const res: LinkData[] = await $treeMapperApi.mappingLinksData(params);
  linkDataArray.value.push(...res);
  diagram.model = new go.GraphLinksModel(nodeDataArray.value, linkDataArray.value);
}
class MyLayout extends TreeLayout {
  constructor() {
    super();
  }
  commitNodes(): void {
    super.commitNodes.call(this);
    if (ROUTINGSTYLE === 'ToGroup') {
      updateNodeWidths(this.group!, this.group?.data.width ?? 100);
    }
  }
}
class TreeNode extends Node {
  findVisibleNode(): Node | null {
    let n: Node | null = this;
    while (n !== null && !n.isVisible()) {
      n = n.findTreeParentNode();
    }
    return n;
  }
}
class MappingLink extends Link {
  getLinkPoint(
    node: Node | null,
    port: GraphObject,
    spot: Spot,
    from: boolean,
    ortho: boolean,
    othernode: Node | null,
    otherport: GraphObject
  ) {
    if (ROUTINGSTYLE !== 'ToGroup') {
      return super.getLinkPoint(
        node,
        port,
        spot,
        from,
        ortho,
        othernode,
        otherport
      );
    } else {
      const r = port.getDocumentBounds();
      const group = node!.containingGroup;
      const b = group !== null ? group.actualBounds : node!.actualBounds;
      const op = othernode!.getDocumentPoint(Spot.Center);
      const x = op.x > r.centerX ? b.right : b.left;
      return new Point(x, r.centerY);
    }
  }
  checkFromNodeToNode(fn: Node, tn: Node) {
    const fg = fn.containingGroup;
    const fb = fg ? fg.actualBounds : fn.actualBounds;
    const fpt = this.getPoint(0);
    const tg = tn.containingGroup;
    const tb = tg ? tg.actualBounds : tn.actualBounds;
    const tpt = this.getPoint(this.pointsCount - 1);
    this.setPoint(1, new Point(fpt.x < tpt.x ? fb.right : fb.left, fpt.y));
    this.setPoint(
      this.pointsCount - 2,
      new Point(fpt.x < tpt.x ? tb.left : tb.right, tpt.y)
    );
  }
  computePoints() {
    const result = super.computePoints();
    if (result && ROUTINGSTYLE === 'ToNode') {
      const fn = this.fromNode;
      const tn = this.toNode;
      if (fn && tn) {
        this.checkFromNodeToNode(fn, tn);
      }
    }
    return result;
  }
}
function hasChildren(node: Node) {
  return node.findTreeChildrenNodes().count > 0;
}
const checkLink = (
  fn: Node,
  fp: GraphObject,
  tn: Node,
  tp: GraphObject,
  link: Link
) => {
  // make sure the nodes are inside different Groups
  if (fn.containingGroup === null || fn.containingGroup.data.key === formDestination)
    return false;
  if (tn.containingGroup === null) return false;
  // optional limit to a single mapping link per node
  // if (fn.linksConnected.any(l => l.category === "Mapping")) return false;
  // if (tn.linksConnected.any(l => l.category === "Mapping")) return false;
  return true;
};
const handleTreeCollapseExpand = (e: DiagramEvent) => {
  e.subject.each((n: any) => {
    n.findExternalTreeLinksConnected().each((l: any) => l.invalidateRoute());
  });
};
const updateNodeWidths = (group: Group, width: any) => {
  if (isNaN(width)) {
    group.memberParts.each((n) => {
      if (n instanceof Node) n.width = NaN; // back to natural width
    });
  } else {
    let minx = Infinity; // figure out minimum group width
    group.memberParts.each((n) => {
      if (n instanceof Node) {
        minx = Math.min(minx, n.actualBounds.x);
      }
    });
    if (minx === Infinity) return;
    let right = minx + width;
    group.memberParts.each((n) => {
      if (n instanceof Node) n.width = Math.max(0, right - n.actualBounds.x);
    });
  }
};
const makeGroupLayout = () => {
  return $(MyLayout, {
    alignment: TreeAlignment.Start,
    angle: 0,
    compaction: TreeCompaction.None,
    layerSpacing: 16,
    layerSpacingParentOverlap: 1,
    nodeIndentPastParent: 1.0,
    nodeSpacing: 5,
    setsPortSpot: false,
    setsChildPortSpot: false
  });
};
const makeTree = (
  level: number,
  count: number,
  max: number,
  nodeDataArray: NodeData[],
  linkDataArray: LinkData[],
  parentdata: NodeData,
  groupkey: number,
  rootkey: number
) => {
  const numchildren = Math.floor(Math.random() * 10);
  for (let i = 0; i < numchildren; i++) {
    if (count >= max) return count;
    count++;
    const childData: any = { key: rootkey + count, group: groupkey };
    nodeDataArray.push(childData);
    linkDataArray.push({ from: parentdata.key, to: childData.key });
    if (level > 0 && Math.random() > 0.5) {
      count = makeTree(
        level - 1,
        count,
        max,
        nodeDataArray,
        linkDataArray,
        childData,
        groupkey,
        rootkey
      );
    }
  }
  return count;
};
// Asegúrate de que contextMenuRef esté correctamente asignado
const contextMenuRef = ref<HTMLElement | null>(null);
const handleContextMenuShowing = (e: go.InputEvent) => {
  console.log('Context menu is being shown', e);
  const menu = contextMenuRef.value;
  if (menu) {
    // Ajusta la posición del menú usando las coordenadas del evento
    menu.style.display = 'block';
    menu.style.left = `${e.viewPoint.x}px`;
    menu.style.top = `${e.viewPoint.y}px`;
    menu.style.opacity = '1'; 
    e.handled = true; // Previene el comportamiento por defecto del menú contextual
  }
};
const handleContextMenuHiding = () => {
  const menu = contextMenuRef.value;
  if (menu) {
    menu.style.display = 'none';
  }
};
let diagram: Diagram;
onMounted(() => {
  getDestination();
  getOrigins();
  getMappingLinks();
  diagram = new Diagram('treeMapperDiv', {
    'commandHandler.copiesTree': true,
    'commandHandler.deletesTree': true,
    'linkingTool.archetypeLinkData': { category: 'Mapping' },
    'linkingTool.linkValidation': checkLink,
    'relinkingTool.linkValidation': checkLink,
    'undoManager.isEnabled': true,
    'contextMenuTool': new go.ContextMenuTool(),
    TreeCollapsed: handleTreeCollapseExpand,
    TreeExpanded: handleTreeCollapseExpand,
    LinkDrawn: async (e: DiagramEvent) => {
      const link = e.subject.part;
      if (link && link.data.category === 'Mapping') {
        const fromNode = link.fromNode;
        const toNode = link.toNode;
        const parentNode = fromNode.findTreeParentNode();
        const toGroup = toNode.containingGroup;
        const mappingLink: MappingData = {
          origin: parentNode ? parentNode.data.key : null,
          from: link.data.from,
          group: 0,
          package_id: packageName.value,
          destination: toGroup ? toGroup.data.key : null,
          to: link.data.to,
          order: 1,
          config: configId
        };
        mappingLinks.push(mappingLink);
      }
    }
  });
  const selectionAdornmentTemplate = $(
    go.Adornment, 'Auto',
    $(go.Shape, 'RoundedRectangle', {
      fill: null,
      stroke: '#55C6C0',
      strokeWidth: 2
    }),
    $(go.Placeholder)
  );
  diagram.nodeTemplate = $(
    TreeNode,
    { isTreeExpanded: false, movable: false, copyable: false, deletable: false },
    {
      selectionAdorned: true,
      selectionAdornmentTemplate: selectionAdornmentTemplate,
      background: 'white'
    },
    new Binding('fromLinkable', 'group', (k) => k !== formDestination),
    new Binding('toLinkable', 'group', (k) => k === formDestination),
    $('TreeExpanderButton', {
      width: 14,
      height: 14,
      'ButtonIcon.stroke': 'white',
      'ButtonIcon.strokeWidth': 2,
      'ButtonBorder.fill': '#55C6C0',
      'ButtonBorder.stroke': null,
      'ButtonBorder.figure': 'Circle',
      _buttonFillOver: '#00AAA1',
      _buttonStrokeOver: null,
      _buttonFillPressed: null
    }),
    $(
      Panel,
      'Auto',
      { position: new Point(16, -10) },
      $(
        go.Shape,
        'RoundedRectangle',
        {
          fill: 'white',
          strokeWidth: 1,
          stroke: 'transparent',
          name: 'SHAPE'
        },
      ),
      $(
        go.TextBlock,
        {
          stroke: '#616161',
          margin: new go.Margin(4, 0, 4, 0)
        },
        new go.Binding('text', 'text'),
        new go.Binding("font", "", function (node) {
          return hasChildren(node.part) ? "600 18px Figtree, sans-serif" : "16px Figtree, sans-serif";
        }).ofObject()
      )
    )
  );
  diagram.linkTemplate = $(Link);
  diagram.linkTemplate = $(
    Link,
    {
      selectable: true,
      routing: Routing.Orthogonal,
      fromEndSegmentLength: 4,
      toEndSegmentLength: 4,
      fromSpot: new Spot(0.001, 1, 7, 0),
      toSpot: Spot.Left
    },
  );
  diagram.linkTemplateMap.add(
    'Mapping',
    $(
      MappingLink,
      {
        isTreeLink: false,
        isLayoutPositioned: false,
        layerName: 'Foreground'
      },
      { fromSpot: Spot.Right, toSpot: Spot.Left },
      { relinkableFrom: true, relinkableTo: true },
      $(Shape, { name: 'link', stroke: '#C2C2C2', strokeWidth: 2 }
      ),
    )
  );
  diagram.groupTemplate = $(
    Group,
    'Auto',
    { deletable: false, layout: makeGroupLayout(), selectionObjectName: 'card' },
    new Binding('position', 'xy', Point.parse).makeTwoWay(Point.stringify),
    new Binding('layout', 'width', makeGroupLayout),
    $(
      go.Shape,
      'RoundedRectangle',
      {
        name: 'card',
        fill: 'white',
        stroke: '#E8E8E8'
      }
    ),
    $(
      Panel,
      'Vertical',
      { defaultAlignment: Spot.Left },
      $(
        TextBlock,
        {
          font: '600 20px Figtree, sans-serif',
          stroke: '#005450',
          margin: new Margin(5, 5, 0, 5)
        },
        new Binding('text')
      ),
      $(Placeholder, { padding: 5 })
    )
  );
  diagram.model = new go.GraphLinksModel(nodeDataArray.value, linkDataArray.value);
});
</script>
<style type="text/css">
/* CSS for the traditional context menu */
.menu {
  display: none;
  position: absolute;
  opacity: 0;
  margin: 0;
  padding: 8px 0;
  z-index: 999;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
  list-style: none;
  background-color: #ffffff;
  border-radius: 4px;
}
.menu-item {
  display: block;
  position: relative;
  min-width: 60px;
  margin: 0;
  padding: 6px 16px;
  font: bold 12px sans-serif;
  color: rgba(0, 0, 0, 0.87);
  cursor: pointer;
}
.menu-item::before {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  pointer-events: none;
  content: '';
  width: 100%;
  height: 100%;
  background-color: #000000;
}
.menu-item:hover::before {
  opacity: 0.04;
}
.menu .menu {
  top: -8px;
  left: 100%;
}
.show-menu,
.menu-item:hover>.menu {
  display: block;
  opacity: 1;
}
</style>

In this VUE code I have tried to use the context menu but it has not been possible, I have not had any errors but I cannot get the behavior of the demo.

Basically I was trying to make the mini modal with the word Rules appear when I right-click on the link or node, I put an address
const contextMenuTool = diagram.toolManager.contextMenuTool as go.ContextMenuTool;
// Configure the context menu for the diagram
contextMenuTool.showContextMenu = (contextMenu: go.HTMLInfo, obj: go.GraphObject | null) => {
console.log(‘Context menu tool is triggered’);
if (contextMenuRef.value) {
handleContextMenuShowing(contextMenu as unknown as go.InputEvent);
}
};
contextMenuTool.hideContextMenu = handleContextMenuHiding;
document.getElementById(‘rules’)?.addEventListener(‘click’, () => {
window.open(‘https://www.google.com’, ‘_blank’);
});
contextMenuRef.value = document.getElementById(‘contextMenu’);

Could someone please guide me? I really appreciate your help

Hello, please only request support in one location. We’ve responded via email.

5 posts were split to a new topic: Tracking the deletion of links

A post was split to a new topic: Ordering of links at node