useContextMenuAction.ts 10.4 KB
import { GraphEdge, GraphNode, VueFlowStore, pointToRendererPoint } from '@vue-flow/core';
import { getRuleNodeCache, setRuleNodeCache } from './useRuleChainCache';
import { RuleContextMenuEnum } from './useRuleChainContextMenu';
import { useAddNodes } from './useAddNodes';
import { buildUUID } from '/@/utils/uuid';
import { Ref, toRaw, unref } from 'vue';
import { useSaveAndRedo } from './useSaveAndRedo';
import { isUnDef } from '/@/utils/is';
import { useAddEdges } from './useAddEdges';
import { EdgeData } from '../types/node';
import { CreateNodeModal } from '../src/components/CreateNodeModal';
import { CreateEdgeModal } from '../src/components/CreateEdgeModal';
import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer';
import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer';
import { CreateRuleChainModal } from '../src/components/CreateRuleChainModal';
import { useBasicDataTransform } from './useBasicDataTransform';
import { cloneDeep } from 'lodash-es';
import { useNewNode } from './useNewNode';

interface HandleContextMenuActionParamsType {
  menuType: RuleContextMenuEnum;
  event?: Event;
  flowActionType?: VueFlowStore;
  node?: GraphNode;
  edge?: GraphEdge;
  useSaveAndRedoActionType?: ReturnType<typeof useSaveAndRedo>;
  modalActionType: {
    createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>;
    createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>;
    updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>;
    updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>;
    createRuleChainModalActionType: Ref<Nullable<InstanceType<typeof CreateRuleChainModal>>>;
  };
}

export function transformToRuleChain(
  nodesRef: Ref<GraphNode[]> | GraphNode[] = [],
  edgesRef: Ref<GraphEdge[]> | GraphEdge[] = []
) {
  const nodeMap = new Map<string, GraphNode>();
  const { combineData, validateCanCreateRuleChain } = useBasicDataTransform();

  nodesRef = cloneDeep(unref(nodesRef));
  edgesRef = cloneDeep(unref(edgesRef));

  unref(nodesRef).forEach((node) => nodeMap.set(node.id, node));
  const outputEdges = unref(edgesRef).filter((edge) => !nodeMap.has(edge.target));
  const outputEdgesId = outputEdges.map((edge) => edge.id);

  const { getOutputNodeConfig } = useNewNode();
  const outputNode = outputEdges.map((edge) => {
    const id = buildUUID();
    const name = (edge.data as EdgeData).data?.type?.join(' / ') || '';
    edge.target = id;
    return getOutputNodeConfig(name, edge.targetNode.position, id);
  });

  nodesRef = [...nodesRef, ...(outputNode as GraphNode[])];

  const { connections, nodes } = combineData(nodesRef, edgesRef, true);

  const { firstNode } = validateCanCreateRuleChain(nodesRef, edgesRef);

  const firstNodeIndex = nodesRef.findIndex((node) => node.id === firstNode.id);

  return { connections, nodes, firstNodeIndex, outputEdgesId };
}

export const NODE_WIDTH = 176;
export const NODE_HEIGHT = 48;

function getElementsCenter(nodes: Ref<GraphNode[]> | GraphNode[] = []) {
  let leftTopX: number | undefined;
  let leftTopY: number | undefined;
  let rightBottomX: number | undefined;
  let rightBottomY: number | undefined;

  for (const node of unref(nodes)) {
    const { position } = node;
    const { x, y } = position;
    if (isUnDef(leftTopX)) {
      leftTopX = x;
      leftTopY = y;
      rightBottomX = x + NODE_WIDTH;
      rightBottomY = y + NODE_HEIGHT;

      continue;
    }

    if (x < leftTopX!) {
      leftTopX = x;
      if (y < leftTopY!) {
        leftTopY = y;
      }
      continue;
    }

    if (x + NODE_WIDTH > rightBottomX!) {
      rightBottomX = x + NODE_WIDTH;
      if (y + NODE_HEIGHT > rightBottomY!) {
        rightBottomY = y + NODE_HEIGHT;
      }
    }
  }

  return {
    originX: (rightBottomX! - leftTopX!) / 2 + leftTopX!,
    originY: (rightBottomY! - leftTopY!) / 2 + leftTopY!,
  };
}

