feat(ui): duplicate workflow node

This commit is contained in:
Fu Diwei
2025-06-02 23:06:18 +08:00
parent d3e8bacd58
commit 025e606db4
10 changed files with 223 additions and 77 deletions

View File

@@ -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>

View File

@@ -14,7 +14,7 @@ const UnknownNode = ({ node, disabled }: MonitorNodeProps) => {
const { removeNode } = useWorkflowStore(useZustandShallowSelector(["removeNode"]));
const handleClickRemove = () => {
removeNode(node.id);
removeNode(node);
};
return (

View File

@@ -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"];