新版本流程编辑器

This commit is contained in:
mlogclub
2026-07-27 19:04:15 +08:00
parent d33960961a
commit e072141953
281 changed files with 25145 additions and 3405 deletions
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { FormRenderProps, FlowNodeJSON, Field, FormMeta } from '@flowgram.ai/free-layout-editor';
import { SubCanvasRender } from '@flowgram.ai/free-container-plugin';
import {
BatchOutputs,
BatchVariableSelector,
createBatchOutputsFormPlugin,
DisplayOutputs,
IFlowRefValue,
provideBatchInputEffect,
} from '@flowgram.ai/form-materials';
import { defaultFormMeta } from '../default-form-meta';
import { useIsSidebar, useNodeRenderContext } from '../../hooks';
import { FormHeader, FormContent, FormItem, Feedback } from '../../form-components';
interface LoopNodeJSON extends FlowNodeJSON {
data: {
loopFor: IFlowRefValue;
};
}
export const LoopFormRender = ({ form }: FormRenderProps<LoopNodeJSON>) => {
const isSidebar = useIsSidebar();
const { readonly } = useNodeRenderContext();
const formHeight = 115;
const loopFor = (
<Field<IFlowRefValue> name={`loopFor`}>
{({ field, fieldState }) => (
<FormItem name={'loopFor'} type={'array'} required>
<BatchVariableSelector
style={{ width: '100%' }}
value={field.value?.content}
onChange={(val) => field.onChange({ type: 'ref', content: val })}
readonly={readonly}
hasError={Object.keys(fieldState?.errors || {}).length > 0}
/>
<Feedback errors={fieldState?.errors} />
</FormItem>
)}
</Field>
);
const loopOutputs = (
<Field<Record<string, IFlowRefValue | undefined> | undefined> name={`loopOutputs`}>
{({ field, fieldState }) => (
<FormItem name="loopOutputs" type="object" vertical>
<BatchOutputs
style={{ width: '100%' }}
value={field.value}
onChange={(val) => field.onChange(val)}
readonly={readonly}
hasError={Object.keys(fieldState?.errors || {}).length > 0}
/>
<Feedback errors={fieldState?.errors} />
</FormItem>
)}
</Field>
);
if (isSidebar) {
return (
<>
<FormHeader />
<FormContent>
{loopFor}
{loopOutputs}
</FormContent>
</>
);
}
return (
<>
<FormHeader />
<FormContent>
{loopFor}
<SubCanvasRender offsetY={-formHeight} />
<DisplayOutputs displayFromScope />
</FormContent>
</>
);
};
export const formMeta: FormMeta = {
...defaultFormMeta,
render: LoopFormRender,
effect: {
loopFor: provideBatchInputEffect,
},
plugins: [createBatchOutputsFormPlugin({ outputKey: 'loopOutputs', inferTargetKey: 'outputs' })],
};
+114
View File
@@ -0,0 +1,114 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { nanoid } from 'nanoid';
import {
WorkflowNodeEntity,
PositionSchema,
FlowNodeTransformData,
} from '@flowgram.ai/free-layout-editor';
import { FlowNodeRegistry } from '../../typings';
import iconLoop from '../../assets/icon-loop.jpg';
import { formMeta } from './form-meta';
import { WorkflowNodeType } from '../constants';
let index = 0;
export const LoopNodeRegistry: FlowNodeRegistry = {
type: WorkflowNodeType.Loop,
info: {
icon: iconLoop,
description:
'Used to repeatedly execute a series of tasks by setting the number of iterations and logic.',
},
meta: {
/**
* Mark as subcanvas
* 子画布标记
*/
isContainer: true,
/**
* The subcanvas default size setting
* 子画布默认大小设置
*/
size: {
width: 424,
height: 244,
},
// autoResizeDisable: true,
/**
* The subcanvas padding setting
* 子画布 padding 设置
*/
padding: (transform) => {
if (!transform.isContainer) {
return {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
}
return {
top: 120,
bottom: 80,
left: 80,
right: 80,
};
},
/**
* Controls the node selection status within the subcanvas
* 控制子画布内的节点选中状态
*/
selectable(node: WorkflowNodeEntity, mousePos?: PositionSchema): boolean {
if (!mousePos) {
return true;
}
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
// 鼠标开始时所在位置不包括当前节点时才可选中
return !transform.bounds.contains(mousePos.x, mousePos.y);
},
// expandable: false, // disable expanded
wrapperStyle: {
minWidth: 'unset',
width: '100%',
},
// defaultPorts: [{ type: 'output', location: 'right' }, { type: 'input', location: 'left'}, { type: 'output', location: 'bottom', portID: 'bottom' }, { type: 'input', location: 'top', portID: 'top'}]
},
onAdd() {
return {
id: `loop_${nanoid(5)}`,
type: WorkflowNodeType.Loop,
data: {
title: `Loop_${++index}`,
},
blocks: [
{
id: `block_start_${nanoid(5)}`,
type: WorkflowNodeType.BlockStart,
meta: {
position: {
x: 32,
y: 0,
},
},
data: {},
},
{
id: `block_end_${nanoid(5)}`,
type: WorkflowNodeType.BlockEnd,
meta: {
position: {
x: 192,
y: 0,
},
},
data: {},
},
],
};
},
formMeta,
};