fix: flowDocument fromJSON will reload the node json data (#240)

* fix: flowDocument fromJSON update node data

* feat: getNodeForm add updateFormValues
This commit is contained in:
xiamidaxia 2025-05-16 20:25:26 +08:00 committed by GitHub
parent d9e805b167
commit 766fdc1597
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1071 additions and 159 deletions

View File

@ -13,7 +13,7 @@ doc.toJSON() // TODO This is the old version data, not yet optimized. Business l
doc.addFromNode(targetNode, json) // Insert after the specified node
doc.onNodeCreate(({ node, data}) => {}) // Listen to node creation, data is the JSON data at creation time
doc.onNodeCreate(({ node, json }) => {}) // Listen to node creation, data is the JSON data at creation time
doc.onNodeDispose(({ node }) => {}) // Listen to node deletion
```

View File

@ -13,7 +13,7 @@ doc.toJSON() // TODO 这里老版本的数据,还没优化,业务最好自
doc.addFromNode(targetNode, json) // 插入到指定节点的后边
doc.onNodeCreate(({ node, data}) => {}) // 监听节点创建data 为创建时候的json数据
doc.onNodeCreate(({ node, json }) => {}) // 监听节点创建data 为创建时候的json数据
doc.onNodeDispose(({ node }) => {}) // 监听节点删除
```

View File

@ -23,7 +23,7 @@ export interface FlowDocumentOptions {
*/
allNodesDefaultExpanded?: boolean;
toNodeJSON?(node: FlowNodeEntity): FlowNodeJSON;
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): void;
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON, isFirstCreate: boolean): void;
constants?: Record<string, any>;
formatNodeLines?: (node: FlowNodeEntity, lines: FlowTransitionLine[]) => FlowTransitionLine[];
formatNodeLabels?: (node: FlowNodeEntity, lines: FlowTransitionLabel[]) => FlowTransitionLabel[];

View File

