useRuleFlow.ts 5.29 KB
import type {
  Connection,
  EdgeComponent,
  NodeComponent,
  ValidConnectionFunc,
  GraphNode,
} from '@vue-flow/core';
import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core';
import type { Ref } from 'vue';
import { markRaw, toRaw, unref } from 'vue';
import { isFunction } from 'lodash-es';
import type { CreateNodeModal } from '../src/components/CreateNodeModal';
import { EdgeTypeEnum, NodeTypeEnum } from '../enum';
import { BasicEdge, BasicNode } from '../src/components';
import type { EdgeData, NodeData } from '../types/node';
import type { CreateEdgeModal } from '../src/components/CreateEdgeModal';
import { isInputHandle, isOutputHandle } from '../utils';
import { useAddEdges } from './useAddEdges';
import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer';
import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer';
import { isNumber } from '/@/utils/is';

interface UseRuleFlowOptionsType {
  id: string;
  createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>;
  createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>;
  updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>;
  updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>;
  triggerChange: () => void;
}

const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => {
  const { sourceHandle, targetHandle, source, target } = connection;

  return (
    isOutputHandle(sourceHandle || '') && isInputHandle(targetHandle || '') && source !== target
  );
};

export function useRuleFlow(options: UseRuleFlowOptionsType) {
  const {
    id,
    createEdgeModalActionType,
    updateEdgeDrawerActionType,
    updateNodeDrawerActionType,
    triggerChange,
  } = options;

  const flowActionType = useVueFlow({
    id,
    maxZoom: 1,
    minZoom: 1,
    panOnScroll: true,
    selectionMode: SelectionMode.Partial,
    nodeTypes: {
      [NodeTypeEnum.CUSTOM]: markRaw(BasicNode) as NodeComponent,
    },
    edgeTypes: {
      [EdgeTypeEnum.CUSTOM]: markRaw(BasicEdge) as EdgeComponent,
    },
    connectionLineOptions: {
      type: ConnectionLineType.Bezier,
    },
    defaultViewport: {
      x: 0,
      y: 0,
    },
    isValidConnection(connection, elements) {
      const validateList = [validateInputAndOutput];
      const targetData = elements.targetNode.data as NodeData;

      if (
        targetData.category?.validateConnection &&
        isFunction(targetData.category.validateConnection)
      )
        validateList.push(targetData.category?.validateConnection);

      if (targetData.config?.validateConnection && isFunction(targetData.config.validateConnection))
        validateList.push(targetData.config.validateConnection);

      if (!validateList.every((item) => item(connection, elements))) return false;

      return true;
    },
  });

  const {
    getEdges,
    addEdges,
    findEdge,
    findNode,
    setViewport,
    removeEdges,
    onConnect,
    onPaneReady,
    onNodeDoubleClick,
    onEdgeDoubleClick,
    onNodeDragStop,
  } = flowActionType;

  const { getAddedgesParams } = useAddEdges();

  onConnect(async (params) => {
    const { source } = params;
    const sourceNode = findNode(source);
    const sourceData = sourceNode?.data as NodeData;

    let types: string[] = [];

    if (sourceData && validateHasLabelConnection(sourceData)) {
      const { flag, data } = (await unref(createEdgeModalActionType)?.open(sourceData)) || {};
      if (!flag) return;
      types = toRaw(unref(data));
    }

    handleMaxConnectionPoint(sourceNode);

    addEdges(getAddedgesParams(params, types));

    triggerChange();
  });

  onPaneReady(async () => {
    setViewport({ x: 0, y: 0, zoom: 1 });
  });

  onNodeDoubleClick(async ({ node }) => {
    const { flag, data } =
      (await unref(updateNodeDrawerActionType)?.open(
        toRaw((node as NodeData)?.data as unknown as NodeData)
      )) || {};

    if (!flag) return;

    const currentNode = findNode(node.id);

    (currentNode!.data as NodeData).data = data;
  });

  onEdgeDoubleClick(async ({ edge }) => {
    if (!validateHasLabelConnection(edge.sourceNode.data)) return;

    const { flag, data } =
      (await unref(updateEdgeDrawerActionType)?.open(
        toRaw(unref(edge.sourceNode?.data as unknown as NodeData)),
        toRaw(unref(edge.data as EdgeData))
      )) || {};

    if (!flag) return;

    const currentEdge = findEdge(edge.id);

    (currentEdge!.data as EdgeData).data = toRaw(unref(data));
  });

  onNodeDragStop(() => {
    triggerChange();
  });

  /**
   * @description 验证是否有连接label
   * @param sourceData
   * @returns
   */
  function validateHasLabelConnection(sourceData: NodeData) {
    return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length;
  }

  function handleMaxConnectionPoint(sourceNode?: GraphNode) {
    if (!sourceNode) return;

    const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint;

    if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return;

    const sourceId = sourceNode.id;
    const connectionPool = unref(getEdges).filter((item) => item.source === sourceId);
    if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) {
      removeEdges(connectionPool[0].id);
    }
  }

  return { flowActionType };
}