mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat: disable node
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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?",
|
||||
|
||||
@@ -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": "确定要发布更改吗?",
|
||||
|
||||
Reference in New Issue
Block a user