useContextMenuAction.ts 6.58 KB
import { GraphEdge, GraphNode, VueFlowStore } from '@vue-flow/core';
import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste';
import { RuleContextMenuEnum } from './useRuleChainContextMenu';
import { useAddNodes } from './useAddNodes';
import { buildUUID } from '/@/utils/uuid';
import { toRaw, unref } from 'vue';
import { useSaveAndRedo } from './useSaveAndRedo';
import { isUnDef } from '/@/utils/is';
import { useAddEdges } from './useAddEdges';
import { EdgeData } from '../types/node';

interface HandleContextMenuActionParamsType {
  menuType: RuleContextMenuEnum;
  event?: Event;
  flowActionType?: VueFlowStore;
  node?: GraphNode;
  edge?: GraphEdge;
  useSaveAndRedoActionType?: ReturnType<typeof useSaveAndRedo>;
}

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

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

  for (const node of 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 } = node;
    const { getAddNodesParams } = useAddNodes();
    const { x, y } = position;

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

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

  const paste = (params: HandleContextMenuActionParamsType) => {
    const { event, flowActionType, useSaveAndRedoActionType } = params;
    const { triggerChange } = useSaveAndRedoActionType || {};
    const { getAddNodesParams } = useAddNodes();
    const { getAddedgesParams } = useAddEdges();
    const clientX = (event as MouseEvent).offsetX;
    const clientY = (event as MouseEvent).offsetY;

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

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

      const newX = clientX - originX! + x + NODE_WIDTH / 2;
      const newY = clientY - originY! + y + NODE_HEIGHT / 2;

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

    const newEdges = edges.map((edge) => getAddedgesParams(edge, edge.data));

    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: oldId } = node;
      const newId = buildUUID();

      for (const connection of edges || []) {
        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(node.position, toRaw(unref(node.data)), { id: newId });
    });

    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 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,
    };

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

  return {
    handleContextMenuAction,
  };
}