This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Field, FlowNodeRegistry } from '@flowgram.ai/free-layout-editor';
|
||||
import { SubCanvasRender, createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
|
||||
import {
|
||||
provideBatchInputEffect,
|
||||
createBatchOutputsFormPlugin,
|
||||
type IFlowRefValue,
|
||||
DisplayOutputs,
|
||||
} from '@flowgram.ai/form-materials';
|
||||
|
||||
import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
|
||||
|
||||
const BatchVariableSelector = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.BatchVariableSelector,
|
||||
}))
|
||||
);
|
||||
|
||||
const BatchOutputs = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.BatchOutputs,
|
||||
}))
|
||||
);
|
||||
|
||||
type BatchOutputsValueType = Record<string, IFlowRefValue | undefined>;
|
||||
|
||||
const createLoopRegistry = (): FlowNodeRegistry => ({
|
||||
type: 'custom',
|
||||
meta: {
|
||||
isContainer: true,
|
||||
size: {
|
||||
width: 500,
|
||||
height: 260,
|
||||
},
|
||||
padding: () => ({
|
||||
top: 160,
|
||||
bottom: 40,
|
||||
left: 50,
|
||||
right: 50,
|
||||
}),
|
||||
},
|
||||
formMeta: {
|
||||
render: () => (
|
||||
<>
|
||||
<FormHeader />
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
Loop input (select array variable):
|
||||
</div>
|
||||
<Field<IFlowRefValue | undefined>
|
||||
name="loopFor"
|
||||
defaultValue={{ type: 'ref', content: ['start_0', 'arr', 'arr_obj'] }}
|
||||
>
|
||||
{({ field }) => (
|
||||
<BatchVariableSelector
|
||||
style={{ width: '100%' }}
|
||||
value={field.value?.content}
|
||||
onChange={(val) => field.onChange({ type: 'ref', content: val })}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<SubCanvasRender offsetY={-100} />
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
Loop outputs (collected into arrays):
|
||||
</div>
|
||||
<Field<BatchOutputsValueType | undefined>
|
||||
name="loopOutputs"
|
||||
defaultValue={{
|
||||
names: { type: 'ref', content: ['variable_0', 'name'] },
|
||||
}}
|
||||
>
|
||||
{({ field }) => (
|
||||
<BatchOutputs
|
||||
style={{ width: '100%' }}
|
||||
value={field.value}
|
||||
onChange={(val) => field.onChange(val)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
Generated output variables:
|
||||
</div>
|
||||
<DisplayOutputs displayFromScope />
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
effect: {
|
||||
loopFor: provideBatchInputEffect,
|
||||
},
|
||||
plugins: [
|
||||
createBatchOutputsFormPlugin({ outputKey: 'loopOutputs', inferTargetKey: 'outputs' }),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const createLoopRegistryWithInfer = (): FlowNodeRegistry => ({
|
||||
type: 'custom',
|
||||
meta: {
|
||||
isContainer: true,
|
||||
size: {
|
||||
width: 500,
|
||||
height: 260,
|
||||
},
|
||||
padding: () => ({
|
||||
top: 160,
|
||||
bottom: 40,
|
||||
left: 50,
|
||||
right: 50,
|
||||
}),
|
||||
},
|
||||
formMeta: {
|
||||
render: () => (
|
||||
<>
|
||||
<FormHeader />
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>Loop input:</div>
|
||||
<Field<IFlowRefValue | undefined>
|
||||
name="loopFor"
|
||||
defaultValue={{ type: 'ref', content: ['start_0', 'arr', 'arr_obj'] }}
|
||||
>
|
||||
{({ field }) => (
|
||||
<BatchVariableSelector
|
||||
style={{ width: '100%' }}
|
||||
value={field.value?.content}
|
||||
onChange={(val) => field.onChange({ type: 'ref', content: val })}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<SubCanvasRender offsetY={-100} />
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
Loop outputs (with schema inference):
|
||||
</div>
|
||||
<Field<BatchOutputsValueType | undefined>
|
||||
name="loopOutputs"
|
||||
defaultValue={{
|
||||
items: { type: 'ref', content: ['variable_0', 'item_name'] },
|
||||
values: { type: 'ref', content: ['variable_0', 'item_index'] },
|
||||
}}
|
||||
>
|
||||
{({ field }) => (
|
||||
<BatchOutputs
|
||||
style={{ width: '100%' }}
|
||||
value={field.value}
|
||||
onChange={(val) => field.onChange(val)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
Output variables (check Debug panel for inferred schema):
|
||||
</div>
|
||||
<DisplayOutputs displayFromScope />
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
effect: {
|
||||
loopFor: provideBatchInputEffect,
|
||||
},
|
||||
plugins: [
|
||||
createBatchOutputsFormPlugin({ outputKey: 'loopOutputs', inferTargetKey: 'outputs' }),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const BasicStory = () => (
|
||||
<FreeFormMetaStoryBuilder
|
||||
filterEndNode
|
||||
height={550}
|
||||
initialData={{
|
||||
nodes: [
|
||||
{
|
||||
id: 'custom_0',
|
||||
type: 'custom',
|
||||
data: {
|
||||
title: 'Loop',
|
||||
loopFor: { type: 'ref', content: ['start_0', 'arr', 'arr_obj'] },
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'block_start_0',
|
||||
type: 'block-start',
|
||||
data: { title: 'Start' },
|
||||
meta: { position: { x: 20, y: 0 } },
|
||||
},
|
||||
{
|
||||
id: 'variable_0',
|
||||
type: 'variable',
|
||||
data: {
|
||||
title: 'Variable',
|
||||
assign: [
|
||||
{
|
||||
operator: 'declare',
|
||||
left: 'name',
|
||||
right: { type: 'ref', content: ['custom_0_locals', 'item', 'str'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: { position: { x: 100, y: 0 } },
|
||||
},
|
||||
{
|
||||
id: 'block_end_0',
|
||||
type: 'block-end',
|
||||
data: { title: 'End' },
|
||||
meta: { position: { x: 360, y: 0 } },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ sourceNodeID: 'block_start_0', targetNodeID: 'variable_0' },
|
||||
{ sourceNodeID: 'variable_0', targetNodeID: 'block_end_0' },
|
||||
],
|
||||
},
|
||||
],
|
||||
edges: [{ sourceNodeID: 'start_0', targetNodeID: 'custom_0' }],
|
||||
}}
|
||||
transformRegistry={(props) => ({
|
||||
...props,
|
||||
nodeRegistries: [
|
||||
...(props.nodeRegistries || []).filter((r) => r.type !== 'custom'),
|
||||
createLoopRegistry(),
|
||||
],
|
||||
})}
|
||||
plugins={(ctx) => [createContainerNodePlugin({})]}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithInferSchemaStory = () => (
|
||||
<FreeFormMetaStoryBuilder
|
||||
filterEndNode
|
||||
height={550}
|
||||
initialData={{
|
||||
nodes: [
|
||||
{
|
||||
id: 'custom_0',
|
||||
type: 'custom',
|
||||
data: {
|
||||
title: 'Loop',
|
||||
loopFor: { type: 'ref', content: ['start_0', 'arr', 'arr_obj'] },
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'block_start_0',
|
||||
type: 'block-start',
|
||||
data: { title: 'Start' },
|
||||
meta: { position: { x: 20, y: 0 } },
|
||||
},
|
||||
{
|
||||
id: 'variable_0',
|
||||
type: 'variable',
|
||||
data: {
|
||||
title: 'Variable',
|
||||
assign: [
|
||||
{
|
||||
operator: 'declare',
|
||||
left: 'item_name',
|
||||
right: { type: 'ref', content: ['custom_0_locals', 'item', 'str'] },
|
||||
},
|
||||
{
|
||||
operator: 'declare',
|
||||
left: 'item_index',
|
||||
right: { type: 'ref', content: ['custom_0_locals', 'index'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: { position: { x: 100, y: 0 } },
|
||||
},
|
||||
{
|
||||
id: 'block_end_0',
|
||||
type: 'block-end',
|
||||
data: { title: 'End' },
|
||||
meta: { position: { x: 360, y: 0 } },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ sourceNodeID: 'block_start_0', targetNodeID: 'variable_0' },
|
||||
{ sourceNodeID: 'variable_0', targetNodeID: 'block_end_0' },
|
||||
],
|
||||
},
|
||||
],
|
||||
edges: [{ sourceNodeID: 'start_0', targetNodeID: 'custom_0' }],
|
||||
}}
|
||||
transformRegistry={(props) => ({
|
||||
...props,
|
||||
nodeRegistries: [
|
||||
...(props.nodeRegistries || []).filter((r) => r.type !== 'custom'),
|
||||
createLoopRegistryWithInfer(),
|
||||
],
|
||||
})}
|
||||
plugins={(ctx) => [createContainerNodePlugin({})]}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { createInferAssignPlugin } from '@flowgram.ai/form-materials';
|
||||
|
||||
import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
|
||||
|
||||
const AssignRows = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.AssignRows,
|
||||
}))
|
||||
);
|
||||
|
||||
const DisplayOutputs = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.DisplayOutputs,
|
||||
}))
|
||||
);
|
||||
|
||||
export const BasicStory = () => (
|
||||
<FreeFormMetaStoryBuilder
|
||||
transformInitialNode={{
|
||||
end_0: (node) => {
|
||||
node.data.inputsValues = {
|
||||
info: {
|
||||
type: 'ref',
|
||||
content: ['custom_0', 'userInfo'],
|
||||
},
|
||||
};
|
||||
|
||||
return node;
|
||||
},
|
||||
}}
|
||||
formMeta={{
|
||||
render: () => (
|
||||
<>
|
||||
<FormHeader />
|
||||
<AssignRows
|
||||
name="assign"
|
||||
defaultValue={[
|
||||
// 从常量声明变量
|
||||
{
|
||||
operator: 'declare',
|
||||
left: 'userName',
|
||||
right: {
|
||||
type: 'constant',
|
||||
content: 'John Doe',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
},
|
||||
// 从变量声明变量
|
||||
{
|
||||
operator: 'declare',
|
||||
left: 'userInfo',
|
||||
right: {
|
||||
type: 'ref',
|
||||
content: ['start_0', 'obj'],
|
||||
},
|
||||
},
|
||||
// 赋值现有变量
|
||||
{
|
||||
operator: 'assign',
|
||||
left: {
|
||||
type: 'ref',
|
||||
content: ['start_0', 'str'],
|
||||
},
|
||||
right: {
|
||||
type: 'constant',
|
||||
content: 'Hello Flowgram',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<DisplayOutputs displayFromScope style={{ marginTop: 10 }} />
|
||||
</>
|
||||
),
|
||||
plugins: [
|
||||
createInferAssignPlugin({
|
||||
assignKey: 'assign',
|
||||
outputKey: 'outputs',
|
||||
}),
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Field } from '@flowgram.ai/free-layout-editor';
|
||||
import { createInferInputsPlugin } from '@flowgram.ai/form-materials';
|
||||
|
||||
import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
|
||||
|
||||
const InputsValues = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.InputsValues,
|
||||
}))
|
||||
);
|
||||
|
||||
const InputsValuesTree = React.lazy(() =>
|
||||
import('@flowgram.ai/form-materials').then((module) => ({
|
||||
default: module.InputsValuesTree,
|
||||
}))
|
||||
);
|
||||
|
||||
/**
|
||||
* Basic usage story - demonstrates automatic schema inference from InputsValues
|
||||
*/
|
||||
export const BasicStory = () => (
|
||||
<FreeFormMetaStoryBuilder
|
||||
filterEndNode
|
||||
height={500}
|
||||
formMeta={{
|
||||
render: () => (
|
||||
<>
|
||||
<FormHeader />
|
||||
<div>
|
||||
<div>
|
||||
<h4>Headers</h4>
|
||||
<Field<Record<string, any> | undefined>
|
||||
name="headersValues"
|
||||
defaultValue={{
|
||||
'Content-Type': {
|
||||
type: 'constant',
|
||||
content: 'application/json',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
Authorization: {
|
||||
type: 'ref',
|
||||
content: ['start_0', 'str'],
|
||||
},
|
||||
}}
|
||||
>
|
||||
{({ field }) => (
|
||||
<InputsValues value={field.value} onChange={(value) => field.onChange(value)} />
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Body</h4>
|
||||
<Field<Record<string, any> | undefined>
|
||||
name="bodyValues"
|
||||
defaultValue={{
|
||||
page: {
|
||||
index: {
|
||||
type: 'ref',
|
||||
content: ['start_0', 'obj', 'obj2', 'num'],
|
||||
},
|
||||
size: {
|
||||
type: 'constant',
|
||||
content: 10,
|
||||
schema: { type: 'number' },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
type: 'ref',
|
||||
content: ['start_0', 'obj'],
|
||||
},
|
||||
}}
|
||||
>
|
||||
{({ field }) => (
|
||||
<InputsValuesTree
|
||||
value={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
plugins: [
|
||||
createInferInputsPlugin({
|
||||
sourceKey: 'headersValues',
|
||||
targetKey: 'headersSchema',
|
||||
}),
|
||||
createInferInputsPlugin({
|
||||
sourceKey: 'bodyValues',
|
||||
targetKey: 'bodySchema',
|
||||
}),
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
Reference in New Issue
Block a user