feat: disable node

This commit is contained in:
Fu Diwei
2025-08-22 22:31:56 +08:00
parent 625e13ece3
commit 628aff9ddd
7 changed files with 122 additions and 12 deletions
+3 -2
View File
@@ -102,8 +102,9 @@ const (
)
type WorkflowNodeData struct {
Name string `json:"name"`
Config WorkflowNodeConfig `json:"config"`
Name string `json:"name"`
Disabled bool `json:"disabled,omitempty,omitzero"`
Config WorkflowNodeConfig `json:"config,omitempty,omitzero"`
}
type WorkflowNodeConfig map[string]any
+5
View File
@@ -195,6 +195,11 @@ func (we *workflowEngine) executeBlocks(wfCtx *WorkflowContext, blocks []*Node)
default:
}
// 节点已禁用,直接跳过执行
if node.Data.Disabled {
continue
}
err := we.executeNode(wfCtx, node)
if err != nil {
return err
@@ -1,11 +1,12 @@
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor";
import { IconX } from "@tabler/icons-react";
import { type FlowNodeEntity, getNodeForm, useClientContext, useRefresh, useWatchFormValueIn } from "@flowgram.ai/fixed-layout-editor";
import { IconEye, IconEyeOff, IconX } from "@tabler/icons-react";
import { useControllableValue } from "ahooks";
import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Typography } from "antd";
import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Tooltip, Typography } from "antd";
import { isEqual } from "radash";
import Show from "@/components/Show";
import { getErrMsg } from "@/utils/error";
import { type NodeRegistry } from "../nodes/typings";
@@ -25,6 +26,11 @@ export interface NodeConfigDrawerProps {
export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true, form: formInst, loading, node, ...props }: NodeConfigDrawerProps) => {
const { t } = useTranslation();
const ctx = useClientContext();
const { playground } = ctx;
const refresh = useRefresh();
const { modal, notification } = App.useApp();
const [open, setOpen] = useControllableValue<boolean>(props, {
@@ -52,6 +58,22 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
</div>
);
const [isNodeDisabled, setIsNodeDisabled] = useState(() => {
if (node) {
return getNodeForm(node)?.getValueIn<boolean>("disabled");
}
return false;
});
useEffect(() => {
const d1 = playground.config.onDataChange(() => refresh());
const d2 = node?.onDataChange(() => setIsNodeDisabled(getNodeForm(node)?.getValueIn<boolean>("disabled")));
return () => {
d1.dispose();
d2.dispose();
};
});
const handleOkClick = async () => {
if (node == null) {
setOpen(false);
@@ -127,6 +149,10 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
promise.then(() => setOpen(false));
};
const handleDisableNodeClick = () => {
getNodeForm(node)!.setValueIn("disabled", !isNodeDisabled);
};
return (
<Drawer
styles={{
@@ -160,21 +186,38 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
<Flex align="center" justify="space-between" gap="small">
<div>{renderNodeIcon()}</div>
<div className="flex-1 truncate">{node?.toJSON()?.data?.name}</div>
<Show when={!!node && !nodeRegistry?.meta?.isStart && !nodeRegistry?.meta?.isNodeEnd}>
<Tooltip
title={isNodeDisabled ? t("workflow.detail.design.drawer.disabled.on.tooltip") : t("workflow.detail.design.drawer.disabled.off.tooltip")}
>
<Button
className="ant-drawer-close"
style={{ marginInline: 0 }}
disabled={playground.config.readonlyOrDisabled}
icon={isNodeDisabled ? <IconEyeOff size="1.25em" /> : <IconEye size="1.25em" />}
size="small"
type="text"
onClick={handleDisableNodeClick}
/>
</Tooltip>
</Show>
<Button className="ant-drawer-close" style={{ marginInline: 0 }} icon={<IconX size="1.25em" />} size="small" type="text" onClick={handleClose} />
</Flex>
<div className="mt-3 text-sm font-normal">
<div className="mt-3 truncate text-sm font-normal">
<Typography.Text className="text-xs" type="secondary">
<span>{t("workflow.detail.design.drawer.node_id.label")}</span>
<span>{node?.id}</span>
</Typography.Text>
</div>
{anchor && (
<Show when={!!anchor}>
<div className="-mx-[2px] mt-3 text-sm font-normal">
<Anchor
affix={false}
getContainer={() => containerRef.current!}
direction="horizontal"
items={anchor.items}
items={(anchor as AnchorProps).items}
onClick={(e, link) => {
// https://github.com/ant-design/ant-design/issues/10577
// https://github.com/ant-design/ant-design/issues/15326
@@ -186,7 +229,7 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
}}
/>
</div>
)}
</Show>
</>
}
onClose={handleClose}
@@ -7,6 +7,7 @@ import {
type NodeRenderReturnType,
useClientContext,
useWatchFormState,
useWatchFormValueIn,
} from "@flowgram.ai/fixed-layout-editor";
import { IconCopy, IconDotsVertical, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react";
import { Button, type ButtonProps, Card, Dropdown, Input, type InputRef, Popover, Tooltip, theme } from "antd";
@@ -73,6 +74,7 @@ const InternalNodeCard = ({
const isActivated = useMemo(() => nodeRenderData.activated || nodeRenderData.lineActivated, [nodeRenderData.activated, nodeRenderData.lineActivated]);
const [isHovering, setIsHovering] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
const isNodeDisabled = useWatchFormValueIn(nodeRender.node, "disabled");
const formState = useWatchFormState(nodeRender.node);
useEffect(() => setIsInvalid(!!formState?.invalid), [formState?.invalid]);
@@ -82,6 +84,7 @@ const InternalNodeCard = ({
className={mergeCls(
"relative rounded-xl shadow-sm",
{ "border-primary": isActivated },
{ "border-dashed": isNodeDisabled },
nodeRegistry.meta?.clickable ? "cursor-pointer" : "cursor-default",
className
)}
@@ -91,7 +94,14 @@ const InternalNodeCard = ({
onMouseEnter={() => startTransition(() => setIsHovering(true))}
onMouseLeave={() => startTransition(() => setIsHovering(false))}
>
<div className="relative z-1">{children}</div>
<div
className="relative z-1 transition-opacity"
style={{
opacity: isHovering ? 1 : isNodeDisabled ? 0.3 : void 0,
}}
>
{children}
</div>
<div
className="absolute z-0 rounded-xl border-solid border-transparent transition-all duration-500"
style={{
@@ -101,6 +111,7 @@ const InternalNodeCard = ({
bottom: "-1px",
borderWidth: "2px",
borderColor: isHovering ? "var(--color-primary)" : isInvalid ? "var(--color-error)" : void 0,
borderStyle: isNodeDisabled ? "dashed" : "solid",
}}
/>
</Card>
@@ -408,7 +419,7 @@ export const BranchNode = ({ className, style, children, description }: BranchNo
/>
)
}
placement="right"
placement="rightTop"
>
<div
className="group/node relative"
@@ -58,21 +58,67 @@ export interface DocumentJSON {
}
export interface NodeMeta extends FlowNodeMeta {
/**
* 自定义样式。
*/
style?: React.CSSProperties;
/**
* 帮助文本。
*/
helpText?: React.ReactNode;
/**
* 标题文本。
*/
labelText?: React.ReactNode;
/**
* 图标组件。
*/
icon?: React.ExoticComponent<any> | React.ComponentType<any>;
/**
* 图标前景色。
*/
iconColor?: string;
/**
* 图标背景色。
*/
iconBgColor?: string;
/**
* 是否可点击。通常配合抽屉表单使用。
*/
clickable?: boolean;
}
export interface NodeRegistry<V extends NodeJSON["data"] = NodeJSON["data"]> extends FlowNodeRegistry<NodeMeta> {
/**
* 节点类型分类。
*/
kind?: NodeKindType;
formMeta?: Omit<FormMeta<V>, "render"> & {
render: (props: FormRenderProps<V>) => React.ReactElement;
};
/**
* 判断是否可以添加一个该类型的节点。
* 如果不存在该方法,默认等同于返回值为 true。
* @param {FixedLayoutPluginContext} ctx
* @param {FlowNodeEntity} from
* @returns {Boolean}
*/
canAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
/**
* 判断是否可以删除一个该类型的节点。
* 如果不存在该方法,默认等同于返回值为 true。
* @param {FixedLayoutPluginContext} ctx
* @param {FlowNodeEntity} from
* @returns {Boolean}
*/
canDelete?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
/**
* 返回一个新的表示该类型的节点结构。
* @param {FixedLayoutPluginContext} ctx
* @param {FlowNodeEntity} from
* @returns {FlowNodeJSON}
*/
onAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => FlowNodeJSON;
}
+2
View File
@@ -74,6 +74,8 @@
"workflow.detail.design.toolbar.hand_mode": "Hand mode",
"workflow.detail.design.toolbar.pointer_mode": "Pointer mode",
"workflow.detail.design.drawer.node_id.label": "Node ID: ",
"workflow.detail.design.drawer.disabled.on.tooltip": "Disable",
"workflow.detail.design.drawer.disabled.off.tooltip": "Enable",
"workflow.detail.design.action.publish.button": "Publish",
"workflow.detail.design.action.publish.modal.title": "Publish changes",
"workflow.detail.design.action.publish.modal.content": "Are you sure to publish your changes?",
+2
View File
@@ -74,6 +74,8 @@
"workflow.detail.design.toolbar.hand_mode": "触摸模式",
"workflow.detail.design.toolbar.pointer_mode": "指针模式",
"workflow.detail.design.drawer.node_id.label": "节点 ID:",
"workflow.detail.design.drawer.disabled.on.tooltip": "禁用",
"workflow.detail.design.drawer.disabled.off.tooltip": "启用",
"workflow.detail.design.action.publish.button": "发布更改",
"workflow.detail.design.action.publish.modal.title": "发布更改",
"workflow.detail.design.action.publish.modal.content": "确定要发布更改吗?",