add execute result branch

This commit is contained in:
yoan
2025-01-19 17:01:02 +08:00
parent 69d4b3f93d
commit e6e964aa8c
13 changed files with 336 additions and 47 deletions

View File

@@ -7,6 +7,8 @@ import BranchNode from "./node/BranchNode";
import ConditionNode from "./node/ConditionNode";
import DeployNode from "./node/DeployNode";
import EndNode from "./node/EndNode";
import ExecuteResultBranchNode from "./node/ExecuteResultBranchNode";
import ExecuteResultNode from "./node/ExecuteResultNode";
import NotifyNode from "./node/NotifyNode";
import StartNode from "./node/StartNode";
@@ -35,6 +37,13 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem
case WorkflowNodeType.Branch:
return <BranchNode node={node} disabled={disabled} />;
case WorkflowNodeType.ExecuteResultBranch:
return <ExecuteResultBranchNode node={node} disabled={disabled} />;
case WorkflowNodeType.ExecuteSuccess:
case WorkflowNodeType.ExecuteFailure:
return <ExecuteResultNode node={node} disabled={disabled} branchId={branchId!} branchIndex={branchIndex!} />;
case WorkflowNodeType.Condition:
return <ConditionNode node={node} disabled={disabled} branchId={branchId!} branchIndex={branchIndex!} />;

View File

@@ -27,20 +27,29 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
[WorkflowNodeType.Apply, "workflow_node.apply.label", <SolutionOutlinedIcon />],
[WorkflowNodeType.Deploy, "workflow_node.deploy.label", <CloudUploadOutlinedIcon />],
[WorkflowNodeType.Branch, "workflow_node.branch.label", <SisternodeOutlinedIcon />],
[WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", <SisternodeOutlinedIcon />],
[WorkflowNodeType.Notify, "workflow_node.notify.label", <SendOutlinedIcon />],
].map(([type, label, icon]) => {
return {
key: type as string,
disabled: disabled,
label: t(label as string),
icon: icon,
onClick: () => {
const nextNode = newNode(type as WorkflowNodeType);
addNode(nextNode, node.id);
},
};
});
}, [node.id, disabled]);
]
.filter(([type]) => {
if (node.type !== WorkflowNodeType.Apply && node.type !== WorkflowNodeType.Deploy && type === WorkflowNodeType.ExecuteResultBranch) {
return false;
}
return true;
})
.map(([type, label, icon]) => {
return {
key: type as string,
disabled: disabled,
label: t(label as string),
icon: icon,
onClick: () => {
const nextNode = newNode(type as WorkflowNodeType);
addNode(nextNode, node.id);
},
};
});
}, [node.id, disabled, node.type]);
return (
<div className="relative py-6 before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']">

View File

@@ -0,0 +1,84 @@
import { memo } from "react";
import { theme } from "antd";
import { type WorkflowNode } from "@/domain/workflow";
import AddNode from "./AddNode";
import WorkflowElement from "../WorkflowElement";
import { type SharedNodeProps } from "./_SharedNode";
const { useToken } = theme;
export type BrandNodeProps = SharedNodeProps;
const ExecuteResultBranchNode = ({ node, disabled }: BrandNodeProps) => {
const token = useToken();
const renderBranch = (node: WorkflowNode, branchNodeId?: string, branchIndex?: number) => {
const elements: JSX.Element[] = [];
let current = node as typeof node | undefined;
while (current) {
elements.push(<WorkflowElement key={current.id} node={current} disabled={disabled} branchId={branchNodeId} branchIndex={branchIndex} />);
current = current.next;
}
return elements;
};
return (
<>
<div
className="relative flex gap-x-16 before:absolute before:inset-x-[128px] before:top-0 before:h-[2px] before:bg-stone-200 before:content-[''] after:absolute after:inset-x-[128px] after:bottom-0 after:h-[2px] after:bg-stone-200 after:content-['']"
style={{
backgroundColor: token.token.colorBgContainer,
}}
>
{node.branches?.map((branch, index) => (
<div
key={branch.id}
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
>
{index == 0 && (
<>
<div
className="absolute -left-px -top-1 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -left-px z-50 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
</>
)}
{node.branches && index == node.branches.length - 1 && (
<>
<div
className="absolute -right-px -top-1 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -right-px z-50 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
</>
)}
<div className="relative flex flex-col items-center">{renderBranch(branch, node.id, index)}</div>
</div>
))}
</div>
<AddNode node={node} disabled={disabled} />
</>
);
};
export default memo(ExecuteResultBranchNode);

View File

@@ -0,0 +1,60 @@
import { memo } from "react";
import { useTranslation } from "react-i18next";
import { MoreOutlined as MoreOutlinedIcon } from "@ant-design/icons";
import { Button, Card, Popover } from "antd";
import { CheckCircleIcon, XCircleIcon } from "lucide-react";
import { WorkflowNodeType } from "@/domain/workflow";
import AddNode from "./AddNode";
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
export type ConditionNodeProps = SharedNodeProps & {
branchId: string;
branchIndex: number;
};
const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeProps) => {
const { t } = useTranslation();
return (
<>
<Popover
arrow={false}
content={
<SharedNode.Menu
node={node}
branchId={branchId}
branchIndex={branchIndex}
disabled={disabled}
trigger={<Button color="primary" icon={<MoreOutlinedIcon />} variant="text" />}
/>
}
overlayClassName="shadow-md"
overlayInnerStyle={{ padding: 0 }}
placement="rightTop"
>
<Card className="relative z-[1] mt-10 w-[256px] shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<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 ? (
<>
<CheckCircleIcon size={18} className="text-green-500" />
<div>{t("workflow_node.execute_success.label")}</div>
</>
) : (
<>
<XCircleIcon size={18} className="text-red-500" />
<div>{t("workflow_node.execute_failure.label")}</div>
</>
)}
</div>
</div>
</Card>
</Popover>
<AddNode node={node} disabled={disabled} />
</>
);
};
export default memo(ExecuteResultNode);

View File

@@ -90,7 +90,13 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
};
const handleDeleteClick = async () => {
if (node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition) {
if (
node.type === WorkflowNodeType.Branch ||
node.type === WorkflowNodeType.Condition ||
node.type === WorkflowNodeType.ExecuteResultBranch ||
node.type === WorkflowNodeType.ExecuteSuccess ||
node.type === WorkflowNodeType.ExecuteFailure
) {
await removeBranch(branchId!, branchIndex!);
} else {
await removeNode(node.id);
@@ -148,7 +154,11 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
key: "remove",
disabled: disabled || node.type === WorkflowNodeType.Start,
label:
node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition
node.type === WorkflowNodeType.Branch ||
node.type === WorkflowNodeType.Condition ||
node.type === WorkflowNodeType.ExecuteResultBranch ||
node.type === WorkflowNodeType.ExecuteSuccess ||
node.type === WorkflowNodeType.ExecuteFailure
? t("workflow_node.action.remove_branch")
: t("workflow_node.action.remove_node"),
icon: <CloseCircleOutlinedIcon />,