export function computeDiagramData()

in packages/dmn-editor/src/store/computed/computeDiagramData.ts [64:408]


export function computeDiagramData(
  diagram: State["diagram"],
  definitions: State["dmn"]["model"]["definitions"],
  externalModelTypesByNamespace: TypeOrReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>,
  indexedDrd: TypeOrReturnType<Computed["indexedDrd"]>,
  isAlternativeInputDataShape: boolean
) {
  // console.time("nodes");
  ___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches.flag =
    !___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches.flag;

  const drgElementsWithoutVisualRepresentationOnCurrentDrd: string[] = [];

  const selectedNodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
  const selectedEdgesById = new Map<string, RF.Edge<DmnDiagramEdgeData>>();
  const selectedNodeTypes = new Set<NodeType>();
  const nodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
  const edgesById = new Map<string, RF.Edge<DmnDiagramEdgeData>>();
  const parentIdsById = new Map<string, DmnDiagramNodeData>();
  const externalNodesByNamespace = new Map<string, Array<RF.Node<DmnDiagramNodeData>>>();
  const edgesFromExternalNodesByNamespace = new Map<string, Array<RF.Edge<DmnDiagramEdgeData>>>();

  const { selectedNodes, draggingNodes, resizingNodes, selectedEdges } = {
    selectedNodes: new Set(diagram._selectedNodes),
    draggingNodes: new Set(diagram.draggingNodes),
    resizingNodes: new Set(diagram.resizingNodes),
    selectedEdges: new Set(diagram._selectedEdges),
  };

  // console.time("edges");
  const edges: RF.Edge<DmnDiagramEdgeData>[] = [];

  const drgEdges: DrgEdge[] = [];
  const drgAdjacencyList: DrgAdjacencyList = new Map();

  const ackEdge: AckEdge = ({ id, type, dmnObject, source, target, sourceNamespace }) => {
    const data = {
      dmnObject,
      dmnEdge: id ? indexedDrd.dmnEdgesByDmnElementRef.get(xmlHrefToQName(id, definitions)) : undefined,
      dmnShapeSource: indexedDrd.dmnShapesByHref.get(source),
      dmnShapeTarget: indexedDrd.dmnShapesByHref.get(target),
    };

    const edge: RF.Edge<DmnDiagramEdgeData> = {
      data,
      id,
      type,
      source,
      target,
      selected: selectedEdges.has(id),
    };

    if (sourceNamespace && sourceNamespace !== KIE_UNKNOWN_NAMESPACE) {
      edgesFromExternalNodesByNamespace.set(sourceNamespace, [
        ...(edgesFromExternalNodesByNamespace.get(sourceNamespace) ?? []),
        edge,
      ]);
    }

    edgesById.set(edge.id, edge);
    if (edge.selected) {
      selectedEdgesById.set(edge.id, edge);
    }

    edges.push(edge);

    drgEdges.push({ id, sourceId: source, targetId: target, dmnObject });

    const targetAdjancyList = drgAdjacencyList.get(target);
    if (!targetAdjancyList) {
      drgAdjacencyList.set(target, { dependencies: new Set([source]) });
    } else {
      targetAdjancyList.dependencies.add(source);
    }

    return edge;
  };

  // requirements
  ackRequirementEdges(definitions["@_namespace"], definitions["@_namespace"], definitions.drgElement, ackEdge);

  // associations
  (definitions.artifact ?? []).forEach((dmnObject, index) => {
    if (dmnObject.__$$element !== "association") {
      return;
    }

    ackEdge({
      id: dmnObject["@_id"]!,
      dmnObject: {
        namespace: definitions["@_namespace"],
        type: dmnObject.__$$element,
        id: dmnObject["@_id"]!,
        requirementType: "association",
        index,
      },
      type: EDGE_TYPES.association,
      source: dmnObject.sourceRef?.["@_href"],
      target: dmnObject.targetRef?.["@_href"],
      sourceNamespace: undefined, // association are always from the current namespace
    });
  });

  // console.timeEnd("edges");
  const ackNode: AckNode = (dmnObjectQName, dmnObject, index) => {
    const type = getNodeTypeFromDmnObject(dmnObject);
    if (!type) {
      return undefined;
    }

    // If the QName is composite, we try and get the namespace from the XML namespace declarations. If it's not found, we use `UNKNOWN_DMN_NAMESPACE`
    // If the QName is simple, we simply say that the namespace is undefined, which is the same as the default namespace.
    const dmnObjectNamespace = dmnObjectQName.prefix
      ? definitions[`@_xmlns:${dmnObjectQName.prefix}`] ?? KIE_DMN_UNKNOWN_NAMESPACE
      : undefined;

    const id = buildXmlHref({ namespace: dmnObjectNamespace, id: dmnObjectQName.localPart });

    const _shape = indexedDrd.dmnShapesByHref.get(id);
    if (!_shape) {
      drgElementsWithoutVisualRepresentationOnCurrentDrd.push(id);
      return undefined;
    }

    const { dmnElementRefQName, ...shape } = _shape;

    const data: DmnDiagramNodeData = {
      dmnObjectNamespace,
      dmnObjectQName,
      dmnObject,
      shape,
      index,

      // Properties to be overridden
      hasHiddenRequirements: false,
      parentRfNode: undefined,
    };

    const newNode: RF.Node<DmnDiagramNodeData> = {
      id,
      type,
      selected: selectedNodes.has(id),
      dragging: draggingNodes.has(id),
      resizing: resizingNodes.has(id),
      position: snapShapePosition(diagram.snapGrid, shape),
      data,
      zIndex: NODE_LAYERS.NODES,
      style: {
        ...snapShapeDimensions(
          diagram.snapGrid,
          shape,
          MIN_NODE_SIZES[type]({ snapGrid: diagram.snapGrid, isAlternativeInputDataShape })
        ),
      },
    };

    if (dmnObject?.__$$element === "decisionService") {
      if (!shape["@_isCollapsed"]) {
        const { containedDecisionHrefsRelativeToThisDmn } = getDecisionServicePropertiesRelativeToThisDmn({
          thisDmnsNamespace: definitions["@_namespace"],
          decisionServiceNamespace: dmnObjectNamespace ?? definitions["@_namespace"],
          decisionService: dmnObject,
        });

        for (let i = 0; i < containedDecisionHrefsRelativeToThisDmn.length; i++) {
          parentIdsById.set(containedDecisionHrefsRelativeToThisDmn[i], data);
        }
      } else {
        newNode.style = {
          ...newNode.style,
          ...DECISION_SERVICE_COLLAPSED_DIMENSIONS,
        };
      }
    }

    if (dmnObjectNamespace && dmnObjectNamespace !== KIE_UNKNOWN_NAMESPACE) {
      externalNodesByNamespace.set(dmnObjectNamespace, [
        ...(externalNodesByNamespace.get(dmnObjectNamespace) ?? []),
        newNode,
      ]);
    }

    nodesById.set(newNode.id, newNode);
    if (newNode.selected) {
      selectedNodesById.set(newNode.id, newNode);
      selectedNodeTypes.add(newNode.type as NodeType);
    }
    return newNode;
  };

  const localNodes: RF.Node<DmnDiagramNodeData>[] = [
    ...(definitions.drgElement ?? []).flatMap((dmnObject, index) => {
      const newNode = ackNode({ type: "xml-qname", localPart: dmnObject["@_id"]! }, dmnObject, index);
      return newNode ? [newNode] : [];
    }),
    ...(definitions.artifact ?? []).flatMap((dmnObject, index) => {
      if (dmnObject.__$$element === "association") {
        return [];
      }

      const newNode = ackNode({ type: "xml-qname", localPart: dmnObject["@_id"]! }, dmnObject, index);
      return newNode ? [newNode] : [];
    }),
  ];

  const externalDrgElementsByIdByNamespace = [...externalModelTypesByNamespace.dmns.entries()].reduce(
    (acc, [namespace, externalDmn]) => {
      // Taking advantage of the loop to add the edges here...
      ackRequirementEdges(
        definitions["@_namespace"],
        externalDmn.model.definitions["@_namespace"],
        externalDmn.model.definitions.drgElement,
        ackEdge
      );

      return acc.set(
        namespace,
        (externalDmn.model.definitions.drgElement ?? []).reduce(
          (acc, e, index) => acc.set(e["@_id"]!, { element: e, index }),
          new Map<
            string,
            {
              index: number;
              element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]>;
            }
          >()
        )
      );
    },
    new Map<
      string,
      Map<
        string,
        {
          index: number;
          element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]>;
        }
      >
    >()
  );

  const externalNodes = [...indexedDrd.dmnShapesByHref.entries()].flatMap(([href, shape]) => {
    if (nodesById.get(href)) {
      return [];
    }

    if (!nodesById.get(href) && !indexedDrd.hrefsOfDmnElementRefsOfShapesPointingToExternalDmnObjects.has(href)) {
      // Unknown local node.
      console.warn(`DMN DIAGRAM: Found a shape that references a local DRG element that doesn't exist.`, shape);
      const newNode = ackNode(shape.dmnElementRefQName, null, -1);
      return newNode ? [newNode] : [];
    }

    const namespace = definitions[`@_xmlns:${shape.dmnElementRefQName.prefix}`];
    if (!namespace) {
      console.warn(
        `DMN DIAGRAM: Found a shape that references an external node with a namespace that is not declared at this DMN.`,
        shape
      );
      const newNode = ackNode(shape.dmnElementRefQName, null, -1);
      return newNode ? [newNode] : [];
    }

    const externalDrgElementsById = externalDrgElementsByIdByNamespace.get(namespace);
    if (!externalDrgElementsById) {
      console.warn(
        `DMN DIAGRAM: Found a shape that references an external node from a namespace that is not provided on this DMN's external DMNs mapping.`,
        shape
      );
      const newNode = ackNode(shape.dmnElementRefQName, null, -1);
      return newNode ? [newNode] : [];
    }

    const externalDrgElement = externalDrgElementsById.get(shape.dmnElementRefQName.localPart);
    if (!externalDrgElement) {
      console.warn(`DMN DIAGRAM: Found a shape that references a non-existent node from an external DMN.`, shape);
      const newNode = ackNode(shape.dmnElementRefQName, null, -1);
      return newNode ? [newNode] : [];
    }

    const newNode = ackNode(shape.dmnElementRefQName, externalDrgElement.element, externalDrgElement.index);
    return newNode ? [newNode] : [];
  });

  // Groups are always at the back. Decision Services after groups, then everything else.
  const sortedNodes = [...localNodes, ...externalNodes]
    .sort((a, b) => Number(b.type === NODE_TYPES.decisionService) - Number(a.type === NODE_TYPES.decisionService))
    .sort((a, b) => Number(b.type === NODE_TYPES.group) - Number(a.type === NODE_TYPES.group));

  // Selected edges go to the end of the array. This is necessary because z-index doesn't work on SVGs.
  const sortedEdges = edges
    .filter((e) => nodesById.has(e.source) && nodesById.has(e.target))
    .sort((a, b) => Number(selectedEdges.has(a.id)) - Number(selectedEdges.has(b.id)));

  // Search on the node list for the missing dependencies on the DRD.
  for (const node of sortedNodes) {
    for (const dependencyNodeId of drgAdjacencyList.get(node.id)?.dependencies ?? new Set()) {
      if (!nodesById.get(dependencyNodeId)) {
        node.data.hasHiddenRequirements = true;
        break;
      }
    }
  }

  // console.timeEnd("nodes");
  if (diagram.overlays.enableNodeHierarchyHighlight) {
    assignClassesToHighlightedHierarchyNodes(diagram._selectedNodes, nodesById, edgesById, drgEdges);
  }

  // Assign parents & z-index to NODES
  for (let i = 0; i < sortedNodes.length; i++) {
    const parentNodeData = parentIdsById.get(sortedNodes[i].id);
    if (parentNodeData) {
      sortedNodes[i].data.parentRfNode = nodesById.get(
        buildXmlHref({
          namespace: parentNodeData.dmnObjectNamespace,
          id: parentNodeData.dmnObjectQName.localPart,
        })
      );
      sortedNodes[i].extent = undefined; // Allows the node to be dragged freely outside of parent's bounds.
      sortedNodes[i].zIndex = NODE_LAYERS.NESTED_NODES;
    }

    if (sortedNodes[i].type === NODE_TYPES.group) {
      sortedNodes[i].zIndex = NODE_LAYERS.GROUP_NODE;
    } else if (sortedNodes[i].type === NODE_TYPES.decisionService) {
      sortedNodes[i].zIndex = NODE_LAYERS.DECISION_SERVICE_NODE;
    }
  }

  return {
    drgEdges,
    drgAdjacencyList,
    nodes: sortedNodes,
    edges: sortedEdges,
    edgesById,
    externalNodesByNamespace,
    edgesFromExternalNodesByNamespace,
    nodesById,
    selectedNodeTypes,
    selectedNodesById,
    selectedEdgesById,
    drgElementsWithoutVisualRepresentationOnCurrentDrd,
  };
}