@ -57,12 +57,22 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
protected onNodeUpdateEmitter = new Emitter<{
node: FlowNodeEntity;
/**
* use 'json' instead
* @deprecated
*/
data: FlowNodeJSON;
json: FlowNodeJSON;
}>();
protected onNodeCreateEmitter = new Emitter<{
node: FlowNodeEntity;
/**
* use 'json' instead
* @deprecated
*/
data: FlowNodeJSON;
json: FlowNodeJSON;
}>();
protected onNodeDisposeEmitter = new Emitter<{
@ -219,7 +229,7 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
addNode(
data: AddNodeData,
addedNodes?: FlowNodeEntity[],
ignoreCreateEvent?: boolean
ignoreCreateAndUpdateEvent?: boolean
): FlowNodeEntity {
const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
let node = this.getNode(id);
@ -244,10 +254,10 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
: this.nodeDataRegistries;
node.addInitializeData(datas);
node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
if (this.options.fromNodeJSON) {
this.options.fromNodeJSON(node, data);
}
this.options.fromNodeJSON?.(node, data, true);
isNew = true;
} else {
this.options.fromNodeJSON?.(node, data, false);
}
// 初始化数据重制
node.initData({
@ -261,7 +271,6 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
if (node.isStart) {
this.root.addChild(node);
}
this.onNodeUpdateEmitter.fire({ node, data });
addedNodes?.push(node);
// 自定义创建逻辑
if (register.onCreate) {
@ -278,11 +287,16 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
}
}
if (isNew && !ignoreCreateEvent) {
this.onNodeCreateEmitter.fire({
node,
data,
});
if (!ignoreCreateAndUpdateEvent) {
if (isNew) {
this.onNodeCreateEmitter.fire({
node,
data,
json: data,
});
} else {
this.onNodeUpdateEmitter.fire({ node, data, json: data });
}
}
return node;

View File

@ -14,19 +14,27 @@ export function toFormJSON(node: FlowNodeEntity) {
return formData.toJSON();
}
export function initFormDataFromJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
export function initFormDataFromJSON(
node: FlowNodeEntity,
json: FlowNodeJSON,
isFirstCreate: boolean
) {
const formData = node.getData(FlowNodeFormData)!;
const registry = node.getNodeRegistry();
const { formMeta } = registry;
if (formData && formMeta) {
formData.createForm(formMeta, json.data);
formData.onDataChange(() => {
(node.document as WorkflowDocument).fireContentChange({
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
toJSON: () => formData.toJSON(),
entity: node,
if (isFirstCreate) {
formData.createForm(formMeta, json.data);
formData.onDataChange(() => {
(node.document as WorkflowDocument).fireContentChange({
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
toJSON: () => formData.toJSON(),
entity: node,
});
});
});
} else {
formData.updateFormValues(json.data);
}
}
}

View File

@ -92,8 +92,8 @@ export const WorkflowDocumentOptionsDefault: WorkflowDocumentOptions = {
'url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02LjYxODE3IDUuNTk4NzVDNi42MTgxNyA0LjgxOTIgNy4yNTAxMiA0LjE4NzI2IDguMDI5NjcgNC4xODcyNkM4Ljc3ODczIDQuMTg3MjYgOS4zOTE1MiA0Ljc3MDc1IDkuNDM4MjkgNS41MDgwMUM5LjQ1OTkyIDQuOTQ3MSA5LjkyMTQ3IDQuNDk5MDIgMTAuNDg3NyA0LjQ5OTAyQzExLjA2NzcgNC40OTkwMiAxMS41Mzc4IDQuOTY5MiAxMS41Mzc4IDUuNTQ5MTlWOC43NjI0NkwxMS41Mzc5IDYuNzExNUMxMS41Mzc5IDYuMDMwOTUgMTIuMDg5NiA1LjQ3OTI2IDEyLjc3MDIgNS40NzkyNkMxMy40NTA3IDUuNDc5MjYgMTQuMDAyNCA2LjAzMDk1IDE0LjAwMjQgNi43MTE1TDE0LjAwMjQgOC43NjI0NkwxNC4wMDI5IDguMDE5ODNDMTQuMDAyOSA3LjMzOTI5IDE0LjU1NDYgNi43ODc1OSAxNS4yMzUyIDYuNzg3NTlDMTUuOTE1NyA2Ljc4NzU5IDE2LjQ2NzQgNy4zMzkyOCAxNi40Njc0IDguMDE5ODNWMTEuNDk3TDE2LjQ2NzUgMTIuNDQ2N0MxNi40Njc1IDE0LjA4MjEgMTUuODA5NiAxNS42NDg3IDE0LjY0MiAxNi43OTM4VjE3LjMyNjJDMTQuNjQyIDE3Ljc4NjQgMTQuMjY4OSAxOC4xNTk1IDEzLjgwODcgMTguMTU5NUg4LjE3MzE3QzcuNzEyOTMgMTguMTU5NSA3LjMzOTg0IDE3Ljc4NjQgNy4zMzk4NCAxNy4zMjYyVjE1Ljk0MjRMNS4zNDU2MiAxNC43NTM0QzQuNTg5MjQgMTQuMzAyNCA0LjEyNTkxIDEzLjQ4NjcgNC4xMjU4OSAxMi42MDYxTDQuMTI1ODMgOS4yODM4M0M0LjEyNTgyIDguOTU0MjcgNC4zMjAwMyA4LjY1NTY2IDQuNjIxMjkgOC41MjIwNUw2LjYxODE3IDcuNjM2MzRWNS41OTg3NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAuNDg3OCAzLjI0OTAyQzExLjI3OTYgMy4yNDkwMiAxMS45Nzc4IDMuNjQ5MDIgMTIuMzkxNyA0LjI1Nzk2QzEyLjUxNTEgNC4yMzkwNiAxMi42NDE2IDQuMjI5MjYgMTIuNzcwMyA0LjIyOTI2QzEzLjcyMjQgNC4yMjkyNiAxNC41NDkzIDQuNzY1MzEgMTQuOTY1NyA1LjU1MjA3QzE1LjA1NDMgNS41NDI1IDE1LjE0NDIgNS41Mzc1OSAxNS4yMzUzIDUuNTM3NTlDMTYuNjA2MiA1LjUzNzU5IDE3LjcxNzYgNi42NDg5MyAxNy43MTc2IDguMDE5ODNMMTcuNzE3NyAxMi40NDY3QzE3LjcxNzcgMTQuMjM1NSAxNy4wNjQ3IDE1Ljk1NTkgMTUuODkyMSAxNy4yOTA0VjE3LjMyNjJDMTUuODkyMSAxOC40NzY4IDE0Ljk1OTQgMTkuNDA5NSAxMy44MDg4IDE5LjQwOTVIOC4xNzMzMkM3LjAyMjczIDE5LjQwOTUgNi4wODk5OCAxOC40NzY4IDYuMDg5OTggMTcuMzI2MlYxNi42NTI0TDQuNzA1NjMgMTUuODI3QzMuNTcxMDYgMTUuMTUwNSAyLjg3NjA3IDEzLjkyNzEgMi44NzYwNCAxMi42MDYxTDIuODc1OTggOS4yODM4NUMyLjg3NTk2IDguNDU5OTYgMy4zNjE0OSA3LjcxMzQ1IDQuMTE0NjIgNy4zNzk0TDUuMzY4MzIgNi44MjMzM1Y1LjU5ODc1QzUuMzY4MzIgNC4xMjg4NSA2LjU1OTkxIDIuOTM3MjYgOC4wMjk4MiAyLjkzNzI2QzguNjA4MzEgMi45MzcyNiA5LjE0MzU1IDMuMTIxNyA5LjU4MDA1IDMuNDM1MDVDOS44NTg1MyAzLjMxNTMyIDEwLjE2NTQgMy4yNDkwMiAxMC40ODc4IDMuMjQ5MDJaTTEyLjQ0NzkgNS41MjE4NlY5LjQ3NTU3QzEyLjQ0NzkgOS43Mjc2MiAxMi4yNDM2IDkuOTMxOTUgMTEuOTkxNiA5LjkzMTk1QzExLjc1NjggOS45MzE5NSAxMS41NjM0IDkuNzU0NjUgMTEuNTM4IDkuNTI2NjNDMTEuNTM2MSA5LjUwOTg3IDExLjUzNTIgOS40OTI4MyAxMS41MzUyIDkuNDc1NTdWNS40NzE1OEMxMS41MTU0IDUuMjAwODMgMTEuMzkzIDQuOTU4NTggMTEuMjA2NiA0Ljc4MzU4QzExLjAxODggNC42MDcxMSAxMC43NjU5IDQuNDk5MDIgMTAuNDg3OCA0LjQ5OTAyQzEwLjQ3NjYgNC40OTkwMiAxMC40NjU0IDQuNDk5MTkgMTAuNDU0MiA0LjQ5OTU0QzkuOTAzNDcgNC41MTY4NCA5LjQ1OTY0IDQuOTU4MjQgOS40Mzg0NCA1LjUwODAxQzkuNDM4MiA1LjUwNDMgOS40Mzc5NSA1LjUwMDU4IDkuNDM3NjkgNS40OTY4OFY5LjkwMjQzQzkuNDM3NjkgMTAuMTU0NSA5LjIzMzM2IDEwLjM1ODggOC45ODEzMSAxMC4zNTg4QzguNzI5MjUgMTAuMzU4OCA4LjUyNDkyIDEwLjE1NDUgOC41MjQ5MiA5LjkwMjQzVjQuMjc2NTNDOC4zNzA4NiA0LjIxODgyIDguMjA0MDIgNC4xODcyNiA4LjAyOTgyIDQuMTg3MjZDNy4yNTAyNyA0LjE4NzI2IDYuNjE4MzIgNC44MTkyIDYuNjE4MzIgNS41OTg3NUw2LjYxODI3IDkuOTc1OTlDNi42MTgyNyAxMC4yMjggNi40MTM5NCAxMC40MzI0IDYuMTYxODkgMTAuNDMyNEM1LjkwOTgzIDEwLjQzMjQgNS43MDU1IDEwLjIyOCA1LjcwNTUgOS45NzU5OVY4LjA0MTIyTDQuNjIxNDQgOC41MjIwNUM0LjMyMDE4IDguNjU1NjYgNC4xMjU5NyA4Ljk1NDI3IDQuMTI1OTggOS4yODM4M0w0LjEyNjA0IDEyLjYwNjFDNC4xMjYwNiAxMy40ODY3IDQuNTg5MzkgMTQuMzAyNCA1LjM0NTc2IDE0Ljc1MzRMNy4zMzk5OCAxNS45NDI0VjE3LjMyNjJDNy4zMzk5OCAxNy43ODY0IDcuNzEzMDggMTguMTU5NSA4LjE3MzMyIDE4LjE1OTVIMTMuODA4OEMxNC4yNjkgMTguMTU5NSAxNC42NDIxIDE3Ljc4NjQgMTQuNjQyMSAxNy4zMjYyVjE2Ljc5MzhDMTUuNzM4MSAxNS43MTkgMTYuMzg1IDE0LjI3MjggMTYuNDYwMyAxMi43NDdDMTYuNDY0NiAxMi42NiAxNi40NjcgMTIuNTcyOCAxNi40Njc2IDEyLjQ4NTRMMTYuNDY3NyAxMi40NDY3TDE2LjQ2NzYgOC4wMTk4M0MxNi40Njc2IDcuMzQ1MDQgMTUuOTI1MiA2Ljc5NjkzIDE1LjI1MjUgNi43ODc3MUwxNS4yMzUzIDYuNzg3NTlDMTUuMTI1IDYuNzg3NTkgMTUuMDE4IDYuODAyMSAxNC45MTYyIDYuODI5MzFWOS42MDEzNkMxNC45MTYyIDkuODUzNDIgMTQuNzExOSAxMC4wNTc3IDE0LjQ1OTggMTAuMDU3N0MxNC4yMDc4IDEwLjA1NzcgMTQuMDAzNCA5Ljg1MzQxIDE0LjAwMzQgOS42MDEzNlY3Ljk4OTg1QzE0LjAwMzIgNy45OTk4MiAxNC4wMDMxIDguMDA5ODEgMTQuMDAzMSA4LjAxOTgzTDE0LjAwMzQgOS42MDEzNkwxNC4wMDI1IDYuNzExNUMxNC4wMDI1IDYuNDQ5NzQgMTMuOTIwOSA2LjIwNzA1IDEzLjc4MTggNi4wMDc0OEMxMy41NjIgNS42OTI0MiAxMy4xOTg5IDUuNDg0ODMgMTIuNzg3IDUuNDc5MzdMMTIuNzcwMyA1LjQ3OTI2QzEyLjY1ODggNS40NzkyNiAxMi41NTA3IDUuNDk0MDggMTIuNDQ3OSA1LjUyMTg2WiIgZmlsbD0iIzFEMUMyMyIvPgo8L3N2Zz4="), auto',
},
fromNodeJSON(node, json) {
initFormDataFromJSON(node, json);
fromNodeJSON(node, json, isFirstCreate) {
initFormDataFromJSON(node, json, isFirstCreate);
return;
},
toNodeJSON(node: WorkflowNodeEntity): WorkflowNodeJSON {

View File

@ -180,10 +180,12 @@ export class WorkflowDocument extends FlowDocument {
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData)!;
const freeLayout = this.layout as FreeLayout;
transform.onDataChange(() => {
// TODO 这个有点难以理解其实是为了同步size 数据
freeLayout.syncTransform(node);
});
if (!isExistedNode) {
transform.onDataChange(() => {
// TODO 这个有点难以理解其实是为了同步size 数据
freeLayout.syncTransform(node);
});
}
let { position } = meta;
if (!position) {
// 获取默认的位置
@ -210,13 +212,15 @@ export class WorkflowDocument extends FlowDocument {
}
// 位置变更
const positionData = node.getData<PositionData>(PositionData)!;
positionData.onDataChange(() => {
this.fireContentChange({
type: WorkflowContentChangeType.MOVE_NODE,
toJSON: () => positionData.toJSON(),
entity: node,
if (!isExistedNode) {
positionData.onDataChange(() => {
this.fireContentChange({
type: WorkflowContentChangeType.MOVE_NODE,
toJSON: () => positionData.toJSON(),
entity: node,
});
});
});
}
const subCanvas = this.getNodeSubCanvas(node);
@ -265,19 +269,29 @@ export class WorkflowDocument extends FlowDocument {
canvasTransform.update({
position: subCanvas.parentNode.getNodeMeta()?.canvasPosition,
});
subCanvas.parentNode.onDispose(() => {
subCanvas.canvasNode.dispose();
if (!isExistedNode) {
subCanvas.parentNode.onDispose(() => {
subCanvas.canvasNode.dispose();
});
subCanvas.canvasNode.onDispose(() => {
subCanvas.parentNode.dispose();
});
}
}
if (!isExistedNode) {
this.onNodeCreateEmitter.fire({
node,
data: json,
json,
});
subCanvas.canvasNode.onDispose(() => {
subCanvas.parentNode.dispose();
} else {
this.onNodeUpdateEmitter.fire({
node,
data: json,
json,
});
}
this.onNodeCreateEmitter.fire({
node,
data: json,
});
return node;
}
@ -554,8 +568,6 @@ export class WorkflowDocument extends FlowDocument {
*
*/
toJSON(): WorkflowJSON {
// 要等 一些节点的 dispose 触发结束
// await delay(10);
const rootJSON = this.toNodeJSON(this.root);
return {
nodes: rootJSON.blocks ?? [],

View File

@ -1 +0,0 @@
export const EditorOptions = Symbol('EditorOptions');

View File

@ -10,7 +10,6 @@ import { FlowDocumentContainerModule } from '@flowgram.ai/document';
import { createPlaygroundPlugin, Plugin, PluginsProvider } from '@flowgram.ai/core';
import { compose } from '../utils/compose';
import { EditorOptions } from '../constants';
import { createFlowEditorClientPlugins } from '../clients/flow-editor-client-plugins';
import { EditorPluginContext, EditorProps } from './editor-props';
@ -20,7 +19,6 @@ export function createDefaultPreset<CTX extends EditorPluginContext = EditorPlug
): PluginsProvider<CTX> {
return (ctx: CTX) => {
opts = { ...EditorProps.DEFAULT, ...opts };
ctx.container.bind(EditorOptions).toConstantValue(opts);
/**
* i18n support
*/

View File

@ -5,7 +5,7 @@ import { NodeCorePluginOptions } from '@flowgram.ai/node-core-plugin';
import { MaterialsPluginOptions } from '@flowgram.ai/materials-plugin';
import { I18nPluginOptions } from '@flowgram.ai/i18n-plugin';
import { HistoryPluginOptions } from '@flowgram.ai/history';
import { FlowNodeFormData, FormMetaOrFormMetaGenerator } from '@flowgram.ai/form-core';
import { FormMetaOrFormMetaGenerator } from '@flowgram.ai/form-core';
import {
FlowDocument,
FlowDocumentJSON,
@ -18,8 +18,6 @@ import {
} from '@flowgram.ai/document';
import { PluginContext } from '@flowgram.ai/core';
import { EditorOptions } from '../constants';
export interface EditorPluginContext extends PluginContext {
document: FlowDocument;
selection: SelectionService;
@ -94,12 +92,18 @@ export interface EditorProps<
};
/**
*
* @param node
*
* - node
* - json
*/
toNodeJSON?(node: FlowNodeEntity): FlowNodeJSON;
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): void;
toNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): FlowNodeJSON;
/**
*
* - node
* - json
* - isFirstCreate
*/
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON, isFirstCreate: boolean): FlowNodeJSON;
/**
*
*/
@ -124,54 +128,5 @@ export namespace EditorProps {
*/
export const DEFAULT: EditorProps = {
background: {},
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
const formData = node.getData(FlowNodeFormData)!;
// 如果没有使用表单引擎,将 data 数据填入 extInfo
if (!formData) {
if (json.data) {
node.updateExtInfo(json.data);
}
} else {
const defaultFormMeta = node
.getService<EditorProps>(EditorOptions)
.nodeEngine?.createDefaultFormMeta?.(node);
const formMeta = node.getNodeRegistry()?.formMeta || defaultFormMeta;
if (formMeta) {
formData.createForm(formMeta, json.data);
}
}
},
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
const nodesMap: Record<string, FlowNodeJSON> = {};
let startNodeJSON: FlowNodeJSON;
node.document.traverse((node) => {
const isSystemNode = node.id.startsWith('$');
if (isSystemNode) return;
const formData = node.getData(FlowNodeFormData);
let formJSON =
formData && formData.formModel && formData.formModel.initialized
? formData.toJSON()
: undefined;
const nodeJSON = {
id: node.id,
type: node.flowNodeType,
data: formData ? formJSON : node.getExtInfo(),
blocks: [],
};
if (!startNodeJSON) startNodeJSON = nodeJSON;
let { parent } = node;
if (parent && parent.id.startsWith('$')) {
parent = parent.originParent;
}
const parentJSON = parent ? nodesMap[parent.id] : undefined;
if (parentJSON) {
parentJSON.blocks?.push(nodeJSON);
}
nodesMap[node.id] = nodeJSON;
}, node);
return startNodeJSON!;
},
};
}

View File

@ -26,6 +26,17 @@ export const formMock: FlowDocumentJSON = {
}]
}
export const formMock2: FlowDocumentJSON = {
nodes: [{
id: 'noop_0',
type: 'noop',
data: {
title: 'noop title changed',
},
blocks: [],
}]
}
export const baseWithDataMock: FlowDocumentJSON = {
nodes: [ {
id: 'start_0',
@ -58,6 +69,39 @@ export const baseWithDataMock: FlowDocumentJSON = {
]
}
export const baseWithDataMock2: FlowDocumentJSON = {
nodes: [ {
id: 'start_0',
type: 'start',
data: {
title: 'start title changed',
},
blocks: [],
},
{
id: 'dynamicSplit_0',
type: 'dynamicSplit',
data: {
title: 'dynamic title changed',
},
blocks: [
{ id: 'block_3', data: { title: '' }, blocks: [], type: 'block' },
{ id: 'block_4',data: { title: '' }, blocks: [], type: 'block'},
{ id: 'block_2',data: { title: 'title changed' },blocks: [], type: 'block' }
],
},
{
id: 'end_0',
type: 'end',
data: {
title: 'end title changed',
},
blocks: [],
},
]
}
export const baseMock: FlowDocumentJSON = {
nodes: [
{

View File

@ -1,5 +1,141 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`fixed-layout-preset > custom fromNodeJSON and toNodeJSON 1`] = `
{
"nodes": [
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "start title",
},
"id": "start_0",
"type": "start",
},
{
"blocks": [
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "",
},
"id": "block_0",
"type": "block",
},
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "",
},
"id": "block_1",
"type": "block",
},
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "",
},
"id": "block_2",
"type": "block",
},
],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "dynamic title",
},
"id": "dynamicSplit_0",
"type": "dynamicSplit",
},
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "end title",
},
"id": "end_0",
"type": "end",
},
],
}
`;
exports[`fixed-layout-preset > custom fromNodeJSON and toNodeJSON 2`] = `
{
"nodes": [
{
"blocks": [],
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "start title changed",
},
"id": "start_0",
"type": "start",
},
{
"blocks": [
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "",
},
"id": "block_3",
"type": "block",
},
{
"blocks": [],
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "",
},
"id": "block_4",
"type": "block",
},
{
"blocks": [],
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "title changed",
},
"id": "block_2",
"type": "block",
},
],
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "dynamic title changed",
},
"id": "dynamicSplit_0",
"type": "dynamicSplit",
},
{
"blocks": [],
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "end title changed",
},
"id": "end_0",
"type": "end",
},
],
}
`;
exports[`fixed-layout-preset > nodeEngine(v2) toJSON 1`] = `
{
"nodes": [

View File

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { FlowDocument, FlowNodeFormData } from '@flowgram.ai/editor';
import { baseWithDataMock, formMock } from '../__mocks__/flow.mock';
import { baseWithDataMock, baseWithDataMock2, formMock, formMock2 } from '../__mocks__/flow.mock';
import { createContainer } from './create-container';
describe('fixed-layout-preset', () => {
@ -13,6 +13,28 @@ describe('fixed-layout-preset', () => {
it('fromJSON and toJSON', () => {
flowDocument.fromJSON(baseWithDataMock);
expect(flowDocument.toJSON()).toEqual(baseWithDataMock);
// reload data
flowDocument.fromJSON(baseWithDataMock2);
expect(flowDocument.toJSON()).toEqual(baseWithDataMock2);
});
it('custom fromNodeJSON and toNodeJSON', () => {
const container = createContainer({
fromNodeJSON: (node, json, isFirstCreate) => {
if (!json.data) {
json.data = {};
}
json.data = { ...json.data, isFirstCreate };
return json;
},
toNodeJSON(node, json) {
json.data.runningTimes = (json.data.runningTimes || 0) + 1;
return json;
},
});
container.get(FlowDocument).fromJSON(baseWithDataMock);
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
container.get(FlowDocument).fromJSON(baseWithDataMock2);
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
});
it('nodeEngine(v2) toJSON', async () => {
const container = createContainer({
@ -33,5 +55,7 @@ describe('fixed-layout-preset', () => {
expect(formModel.getFormItemByPath('title').value).toEqual('noop title');
formModel.getFormItemByPath('title').value = 'noop title2';
expect(flowDocument.toJSON()).toMatchSnapshot();
flowDocument.fromJSON(formMock2);
expect(flowDocument.toJSON()).toEqual(formMock2);
});
});

View File

@ -1,5 +1,5 @@
import { beforeEach, describe, it, expect } from 'vitest';
import { FlowNodeEntity, FlowNodeFormData, FlowNodeJSON, FormModelV2 } from '@flowgram.ai/editor';
import { FlowNodeFormData, FormModelV2 } from '@flowgram.ai/editor';
import { createHistoryContainer } from '../../create-container';
import { formMock } from '../../../__mocks__/form.mock';
@ -7,14 +7,6 @@ import { emptyMock } from '../../../__mocks__/flow.mock';
describe('history-operation-service changeFormData', () => {
const { flowDocument, flowOperationService, historyService } = createHistoryContainer({
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
const formData = node.getData(FlowNodeFormData)!;
const formMeta = node.getNodeRegistry()?.formMeta;
if (formMeta) {
formData.createForm(formMeta, json.data);
}
},
nodeEngine: {},
nodeRegistries: [
{

View File

@ -24,6 +24,7 @@ import {
import { FlowOperationService } from '../types';
import { createOperationPlugin } from '../plugins/create-operation-plugin';
import { fromNodeJSON, toNodeJSON } from './node-serialize';
import { FixedLayoutPluginContext, FixedLayoutProps } from './fixed-layout-props';
export function createFixedLayoutPreset(
@ -128,8 +129,9 @@ export function createFixedLayoutPreset(
bindConfig.bind(FlowDocumentOptions).toConstantValue({
...FlowDocumentOptionsDefault,
defaultLayout: opts.defaultLayout,
toNodeJSON: opts.toNodeJSON,
fromNodeJSON: opts.fromNodeJSON,
toNodeJSON: (node) => toNodeJSON(opts, node),
fromNodeJSON: (node, json, isFirstCreate) =>
fromNodeJSON(opts, node, json, isFirstCreate),
allNodesDefaultExpanded: opts.allNodesDefaultExpanded,
} as FlowDocumentOptions);
}

View File

@ -0,0 +1,67 @@
import { FlowNodeEntity, FlowNodeJSON, FlowNodeFormData } from '@flowgram.ai/editor';
import { FixedLayoutProps } from './fixed-layout-props';
export function fromNodeJSON(
opts: FixedLayoutProps,
node: FlowNodeEntity,
json: FlowNodeJSON,
isFirstCreate: boolean
) {
json = opts.fromNodeJSON ? opts.fromNodeJSON(node, json, isFirstCreate) : json;
const formData = node.getData(FlowNodeFormData)!;
// 如果没有使用表单引擎,将 data 数据填入 extInfo
if (!formData) {
if (json.data) {
node.updateExtInfo(json.data);
}
} else {
const defaultFormMeta = opts.nodeEngine?.createDefaultFormMeta?.(node);
const formMeta = node.getNodeRegistry()?.formMeta || defaultFormMeta;
if (formMeta) {
if (isFirstCreate) {
formData.createForm(formMeta, json.data);
} else {
formData.updateFormValues(json.data);
}
}
}
}
export function toNodeJSON(opts: FixedLayoutProps, node: FlowNodeEntity): FlowNodeJSON {
const nodesMap: Record<string, FlowNodeJSON> = {};
let startNodeJSON: FlowNodeJSON;
node.document.traverse((node) => {
const isSystemNode = node.id.startsWith('$');
if (isSystemNode) return;
const formData = node.getData(FlowNodeFormData);
let formJSON =
formData && formData.formModel && formData.formModel.initialized
? formData.toJSON()
: undefined;
let nodeJSON: FlowNodeJSON = {
id: node.id,
type: node.flowNodeType,
data: formData ? formJSON : node.getExtInfo(),
blocks: [],
};
if (opts.toNodeJSON) {
nodeJSON = opts.toNodeJSON(node, nodeJSON);
}
if (!startNodeJSON) startNodeJSON = nodeJSON;
let { parent } = node;
if (parent && parent.id.startsWith('$')) {
parent = parent.originParent;
}
const parentJSON = parent ? nodesMap[parent.id] : undefined;
if (parentJSON) {
parentJSON.blocks?.push(nodeJSON);
}
nodesMap[node.id] = nodeJSON;
}, node);
// @ts-ignore
return startNodeJSON;
}

View File

@ -0,0 +1,440 @@
import { WorkflowJSON } from '@flowgram.ai/free-layout-core';
export const mockJSON: WorkflowJSON = {
nodes: [
{
id: 'start_0',
type: 'start',
meta: {
position: {
x: 180,
y: 381.75,
},
},
data: {
title: 'Start',
},
},
{
id: 'condition_0',
type: 'condition',
meta: {
position: {
x: 640,
y: 363.25,
},
},
data: {
title: 'Condition',
},
},
{
id: 'end_0',
type: 'end',
meta: {
position: {
x: 2220,
y: 381.75,
},
},
data: {
title: 'End',
},
},
{
id: 'loop_H8M3U',
type: 'loop',
meta: {
position: {
x: 1020,
y: 547.96875,
},
},
data: {
title: 'Loop_2',
},
blocks: [
{
id: 'llm_CBdCg',
type: 'llm',
meta: {
position: {
x: 180,
y: 0,
},
},
data: {
title: 'LLM_4',
},
},
{
id: 'llm_gZafu',
type: 'llm',
meta: {
position: {
x: 640,
y: 0,
},
},
data: {
title: 'LLM_5',
},
},
],
edges: [
{
sourceNodeID: 'llm_CBdCg',
targetNodeID: 'llm_gZafu',
},
],
},
{
id: '159623',
type: 'comment',
meta: {
position: {
x: 640,
y: 522.46875,
},
},
data: {
size: {
width: 240,
height: 150,
},
note: 'hi ~\n\nthis is a comment node\n\n- flowgram.ai',
},
},
{
id: 'group_V-_st',
type: 'group',
meta: {
position: {
x: 1020,
y: 96.25,
},
},
data: {
title: 'LLM_Group',
color: 'Violet',
},
blocks: [
{
id: 'llm_0',
type: 'llm',
meta: {
position: {
x: 640,
y: 0,
},
},
data: {
title: 'LLM_0',
},
},
{
id: 'llm_l_TcE',
type: 'llm',
meta: {
position: {
x: 180,
y: 0,
},
},
data: {
title: 'LLM_1',
},
},
],
edges: [
{
sourceNodeID: 'llm_l_TcE',
targetNodeID: 'llm_0',
},
{
sourceNodeID: 'llm_0',
targetNodeID: 'end_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'llm_l_TcE',
sourcePortID: 'if_0',
},
],
},
],
edges: [
{
sourceNodeID: 'start_0',
targetNodeID: 'condition_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'llm_l_TcE',
sourcePortID: 'if_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'loop_H8M3U',
sourcePortID: 'if_f0rOAt',
},
{
sourceNodeID: 'llm_0',
targetNodeID: 'end_0',
},
{
sourceNodeID: 'loop_H8M3U',
targetNodeID: 'end_0',
},
],
};
export const mockJSON2: WorkflowJSON = {
nodes: [
{
id: 'start_0',
type: 'start',
meta: {
position: {
x: 0,
y: 0,
},
},
data: {
title: 'Start changed',
},
},
{
id: 'condition_0',
type: 'condition',
meta: {
position: {
x: 0,
y: 0,
},
},
data: {
title: 'Condition changed',
},
},
{
id: 'end_0',
type: 'end',
meta: {
position: {
x: 0,
y: 0,
},
},
data: {
title: 'End',
},
},
{
id: 'loop_H8M3U',
type: 'loop',
meta: {
position: {
x: 1020,
y: 547.96875,
},
},
data: {
title: 'Loop_2 changed',
},
blocks: [
{
id: 'llm_CBdCg',
type: 'llm',
meta: {
position: {
x: 180,
y: 0,
},
},
data: {
title: 'LLM_4 chnaged',
},
},
{
id: 'llm_gZafu',
type: 'llm changed',
meta: {
position: {
x: 6,
y: 0,
},
},
data: {
title: 'LLM_5',
},
},
],
edges: [
{
sourceNodeID: 'llm_CBdCg',
targetNodeID: 'llm_gZafu',
},
],
},
{
id: '159623',
type: 'comment',
meta: {
position: {
x: 640,
y: 522.46875,
},
},
data: {
size: {
width: 240,
height: 150,
},
note: 'hi ~\n\nthis is a comment node changed\n\n- flowgram.ai',
},
},
{
id: 'group_V-_st',
type: 'group',
meta: {
position: {
x: 1020,
y: 96.25,
},
},
data: {
title: 'LLM_Group changed',
color: 'Violet',
},
blocks: [
{
id: 'llm_0',
type: 'llm',
meta: {
position: {
x: 640,
y: 0,
},
},
data: {
title: 'LLM_0 changed',
},
},
{
id: 'llm_l_TcE',
type: 'llm',
meta: {
position: {
x: 180,
y: 0,
},
},
data: {
title: 'LLM_1',
},
},
],
edges: [
{
sourceNodeID: 'llm_l_TcE',
targetNodeID: 'llm_0',
},
{
sourceNodeID: 'llm_0',
targetNodeID: 'end_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'llm_l_TcE',
sourcePortID: 'if_0',
},
],
},
],
edges: [
{
sourceNodeID: 'start_0',
targetNodeID: 'condition_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'llm_l_TcE',
sourcePortID: 'if_0',
},
{
sourceNodeID: 'condition_0',
targetNodeID: 'loop_H8M3U',
sourcePortID: 'if_f0rOAt',
},
{
sourceNodeID: 'llm_0',
targetNodeID: 'end_0',
},
{
sourceNodeID: 'loop_H8M3U',
targetNodeID: 'end_0',
},
],
};
export const mockSimpleJSON: WorkflowJSON = {
nodes: [
{
id: 'start_0',
type: 'start',
meta: {
position: { x: 0, y: 0 },
},
data: {
title: 'start'
},
},
{
id: 'end_0',
type: 'end',
meta: {
position: { x: 800, y: 0 },
},
data: {
title: 'end'
},
},
],
edges: [
{
sourceNodeID: 'start_0',
targetNodeID: 'end_0',
},
],
};
export const mockSimpleJSON2: WorkflowJSON = {
nodes: [
{
id: 'start_0',
type: 'start',
meta: {
position: { x: 1, y: 1 },
},
data: {
title: 'start changed'
},
},
{
id: 'end_0',
type: 'end',
meta: {
position: { x: 801, y: 1 },
},
data: {
title: 'end changed'
},
},
],
edges: [
{
sourceNodeID: 'start_0',
targetNodeID: 'end_0',
},
],
};

View File

@ -0,0 +1,87 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`free-layout-preset > custom fromNodeJSON and toNodeJSON 1`] = `
{
"edges": [
{
"sourceNodeID": "start_0",
"targetNodeID": "end_0",
},
],
"nodes": [
{
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "start",
},
"id": "start_0",
"meta": {
"position": {
"x": 0,
"y": 0,
},
},
"type": "start",
},
{
"data": {
"isFirstCreate": true,
"runningTimes": 1,
"title": "end",
},
"id": "end_0",
"meta": {
"position": {
"x": 800,
"y": 0,
},
},
"type": "end",
},
],
}
`;
exports[`free-layout-preset > custom fromNodeJSON and toNodeJSON 2`] = `
{
"edges": [
{
"sourceNodeID": "start_0",
"targetNodeID": "end_0",
},
],
"nodes": [
{
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "start changed",
},
"id": "start_0",
"meta": {
"position": {
"x": 1,
"y": 1,
},
},
"type": "start",
},
{
"data": {
"isFirstCreate": false,
"runningTimes": 1,
"title": "end changed",
},
"id": "end_0",
"meta": {
"position": {
"x": 801,
"y": 1,
},
},
"type": "end",
},
],
}
`;

View File

@ -0,0 +1,31 @@
import { interfaces } from 'inversify';
import {
createPlaygroundContainer,
Playground,
loadPlugins,
PluginContext,
createPluginContextDefault,
FlowDocument,
} from '@flowgram.ai/editor';
import { FreeLayoutPluginContext, FreeLayoutProps, createFreeLayoutPreset } from '../src';
export function createEditor(opts: FreeLayoutProps): interfaces.Container {
const container = createPlaygroundContainer();
const playground = container.get(Playground);
const preset = createFreeLayoutPreset(opts);
const customPluginContext = (container: interfaces.Container) =>
({
...createPluginContextDefault(container),
get document(): FlowDocument {
return container.get<FlowDocument>(FlowDocument);
},
} as FreeLayoutPluginContext);
const ctx = customPluginContext(container);
container.rebind(PluginContext).toConstantValue(ctx);
loadPlugins(preset(ctx), container);
playground.init();
return container;
}

View File

@ -0,0 +1,65 @@
import { describe, it, expect } from 'vitest';
import { FlowDocument, FlowNodeFormData } from '@flowgram.ai/editor';
import { mockJSON, mockJSON2, mockSimpleJSON, mockSimpleJSON2 } from '../__mocks__/flow.mocks';
import { createEditor } from './create-editor';
describe('free-layout-preset', () => {
it('fromJSON and toJSON', () => {
const editor = createEditor({});
const document = editor.get(FlowDocument);
document.fromJSON(mockJSON);
expect(document.toJSON()).toEqual(mockJSON);
document.fromJSON(mockJSON2);
expect(document.toJSON()).toEqual(mockJSON2);
});
it('custom fromNodeJSON and toNodeJSON', () => {
const container = createEditor({
fromNodeJSON: (node, json, isFirstCreate) => {
if (!json.data) {
json.data = {};
}
json.data = { ...json.data, isFirstCreate };
return json;
},
toNodeJSON(node, json) {
json.data.runningTimes = (json.data.runningTimes || 0) + 1;
return json;
},
});
container.get(FlowDocument).fromJSON(mockSimpleJSON);
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
container.get(FlowDocument).fromJSON(mockSimpleJSON2);
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
});
it('nodeEngine(v2) toJSON', async () => {
const container = createEditor({
nodeEngine: {},
nodeRegistries: [
{
type: 'start',
formMeta: {
render: () => undefined,
},
},
{
type: 'end',
formMeta: {
render: () => undefined,
},
},
],
});
const flowDocument = container.get(FlowDocument);
flowDocument.fromJSON(mockSimpleJSON);
expect(flowDocument.toJSON()).toEqual(mockSimpleJSON);
flowDocument.fromJSON(mockSimpleJSON2);
expect(flowDocument.toJSON()).toEqual(mockSimpleJSON2);
const { formModel } = flowDocument.getNode('start_0').getData(FlowNodeFormData);
expect(formModel.getFormItemByPath('title').value).toEqual('start changed');
formModel.getFormItemByPath('title').value = 'start changed 2';
expect(formModel.toJSON()).toEqual({
title: 'start changed 2',
});
});
});

View File

@ -32,6 +32,7 @@ import {
createPlaygroundReactPreset,
} from '@flowgram.ai/editor';
import { fromNodeJSON, toNodeJSON } from './node-serialize';
import { FreeLayoutProps, FreeLayoutPluginContext } from './free-layout-props';
const renderElement = (ctx: PluginContext) => {
@ -162,8 +163,9 @@ export function createFreeLayoutPreset(
cursors: opts.cursors ?? WorkflowDocumentOptionsDefault.cursors,
lineColor: opts.lineColor ?? WorkflowDocumentOptionsDefault.lineColor,
allNodesDefaultExpanded: opts.allNodesDefaultExpanded,
toNodeJSON: opts.toNodeJSON,
fromNodeJSON: opts.fromNodeJSON,
toNodeJSON: (node) => toNodeJSON(opts, node),
fromNodeJSON: (node, json, isFirstCreate) =>
fromNodeJSON(opts, node, json, isFirstCreate),
} as WorkflowDocumentOptions);
},
onInit: (ctx) => {

View File

@ -5,9 +5,7 @@ import {
LineRenderType,
onDragLineEndParams,
WorkflowContentChangeEvent,
WorkflowContentChangeType,
WorkflowDocument,
WorkflowDocumentOptionsDefault,
WorkflowJSON,
WorkflowLineEntity,
WorkflowLinePortInfo,
@ -20,9 +18,6 @@ import {
ClipboardService,
EditorPluginContext,
EditorProps,
FlowNodeEntity,
FlowNodeFormData,
type FlowNodeJSON,
SelectionService,
PluginContext,
} from '@flowgram.ai/editor';
@ -203,41 +198,5 @@ export namespace FreeLayoutProps {
*/
export const DEFAULT: FreeLayoutProps = {
...EditorProps.DEFAULT,
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
const formData = node.getData(FlowNodeFormData)!;
// 如果没有使用表单引擎,将 data 数据填入 extInfo
if (!formData) {
if (json.data) {
node.updateExtInfo(json.data);
}
// extInfo 数据更新则触发内容更新
node.onExtInfoChange(() => {
(node.document as WorkflowDocument).fireContentChange({
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
toJSON: () => node.getExtInfo(),
entity: node,
});
});
return;
}
return WorkflowDocumentOptionsDefault.fromNodeJSON?.(node, json);
},
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
const formData = node.getData(FlowNodeFormData)!;
const position = node.transform.position;
// 不使用节点引擎则采用 extInfo
if (!formData) {
return {
id: node.id,
type: node.flowNodeType,
meta: {
position: { x: position.x, y: position.y },
},
data: node.getExtInfo(),
};
}
return WorkflowDocumentOptionsDefault.toNodeJSON!(node);
},
} as FreeLayoutProps;
}

View File

@ -0,0 +1,57 @@
import {
WorkflowContentChangeType,
WorkflowDocument,
WorkflowDocumentOptionsDefault,
} from '@flowgram.ai/free-layout-core';
import { FlowNodeEntity, FlowNodeFormData, type FlowNodeJSON } from '@flowgram.ai/editor';
import { FreeLayoutProps } from './free-layout-props';
export function fromNodeJSON(
opts: FreeLayoutProps,
node: FlowNodeEntity,
json: FlowNodeJSON,
isFirstCreate: boolean
) {
json = opts.fromNodeJSON ? opts.fromNodeJSON(node, json, isFirstCreate) : json;
const formData = node.getData(FlowNodeFormData)!;
// 如果没有使用表单引擎,将 data 数据填入 extInfo
if (!formData) {
if (json.data) {
node.updateExtInfo(json.data);
}
// extInfo 数据更新则触发内容更新
if (isFirstCreate) {
node.onExtInfoChange(() => {
(node.document as WorkflowDocument).fireContentChange({
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
toJSON: () => node.getExtInfo(),
entity: node,
});
});
}
return;
}
return WorkflowDocumentOptionsDefault.fromNodeJSON?.(node, json, isFirstCreate);
}
export function toNodeJSON(opts: FreeLayoutProps, node: FlowNodeEntity): FlowNodeJSON {
const formData = node.getData(FlowNodeFormData)!;
const position = node.transform.position;
let json: FlowNodeJSON;
// 不使用节点引擎则采用 extInfo
if (!formData) {
json = {
id: node.id,
type: node.flowNodeType,
meta: {
position: { x: position.x, y: position.y },
},
data: node.getExtInfo(),
};
} else {
json = WorkflowDocumentOptionsDefault.toNodeJSON!(node);
}
return opts.toNodeJSON ? opts.toNodeJSON(node, json) : json;
}

View File

@ -78,6 +78,10 @@ export class FlowNodeFormData extends EntityData {
}
}
updateFormValues(value: any) {
this.formModel.updateFormValues(value);
}
recreateForm(formMetaOrFormMetaGenerator: FormMetaOrFormMetaGenerator, initialValue?: any): void {
this.createForm(formMetaOrFormMetaGenerator, initialValue);
}

View File

@ -39,6 +39,8 @@ export abstract class FormModel {
abstract get valid(): FormModelValid;
abstract updateFormValues(value: any): void;
/**
* @deprecated
* use `formModel.getFieldIn` instead in FormModelV2 to get the model of a form field

View File

@ -145,6 +145,12 @@ export class FormModelV2 extends FormModel implements Disposable {
return this._feedbacks;
}
updateFormValues(value: any) {
if (this.nativeFormModel) {
this.nativeFormModel.values = value;
}
}
private set feedbacks(feedbacks: FormFeedback[]) {
this._feedbacks = feedbacks;
this.onFeedbacksChangeEmitter.fire(feedbacks);

View File

@ -33,6 +33,10 @@ export interface NodeFormProps<TValues> {
* @param name path
*/
setValueIn<TValue>(name: FieldName, value: TValue): void;
/**
* set form values
*/
updateFormValues(values: any): void;
/**
* Render form
*/
@ -75,9 +79,13 @@ export function getNodeForm<TValues = FieldValue>(
get values() {
return nativeFormModel.values;
},
state: nativeFormModel.state,
getValueIn: (name: FieldName) => nativeFormModel.getValueIn(name),
setValueIn: (name: FieldName, value: any) => nativeFormModel.setValueIn(name, value),
updateFormValues: (values: any) => {
formModel.updateFormValues(values);
},
render: () => <NodeRender node={node} />,
onFormValuesChange: formModel.onFormValuesChange.bind(formModel),
onFormValueChangeIn: formModel.onFormValueChangeIn.bind(formModel),