mirror of https://github.com/usual2970/certimate
				
				
				
			
						commit
						cc5098c4bc
					
				| 
						 | 
				
			
			@ -22,7 +22,6 @@ func NewExecuteFailureNode(node *domain.WorkflowNode) *executeFailureNode {
 | 
			
		|||
 | 
			
		||||
func (n *executeFailureNode) Process(ctx context.Context) error {
 | 
			
		||||
	// 此类型节点不需要执行任何操作,直接返回
 | 
			
		||||
	n.logger.Info("the previous node execution was failed")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,6 @@ func NewExecuteSuccessNode(node *domain.WorkflowNode) *executeSuccessNode {
 | 
			
		|||
 | 
			
		||||
func (n *executeSuccessNode) Process(ctx context.Context) error {
 | 
			
		||||
	// 此类型节点不需要执行任何操作,直接返回
 | 
			
		||||
	n.logger.Info("the previous node execution was succeeded")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ func NewStartNode(node *domain.WorkflowNode) *startNode {
 | 
			
		|||
 | 
			
		||||
func (n *startNode) Process(ctx context.Context) error {
 | 
			
		||||
	// 此类型节点不需要执行任何操作,直接返回
 | 
			
		||||
	n.logger.Info("ready to start ...")
 | 
			
		||||
	n.logger.Info("workflow is started")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
import { memo } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import {
 | 
			
		||||
  CheckCircleOutlined as CheckCircleOutlinedIcon,
 | 
			
		||||
  CloseCircleOutlined as CloseCircleOutlinedIcon,
 | 
			
		||||
| 
						 | 
				
			
			@ -17,8 +16,6 @@ export type ConditionNodeProps = SharedNodeProps & {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { token: themeToken } = theme.useToken();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			@ -42,16 +39,15 @@ const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionN
 | 
			
		|||
          <div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
 | 
			
		||||
            <div className="flex items-center space-x-2">
 | 
			
		||||
              {node.type === WorkflowNodeType.ExecuteSuccess ? (
 | 
			
		||||
                <>
 | 
			
		||||
                  <CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />
 | 
			
		||||
                  <div>{t("workflow_node.execute_success.label")}</div>
 | 
			
		||||
                </>
 | 
			
		||||
                <CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <>
 | 
			
		||||
                  <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
 | 
			
		||||
                  <div>{t("workflow_node.execute_failure.label")}</div>
 | 
			
		||||
                </>
 | 
			
		||||
                <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
 | 
			
		||||
              )}
 | 
			
		||||
              <SharedNode.Title
 | 
			
		||||
                className="focus:bg-background focus:text-foreground overflow-hidden outline-slate-200 focus:rounded-sm"
 | 
			
		||||
                node={node}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Card>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ const UnknownNode = ({ node, disabled }: MonitorNodeProps) => {
 | 
			
		|||
  const { removeNode } = useWorkflowStore(useZustandShallowSelector(["removeNode"]));
 | 
			
		||||
 | 
			
		||||
  const handleClickRemove = () => {
 | 
			
		||||
    removeNode(node.id);
 | 
			
		||||
    removeNode(node);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import {
 | 
			
		|||
  EllipsisOutlined as EllipsisOutlinedIcon,
 | 
			
		||||
  FormOutlined as FormOutlinedIcon,
 | 
			
		||||
  MoreOutlined as MoreOutlinedIcon,
 | 
			
		||||
  SnippetsOutlined as SnippetsOutlinedIcon,
 | 
			
		||||
} from "@ant-design/icons";
 | 
			
		||||
import { useControllableValue } from "ahooks";
 | 
			
		||||
import { Button, Card, Drawer, Dropdown, Input, type InputRef, type MenuProps, Modal, Popover, Space } from "antd";
 | 
			
		||||
| 
						 | 
				
			
			@ -82,14 +83,27 @@ const isNodeBranchLike = (node: WorkflowNode) => {
 | 
			
		|||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isNodeReadOnly = (node: WorkflowNode) => {
 | 
			
		||||
const isNodeUnduplicatable = (node: WorkflowNode) => {
 | 
			
		||||
  return (
 | 
			
		||||
    node.type === WorkflowNodeType.Start ||
 | 
			
		||||
    node.type === WorkflowNodeType.End ||
 | 
			
		||||
    node.type === WorkflowNodeType.Branch ||
 | 
			
		||||
    node.type === WorkflowNodeType.ExecuteResultBranch ||
 | 
			
		||||
    node.type === WorkflowNodeType.ExecuteSuccess ||
 | 
			
		||||
    node.type === WorkflowNodeType.ExecuteFailure
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isNodeUnremovable = (node: WorkflowNode) => {
 | 
			
		||||
  return node.type === WorkflowNodeType.Start || node.type === WorkflowNodeType.End;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex, afterUpdate, afterDelete }: SharedNodeMenuProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { updateNode, removeNode, removeBranch } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeNode", "removeBranch"]));
 | 
			
		||||
  const { duplicateNode, updateNode, removeNode, duplicateBranch, removeBranch } = useWorkflowStore(
 | 
			
		||||
    useZustandShallowSelector(["duplicateNode", "updateNode", "removeNode", "duplicateBranch", "removeBranch"])
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const [modalApi, ModelContextHolder] = Modal.useModal();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -112,11 +126,19 @@ const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex,
 | 
			
		|||
    afterUpdate?.();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleDeleteClick = async () => {
 | 
			
		||||
  const handleDuplicateClick = async () => {
 | 
			
		||||
    if (isNodeBranchLike(node)) {
 | 
			
		||||
      await duplicateBranch(branchId!, branchIndex!);
 | 
			
		||||
    } else {
 | 
			
		||||
      await duplicateNode(node);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleRemoveClick = async () => {
 | 
			
		||||
    if (isNodeBranchLike(node)) {
 | 
			
		||||
      await removeBranch(branchId!, branchIndex!);
 | 
			
		||||
    } else {
 | 
			
		||||
      await removeNode(node.id);
 | 
			
		||||
      await removeNode(node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    afterDelete?.();
 | 
			
		||||
| 
						 | 
				
			
			@ -155,16 +177,23 @@ const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex,
 | 
			
		|||
          setTimeout(() => nameInputRef.current?.focus(), 1);
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        key: "duplicate",
 | 
			
		||||
        disabled: disabled || isNodeUnduplicatable(node),
 | 
			
		||||
        label: isNodeBranchLike(node) ? t("workflow_node.action.duplicate_branch") : t("workflow_node.action.duplicate_node"),
 | 
			
		||||
        icon: <SnippetsOutlinedIcon />,
 | 
			
		||||
        onClick: handleDuplicateClick,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: "divider",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        key: "remove",
 | 
			
		||||
        disabled: disabled || isNodeReadOnly(node),
 | 
			
		||||
        disabled: disabled || isNodeUnremovable(node),
 | 
			
		||||
        label: isNodeBranchLike(node) ? t("workflow_node.action.remove_branch") : t("workflow_node.action.remove_node"),
 | 
			
		||||
        icon: <CloseCircleOutlinedIcon />,
 | 
			
		||||
        danger: true,
 | 
			
		||||
        onClick: handleDeleteClick,
 | 
			
		||||
        onClick: handleRemoveClick,
 | 
			
		||||
      },
 | 
			
		||||
    ] satisfies MenuProps["items"];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import dayjs from "dayjs";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
import { Immer, produce } from "immer";
 | 
			
		||||
import { nanoid } from "nanoid";
 | 
			
		||||
 | 
			
		||||
import i18n from "@/i18n";
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +234,7 @@ export type NotExpr = { type: ExprType.Not; expr: Expr };
 | 
			
		|||
export type Expr = ConstantExpr | VariantExpr | ComparisonExpr | LogicalExpr | NotExpr;
 | 
			
		||||
// #endregion
 | 
			
		||||
 | 
			
		||||
const isBranchLike = (node: WorkflowNode) => {
 | 
			
		||||
const isBranchNode = (node: WorkflowNode) => {
 | 
			
		||||
  return node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.ExecuteResultBranch;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -458,8 +458,75 @@ export const newNode = (nodeType: WorkflowNodeType, options: NewNodeOptions = {}
 | 
			
		|||
  return node;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
 | 
			
		||||
  return produce(node, (draft) => {
 | 
			
		||||
export const cloneNode = (sourceNode: WorkflowNode): WorkflowNode => {
 | 
			
		||||
  const { produce } = new Immer({ autoFreeze: false });
 | 
			
		||||
  const deepClone = (node: WorkflowNode): WorkflowNode => {
 | 
			
		||||
    return produce(node, (draft) => {
 | 
			
		||||
      draft.id = nanoid();
 | 
			
		||||
 | 
			
		||||
      if (draft.next) {
 | 
			
		||||
        draft.next = cloneNode(draft.next);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (draft.branches) {
 | 
			
		||||
        draft.branches = draft.branches.map((branch) => cloneNode(branch));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return draft;
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const copyNode = produce(sourceNode, (draft) => {
 | 
			
		||||
    draft.name = `${draft.name}-copy`;
 | 
			
		||||
  });
 | 
			
		||||
  return deepClone(copyNode);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addNode = (root: WorkflowNode, targetNode: WorkflowNode, previousNodeId: string) => {
 | 
			
		||||
  if (isBranchNode(targetNode)) {
 | 
			
		||||
    throw new Error("Cannot add a branch node directly. Use `addBranch` instead.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    while (current) {
 | 
			
		||||
      if (current.id === previousNodeId && !isBranchNode(targetNode)) {
 | 
			
		||||
        targetNode.next = current.next;
 | 
			
		||||
        current.next = targetNode;
 | 
			
		||||
        break;
 | 
			
		||||
      } else if (current.id === previousNodeId && isBranchNode(targetNode)) {
 | 
			
		||||
        targetNode.branches![0].next = current.next;
 | 
			
		||||
        current.next = targetNode;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => addNode(branch, targetNode, previousNodeId));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current = current.next as WorkflowNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return draft;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const duplicateNode = (root: WorkflowNode, targetNode: WorkflowNode) => {
 | 
			
		||||
  if (isBranchNode(targetNode)) {
 | 
			
		||||
    throw new Error("Cannot duplicate a branch node directly. Use `duplicateBranch` instead.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const copiedNode = cloneNode(targetNode);
 | 
			
		||||
  return addNode(root, copiedNode, targetNode.id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const updateNode = (root: WorkflowNode, targetNode: WorkflowNode) => {
 | 
			
		||||
  if (isBranchNode(targetNode)) {
 | 
			
		||||
    throw new Error("Cannot update a branch node directly. Use `updateBranch` instead.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    while (current) {
 | 
			
		||||
      if (current.id === targetNode.id) {
 | 
			
		||||
| 
						 | 
				
			
			@ -476,7 +543,7 @@ export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
 | 
			
		|||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchLike(current)) {
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => updateNode(branch, targetNode));
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -488,23 +555,18 @@ export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
 | 
			
		|||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addNode = (node: WorkflowNode, previousNodeId: string, targetNode: WorkflowNode) => {
 | 
			
		||||
  return produce(node, (draft) => {
 | 
			
		||||
export const removeNode = (root: WorkflowNode, targetNodeId: string) => {
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    while (current) {
 | 
			
		||||
      if (current.id === previousNodeId && !isBranchLike(targetNode)) {
 | 
			
		||||
        targetNode.next = current.next;
 | 
			
		||||
        current.next = targetNode;
 | 
			
		||||
        break;
 | 
			
		||||
      } else if (current.id === previousNodeId && isBranchLike(targetNode)) {
 | 
			
		||||
        targetNode.branches![0].next = current.next;
 | 
			
		||||
        current.next = targetNode;
 | 
			
		||||
      if (current.next?.id === targetNodeId) {
 | 
			
		||||
        current.next = current.next.next;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchLike(current)) {
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => addNode(branch, previousNodeId, targetNode));
 | 
			
		||||
        current.branches = current.branches.map((branch) => removeNode(branch, targetNodeId));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current = current.next as WorkflowNode;
 | 
			
		||||
| 
						 | 
				
			
			@ -514,8 +576,8 @@ export const addNode = (node: WorkflowNode, previousNodeId: string, targetNode:
 | 
			
		|||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addBranch = (node: WorkflowNode, branchNodeId: string) => {
 | 
			
		||||
  return produce(node, (draft) => {
 | 
			
		||||
export const addBranch = (root: WorkflowNode, branchNodeId: string) => {
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    while (current) {
 | 
			
		||||
      if (current.id === branchNodeId) {
 | 
			
		||||
| 
						 | 
				
			
			@ -532,7 +594,7 @@ export const addBranch = (node: WorkflowNode, branchNodeId: string) => {
 | 
			
		|||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchLike(current)) {
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => addBranch(branch, branchNodeId));
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -544,29 +606,8 @@ export const addBranch = (node: WorkflowNode, branchNodeId: string) => {
 | 
			
		|||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeNode = (node: WorkflowNode, targetNodeId: string) => {
 | 
			
		||||
  return produce(node, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    while (current) {
 | 
			
		||||
      if (current.next?.id === targetNodeId) {
 | 
			
		||||
        current.next = current.next.next;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchLike(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => removeNode(branch, targetNodeId));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current = current.next as WorkflowNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return draft;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchIndex: number) => {
 | 
			
		||||
  return produce(node, (draft) => {
 | 
			
		||||
export const duplicateBranch = (root: WorkflowNode, branchNodeId: string, branchIndex: number) => {
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    let last: WorkflowNode | undefined = {
 | 
			
		||||
      id: "",
 | 
			
		||||
| 
						 | 
				
			
			@ -576,7 +617,41 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
 | 
			
		|||
    };
 | 
			
		||||
    while (current && last) {
 | 
			
		||||
      if (current.id === branchNodeId) {
 | 
			
		||||
        if (!isBranchLike(current)) {
 | 
			
		||||
        if (!isBranchNode(current)) {
 | 
			
		||||
          return draft;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches.splice(branchIndex + 1, 0, cloneNode(current.branches[branchIndex]));
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => duplicateBranch(branch, branchNodeId, branchIndex));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current = current.next as WorkflowNode;
 | 
			
		||||
      last = last.next;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return draft;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeBranch = (root: WorkflowNode, branchNodeId: string, branchIndex: number) => {
 | 
			
		||||
  return produce(root, (draft) => {
 | 
			
		||||
    let current = draft;
 | 
			
		||||
    let last: WorkflowNode | undefined = {
 | 
			
		||||
      id: "",
 | 
			
		||||
      name: "",
 | 
			
		||||
      type: WorkflowNodeType.Start,
 | 
			
		||||
      next: draft,
 | 
			
		||||
    };
 | 
			
		||||
    while (current && last) {
 | 
			
		||||
      if (current.id === branchNodeId) {
 | 
			
		||||
        if (!isBranchNode(current)) {
 | 
			
		||||
          return draft;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -601,7 +676,7 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
 | 
			
		|||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isBranchLike(current)) {
 | 
			
		||||
      if (isBranchNode(current)) {
 | 
			
		||||
        current.branches ??= [];
 | 
			
		||||
        current.branches = current.branches.map((branch) => removeBranch(branch, branchNodeId, branchIndex));
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -647,7 +722,7 @@ export const getOutputBeforeNodeId = (root: WorkflowNode, nodeId: string, typeFi
 | 
			
		|||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isBranchLike(current)) {
 | 
			
		||||
    if (isBranchNode(current)) {
 | 
			
		||||
      let currentLength = output.length;
 | 
			
		||||
      const latestOutput = output.length > 0 ? output[output.length - 1] : null;
 | 
			
		||||
      for (const branch of current.branches!) {
 | 
			
		||||
| 
						 | 
				
			
			@ -679,7 +754,7 @@ export const getOutputBeforeNodeId = (root: WorkflowNode, nodeId: string, typeFi
 | 
			
		|||
export const isAllNodesValidated = (node: WorkflowNode): boolean => {
 | 
			
		||||
  let current = node as typeof node | undefined;
 | 
			
		||||
  while (current) {
 | 
			
		||||
    if (isBranchLike(current)) {
 | 
			
		||||
    if (isBranchNode(current)) {
 | 
			
		||||
      for (const branch of current.branches!) {
 | 
			
		||||
        if (!isAllNodesValidated(branch)) {
 | 
			
		||||
          return false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,11 @@
 | 
			
		|||
  "workflow_node.action.configure_node": "Configure node",
 | 
			
		||||
  "workflow_node.action.add_node": "Add node",
 | 
			
		||||
  "workflow_node.action.rename_node": "Rename node",
 | 
			
		||||
  "workflow_node.action.duplicate_node": "Duplicate node",
 | 
			
		||||
  "workflow_node.action.remove_node": "Delete node",
 | 
			
		||||
  "workflow_node.action.add_branch": "Add branch",
 | 
			
		||||
  "workflow_node.action.rename_branch": "Rename branch",
 | 
			
		||||
  "workflow_node.action.duplicate_branch": "Duplicate branch",
 | 
			
		||||
  "workflow_node.action.remove_branch": "Delete branch",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.unsaved_changes.confirm": "You have unsaved changes. Do you really want to close the panel and drop those changes?",
 | 
			
		||||
| 
						 | 
				
			
			@ -901,11 +903,11 @@
 | 
			
		|||
  "workflow_node.condition.form.expression.add_condition.button": "Add condition",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_result_branch.label": "Execution result branch",
 | 
			
		||||
  "workflow_node.execute_result_branch.default_name": "Execution result branch",
 | 
			
		||||
  "workflow_node.execute_result_branch.default_name": "Branch",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_success.label": "If the previous node succeeded ...",
 | 
			
		||||
  "workflow_node.execute_success.default_name": "If the previous node succeeded ...",
 | 
			
		||||
  "workflow_node.execute_success.default_name": "On Succeeded",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_failure.label": "If the previous node failed ...",
 | 
			
		||||
  "workflow_node.execute_failure.default_name": "If the previous node failed ..."
 | 
			
		||||
  "workflow_node.execute_failure.default_name": "On Failed"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,11 @@
 | 
			
		|||
  "workflow_node.action.configure_node": "配置节点",
 | 
			
		||||
  "workflow_node.branch.add_node": "添加节点",
 | 
			
		||||
  "workflow_node.action.rename_node": "重命名",
 | 
			
		||||
  "workflow_node.action.duplicate_node": "复制节点",
 | 
			
		||||
  "workflow_node.action.remove_node": "删除节点",
 | 
			
		||||
  "workflow_node.action.add_branch": "添加分支",
 | 
			
		||||
  "workflow_node.action.rename_branch": "重命名",
 | 
			
		||||
  "workflow_node.action.duplicate_branch": "复制分支",
 | 
			
		||||
  "workflow_node.action.remove_branch": "删除分支",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.unsaved_changes.confirm": "你有尚未保存的更改。确定要关闭面板吗?",
 | 
			
		||||
| 
						 | 
				
			
			@ -900,11 +902,11 @@
 | 
			
		|||
  "workflow_node.condition.form.expression.add_condition.button": "添加条件",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_result_branch.label": "执行结果分支",
 | 
			
		||||
  "workflow_node.execute_result_branch.default_name": "执行结果分支",
 | 
			
		||||
  "workflow_node.execute_result_branch.default_name": "分支",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_success.label": "若上一节点执行成功…",
 | 
			
		||||
  "workflow_node.execute_success.default_name": "若上一节点执行成功…",
 | 
			
		||||
  "workflow_node.execute_success.default_name": "执行成功",
 | 
			
		||||
 | 
			
		||||
  "workflow_node.execute_failure.label": "若上一节点执行失败…",
 | 
			
		||||
  "workflow_node.execute_failure.default_name": "若上一节点执行失败…"
 | 
			
		||||
  "workflow_node.execute_failure.default_name": "执行失败"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,8 @@ import {
 | 
			
		|||
  type WorkflowNodeConfigForStart,
 | 
			
		||||
  addBranch,
 | 
			
		||||
  addNode,
 | 
			
		||||
  duplicateBranch,
 | 
			
		||||
  duplicateNode,
 | 
			
		||||
  getOutputBeforeNodeId,
 | 
			
		||||
  removeBranch,
 | 
			
		||||
  removeNode,
 | 
			
		||||
| 
						 | 
				
			
			@ -26,10 +28,12 @@ export type WorkflowState = {
 | 
			
		|||
  destroy(): void;
 | 
			
		||||
 | 
			
		||||
  addNode: (node: WorkflowNode, previousNodeId: string) => void;
 | 
			
		||||
  duplicateNode: (node: WorkflowNode) => void;
 | 
			
		||||
  updateNode: (node: WorkflowNode) => void;
 | 
			
		||||
  removeNode: (nodeId: string) => void;
 | 
			
		||||
  removeNode: (node: WorkflowNode) => void;
 | 
			
		||||
 | 
			
		||||
  addBranch: (branchId: string) => void;
 | 
			
		||||
  duplicateBranch: (branchId: string, index: number) => void;
 | 
			
		||||
  removeBranch: (branchId: string, index: number) => void;
 | 
			
		||||
 | 
			
		||||
  getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => WorkflowNode[];
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +150,27 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
 | 
			
		|||
  addNode: async (node: WorkflowNode, previousNodeId: string) => {
 | 
			
		||||
    if (!get().initialized) throw "Workflow not initialized yet";
 | 
			
		||||
 | 
			
		||||
    const root = addNode(get().workflow.draft!, previousNodeId, node);
 | 
			
		||||
    const root = addNode(get().workflow.draft!, node, previousNodeId);
 | 
			
		||||
    const resp = await saveWorkflow({
 | 
			
		||||
      id: get().workflow.id!,
 | 
			
		||||
      draft: root,
 | 
			
		||||
      hasDraft: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    set((state: WorkflowState) => {
 | 
			
		||||
      return {
 | 
			
		||||
        workflow: produce(state.workflow, (draft) => {
 | 
			
		||||
          draft.draft = resp.draft;
 | 
			
		||||
          draft.hasDraft = resp.hasDraft;
 | 
			
		||||
        }),
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  duplicateNode: async (node: WorkflowNode) => {
 | 
			
		||||
    if (!get().initialized) throw "Workflow not initialized yet";
 | 
			
		||||
 | 
			
		||||
    const root = duplicateNode(get().workflow.draft!, node);
 | 
			
		||||
    const resp = await saveWorkflow({
 | 
			
		||||
      id: get().workflow.id!,
 | 
			
		||||
      draft: root,
 | 
			
		||||
| 
						 | 
				
			
			@ -183,10 +207,10 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
 | 
			
		|||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  removeNode: async (nodeId: string) => {
 | 
			
		||||
  removeNode: async (node: WorkflowNode) => {
 | 
			
		||||
    if (!get().initialized) throw "Workflow not initialized yet";
 | 
			
		||||
 | 
			
		||||
    const root = removeNode(get().workflow.draft!, nodeId);
 | 
			
		||||
    const root = removeNode(get().workflow.draft!, node.id);
 | 
			
		||||
    const resp = await saveWorkflow({
 | 
			
		||||
      id: get().workflow.id!,
 | 
			
		||||
      draft: root,
 | 
			
		||||
| 
						 | 
				
			
			@ -223,6 +247,26 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
 | 
			
		|||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  duplicateBranch: async (branchId: string, index: number) => {
 | 
			
		||||
    if (!get().initialized) throw "Workflow not initialized yet";
 | 
			
		||||
 | 
			
		||||
    const root = duplicateBranch(get().workflow.draft!, branchId, index);
 | 
			
		||||
    const resp = await saveWorkflow({
 | 
			
		||||
      id: get().workflow.id!,
 | 
			
		||||
      draft: root,
 | 
			
		||||
      hasDraft: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    set((state: WorkflowState) => {
 | 
			
		||||
      return {
 | 
			
		||||
        workflow: produce(state.workflow, (draft) => {
 | 
			
		||||
          draft.draft = resp.draft;
 | 
			
		||||
          draft.hasDraft = resp.hasDraft;
 | 
			
		||||
        }),
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  removeBranch: async (branchId: string, index: number) => {
 | 
			
		||||
    if (!get().initialized) throw "Workflow not initialized yet";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue