useSaveAndRedo.ts 6.8 KB
import type { VueFlowStore, Getters, Elements } from '@vue-flow/core';
import { ComputedRef, computed, ref, unref } from 'vue';
import { BasicNodeBindData, EdgeData, NodeData } from '../types/node';
import { EntryCategoryComponentEnum } from '../enum/category';
import { useBasicDataTransform } from './useBasicDataTransform';
import {
  getRuleChainData,
  getRuleChainDetail,
  saveRuleChainData,
  saveRuleChainDetail,
} from '/@/api/ruleDesigner';
import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode';
import { useInputNode } from './useInputNode';
import { buildUUID } from '/@/utils/uuid';
import { useRoute, useRouter } from 'vue-router';
import { RuleChainEntityType } from '../enum/entity';
import { PageEnum } from '/@/enums/pageEnum';
import { clearRuleChainImportCache, getRuleChainImportCache } from './useRuleChainCache';

const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT];

export function useSaveAndRedo() {
  const changeMarker = ref(false);

  const loading = ref(false);

  const redoDataRef = ref<Elements>([]);

  const ROUTE = useRoute();

  const ROUTER = useRouter();

  const debugMarker = ref(false);

  const getRuleChainId = computed(() => (ROUTE.params as Record<'id', string>).id);

  const getIsImportFlag = computed(() => ROUTE.fullPath === PageEnum.RULE_CHAIN_DETAIL_IMPORT);

  const ruleChainDetail = ref<RuleChainDetail>();

  const { mergeData, deconstructionData } = useBasicDataTransform();

  const triggerChange = () => {
    changeMarker.value = true;
  };

  const resetChange = () => {
    changeMarker.value = false;
  };

  /**
   * @description 保存连接信息
   */
  function getConnections(
    nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'],
    edges: ComputedRef<Getters['getEdges']>
  ) {
    const nodeIndexMap = new Map();

    const connections: ConnectionItemType[] = [];

    unref(nodesRef).forEach((item, index) => {
      nodeIndexMap.set(item.id, index);
    });

    for (const item of unref(edges)) {
      const { data, target, source } = item;
      const { data: bindData } = data as EdgeData;
      const { type } = bindData || {};
      const fromIndex = nodeIndexMap.get(source);
      const toIndex = nodeIndexMap.get(target);
      type?.forEach((key) => {
        connections.push({ fromIndex, toIndex, type: key });
      });
    }

    return connections;
  }

  function getNodes(nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes']) {
    const nodes: BasicNodeBindData[] = [];

    for (const node of unref(nodesRef)) {
      const nodeData = node.data as NodeData;

      if (ignoreNodeKeys.includes(nodeData.config?.key as string)) continue;

      const data = nodeData.data;

      nodes.push(
        Object.assign(
          mergeData(data, nodeData, node),
          nodeData.created
            ? ({
                id: { id: node.id, entityType: RuleChainEntityType.RULE_NODE },
              } as BasicNodeBindData)
            : {}
        )
      );
    }

    return nodes;
  }

  function getFirsetNodeIndex(
    nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'],
    edges: ComputedRef<Getters['getEdges']>
  ) {
    const inputNode = unref(edges).find(
      (item) => (item.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT
    );

    if (inputNode) {
      const targetId = inputNode.target;
      const index = unref(nodesRef).findIndex((item) => item.id === targetId);
      return index;
    }
  }

  const handleApplyChange = (flowActionType: VueFlowStore) => {
    if (!unref(changeMarker)) return;

    const edgesRef = flowActionType.getEdges;

    const extraIgnoreNodeRef = unref(flowActionType.getNodes).filter(
      (item) => !ignoreNodeKeys.includes((item.data as NodeData).config?.key as string)
    );

    const connections = getConnections(extraIgnoreNodeRef, edgesRef);

    const nodes = getNodes(extraIgnoreNodeRef);

    const firstNodeIndex = getFirsetNodeIndex(extraIgnoreNodeRef, edgesRef);

    handleSaveRuleChain(connections, nodes, firstNodeIndex);
  };

  const handleRedoChange = (flowActionType: VueFlowStore) => {
    if (!unref(changeMarker)) return;
    flowActionType.setElements(unref(redoDataRef));
    resetChange();
  };

  async function getCurrentRuleChainDetail() {
    ruleChainDetail.value = unref(getIsImportFlag)
      ? (getImportChainDetail() as RuleChainDetail)
      : await getRuleChainDetail(unref(getRuleChainId));
  }

  async function handleSaveRuleChain(
    connections: ConnectionItemType[],
    nodes: BasicNodeBindData[],
    firstNodeIndex?: number
  ) {
    try {
      loading.value = true;

      let ruleChainId = unref(getRuleChainId);
      if (unref(getIsImportFlag)) {
        const detail = await saveRuleChainDetail(unref(ruleChainDetail)!);
        ruleChainDetail.value = detail;
        ruleChainId = detail.id.id;
      }

      const data = await saveRuleChainData({
        connections,
        nodes,
        firstNodeIndex,
        ruleChainId: {
          entityType: RuleChainEntityType.RULE_CHAIN,
          id: ruleChainId,
        },
      });

      parseRuleChain(data);

      resetChange();

      if (unref(getIsImportFlag)) {
        clearRuleChainImportCache();

        ROUTER.replace({
          path: `/rule/chain/${ruleChainId}`,
          replace: true,
        });
      }
    } finally {
      loading.value = false;
    }
  }

  async function getCurrentPageMetaData(flowActionType: VueFlowStore) {
    try {
      loading.value = true;

      const data = unref(getIsImportFlag)
        ? await getImportMetadata()
        : await getRuleChainData(unref(getRuleChainId));
      if (!data) return;

      const elements = parseRuleChain(data);

      flowActionType.setElements(elements);

      unref(getIsImportFlag) ? triggerChange() : resetChange();
    } finally {
      loading.value = false;
    }
  }

  function parseRuleChain(ruleChain: RuleChainType) {
    const inputId = buildUUID();

    const { getInputNodeConfig } = useInputNode();

    const value = deconstructionData(ruleChain, inputId);

    const { nodes = [], edges = [] } = value || {};

    const inputNode = getInputNodeConfig(inputId);

    const elements = [inputNode, ...nodes, ...edges];

    redoDataRef.value = elements;

    return elements;
  }

  const handleRemoveDebug = (flowActionType: VueFlowStore) => {
    for (const item of unref(flowActionType.getNodes)) {
      (item.data as NodeData)!.data!.debugMode = false;
    }
    triggerChange();
  };

  const getImportMetadata = () => {
    return getRuleChainImportCache().metadata;
  };

  const getImportChainDetail = () => getRuleChainImportCache().ruleChain;

  return {
    loading,
    debugMarker,
    changeMarker,
    ruleChainDetail,
    triggerChange,
    handleApplyChange,
    handleRedoChange,
    handleRemoveDebug,
    getCurrentPageMetaData,
    getCurrentRuleChainDetail,
  };
}