export function useContextMenuAction() {
  const copy = (params: HandleContextMenuActionParamsType) => {
    const { node } = params;
    if (!node) return;
    const { position, data, id } = node;
    const { getAddNodesParams } = useAddNodes();
    const { x, y } = position;

    const value = getAddNodesParams(position, data, { id });

    setRuleNodeCache({
      nodes: [value],
      originX: x + NODE_WIDTH / 2,
      originY: y + NODE_HEIGHT / 2,
    });
  };

  const paste = (params: HandleContextMenuActionParamsType) => {
    const { event, flowActionType, useSaveAndRedoActionType } = params;
    const { triggerChange } = useSaveAndRedoActionType || {};

    const { getAddNodesParams } = useAddNodes();
    const { getAddedgesParams } = useAddEdges();

    const { edges = [], nodes = [], originX, originY } = getRuleNodeCache();

    const { x: clientX, y: clientY } = pointToRendererPoint(
      { x: (event as MouseEvent).offsetX, y: (event as MouseEvent).offsetY },
      flowActionType!.getViewport(),
      false,
      [25, 25]
    );

    const xAxisMoveDistance = clientX - originX!;
    const yAxisMoveDistance = clientY - originY!;

    const newEdges = edges.map((edge) =>
      getAddedgesParams({ ...edge, id: buildUUID() }, edge.data)
    );

    const newNode = nodes.map((node) => {
      const { position, data, id: oldId } = node;
      const { x, y } = position;
      const newId = buildUUID();

      const newX = xAxisMoveDistance + x;
      const newY = yAxisMoveDistance + y;

      for (const connection of newEdges || []) {
        if (connection.source.includes(oldId)) {
          connection.source = newId;
          connection.sourceHandle = connection.sourceHandle?.replaceAll(oldId, newId);
          continue;
        }

        if (connection.target.includes(oldId)) {
          connection.target = newId;
          connection.targetHandle = connection.targetHandle?.replaceAll(oldId, newId);
        }
      }

      return getAddNodesParams({ x: newX, y: newY }, { ...data, created: false }, { id: newId });
    });

    flowActionType?.addNodes(newNode);
    flowActionType?.addEdges(newEdges);

    triggerChange?.();

    flowActionType?.removeSelectedElements();
  };

  const selectAll = (params: HandleContextMenuActionParamsType) => {
    const { flowActionType } = params;
    flowActionType?.addSelectedElements(unref(flowActionType.getElements));
  };

  const unselect = (params: HandleContextMenuActionParamsType) => {
    const { flowActionType } = params;
    flowActionType?.removeSelectedElements();
  };

  const deleteElement = (parmas: HandleContextMenuActionParamsType) => {
    const { useSaveAndRedoActionType, flowActionType, node, edge } = parmas;
    const { triggerChange } = useSaveAndRedoActionType || {};

    node && flowActionType?.removeNodes(node);
    edge && flowActionType?.removeEdges(edge);

    triggerChange?.();
  };

  const deleteElements = (params: HandleContextMenuActionParamsType) => {
    const { flowActionType, useSaveAndRedoActionType } = params;
    flowActionType?.removeNodes(unref(flowActionType.getSelectedNodes));

    useSaveAndRedoActionType?.triggerChange?.();
  };

  const selectCopy = (params: HandleContextMenuActionParamsType) => {
    const { flowActionType } = params;
    const { getAddNodesParams } = useAddNodes();
    const { getAddedgesParams } = useAddEdges();

    const edges = unref(flowActionType?.getSelectedEdges)?.map((edge) =>
      getAddedgesParams(
        {
          source: edge.source,
          target: edge.target,
          sourceHandle: edge.sourceHandle,
          targetHandle: edge.targetHandle,
        },
        toRaw(unref(edge.data as EdgeData)?.data)
      )
    );

    const nodes = unref(flowActionType?.getSelectedNodes)?.map((node) => {
      const { id } = node;

      return getAddNodesParams(node.position, toRaw(unref(node.data)), { id });
    });

    const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []);

    setRuleNodeCache({ nodes, edges, ...originRect });
  };

  const applyChange = (params: HandleContextMenuActionParamsType) => {
    const { useSaveAndRedoActionType, flowActionType } = params;

    useSaveAndRedoActionType?.handleApplyChange(flowActionType!);
  };

  const undoChange = (params: HandleContextMenuActionParamsType) => {
    const { useSaveAndRedoActionType, flowActionType } = params;

    useSaveAndRedoActionType?.handleRedoChange(flowActionType!);
  };

  const createRuleChain = async (_params: HandleContextMenuActionParamsType) => {
    // const { useSaveAndRedoActionType, modalActionType, flowActionType } = params;
    // const { createRuleChainModalActionType } = modalActionType;
    // const result = (await unref(createRuleChainModalActionType)?.openCreateRuleChainModal()) as {
    //   name: string;
    //   additionalInfo: { description: string };
    // };
    // const ruleChainDetail = await saveRuleChainDetail(
    //   Object.assign(result, { debugger: false, type: 'CORE' }) as Partial<RuleChainDetail>
    // );
    // const selectedNodes = unref(flowActionType?.getSelectedNodes);
    // const selectedEdges = unref(flowActionType?.getSelectedEdges);
    // const { firstNodeIndex, connections, nodes, outputEdgesId } = transformToRuleChain(
    //   selectedNodes,
    //   selectedEdges
    // );
    // await saveRuleChainData({
    //   firstNodeIndex,
    //   connections,
    //   nodes,
    //   ruleChainId: ruleChainDetail.id,
    // });
    // const outputEdges = outputEdgesId.map((id) => flowActionType?.findEdge(id));
    // console.log(getElementsCenter(unref(selectedNodes)));
    // const { originX, originY } = getElementsCenter(unref(selectedNodes));
    // const {} = useNewNode();
    // flowActionType?.removeNodes(selectedNodes || []);
    // flowActionType?.removeEdges(selectedEdges || []);
    // useSaveAndRedoActionType?.triggerChange();
  };

  const handleContextMenuAction = (params: HandleContextMenuActionParamsType) => {
    const { menuType } = params;

    const handlerMapping = {
      [RuleContextMenuEnum.COPY]: copy,
      [RuleContextMenuEnum.PASTE]: paste,
      [RuleContextMenuEnum.SELECT_ALL]: selectAll,
      [RuleContextMenuEnum.UNSELECTED]: unselect,
      [RuleContextMenuEnum.DELETE]: deleteElement,
      [RuleContextMenuEnum.DELETE_SELECT]: deleteElements,
      [RuleContextMenuEnum.SELECT_COPY]: selectCopy,
      [RuleContextMenuEnum.APPLY_CHANGE]: applyChange,
      [RuleContextMenuEnum.UNDO_CHANGE]: undoChange,
      [RuleContextMenuEnum.CREATE_RULE_CHAIN]: createRuleChain,
    };

    if (handlerMapping[menuType]) {
      handlerMapping[menuType]?.(params);
    }
  };

  return {
    handleContextMenuAction,
  };
}