mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
feat(fixed-layout): add input/output/multi-outputs/multi-inputs/break node (#246)
* feat(demo): add demo playground * feat(fixed-layout): add multi-start and break node * fix: form-model updateFormValues with formatOnInit * feat(node-engine): formModel.values cloneDeep -> clone and add shllowEuqal checked * feat(demo): demo-fixed-layout-simple add flow-select * fix: use-node-render node undefined * docs: docs error * feat(fixed-layout): add input/output/multi-outputs/multi-inputs node
This commit is contained in:
parent
ec6e5abe23
commit
ce0c13393b
@ -1,6 +1,8 @@
|
||||
import { FlowNodeEntity, useNodeRender } from '@flowgram.ai/fixed-layout-editor';
|
||||
import { FlowNodeEntity, useNodeRender, useClientContext } from '@flowgram.ai/fixed-layout-editor';
|
||||
import { IconDeleteStroked } from '@douyinfe/semi-icons';
|
||||
|
||||
export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
|
||||
const ctx = useClientContext();
|
||||
/**
|
||||
* Provides methods related to node rendering
|
||||
* 提供节点渲染相关的方法
|
||||
@ -36,6 +38,10 @@ export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
|
||||
...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
|
||||
}}
|
||||
>
|
||||
<IconDeleteStroked
|
||||
style={{ position: 'absolute', right: 4, top: 4 }}
|
||||
onClick={() => ctx.operation.deleteNode(nodeRender.node)}
|
||||
/>
|
||||
{form?.render()}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -15,14 +15,26 @@ export function BranchAdder(props: PropsType) {
|
||||
const { isVertical } = node;
|
||||
|
||||
function addBranch() {
|
||||
const block = operation.addBlock(node, {
|
||||
id: `branch_${nanoid(5)}`,
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'New Branch',
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
let block: FlowNodeEntity;
|
||||
if (node.flowNodeType === 'multiOutputs') {
|
||||
block = operation.addBlock(node, {
|
||||
id: `output_${nanoid(5)}`,
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'New Ouput',
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
block = operation.addBlock(node, {
|
||||
id: `branch_${nanoid(5)}`,
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'New Branch',
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
playground.scrollToView({
|
||||
|
||||
49
apps/demo-fixed-layout-simple/src/components/flow-select.tsx
Normal file
49
apps/demo-fixed-layout-simple/src/components/flow-select.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useClientContext, FlowLayoutDefault } from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
import { FLOW_LIST } from '../data';
|
||||
|
||||
const url = new window.URL(window.location.href);
|
||||
|
||||
export function FlowSelect() {
|
||||
const [demoKey, updateDemoKey] = useState(url.searchParams.get('demo') ?? 'condition');
|
||||
const clientContext = useClientContext();
|
||||
useEffect(() => {
|
||||
const targetDemoJSON = FLOW_LIST[demoKey];
|
||||
if (targetDemoJSON) {
|
||||
clientContext.history.stop(); // Stop redo/undo
|
||||
clientContext.document.fromJSON(targetDemoJSON);
|
||||
console.log(clientContext.document.toString());
|
||||
clientContext.history.start();
|
||||
clientContext.document.setLayout(
|
||||
targetDemoJSON.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT
|
||||
);
|
||||
// Update URL
|
||||
if (url.searchParams.get('demo')) {
|
||||
url.searchParams.set('demo', demoKey);
|
||||
window.history.pushState({}, '', `/?${url.searchParams.toString()}`);
|
||||
}
|
||||
// Fit View
|
||||
setTimeout(() => {
|
||||
clientContext.playground.config.fitView(clientContext.document.root.bounds);
|
||||
}, 20);
|
||||
}
|
||||
}, [demoKey]);
|
||||
return (
|
||||
<div style={{ position: 'absolute', zIndex: 100 }}>
|
||||
<label style={{ marginRight: 12 }}>Select Demo:</label>
|
||||
<select
|
||||
style={{ width: '180px', height: '32px', fontSize: 16 }}
|
||||
onChange={(e) => updateDemoKey(e.target.value)}
|
||||
value={demoKey}
|
||||
>
|
||||
{Object.keys(FLOW_LIST).map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
|
||||
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
|
||||
import { IconButton, Space } from '@douyinfe/semi-ui';
|
||||
import { IconUnlock, IconLock } from '@douyinfe/semi-icons';
|
||||
|
||||
export function Tools() {
|
||||
const { history } = useClientContext();
|
||||
const { history, playground } = useClientContext();
|
||||
const tools = usePlaygroundTools();
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
const toggleReadonly = useCallback(() => {
|
||||
playground.config.readonly = !playground.config.readonly;
|
||||
}, [playground]);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = history.undoRedoService.onChange(() => {
|
||||
@ -17,7 +22,7 @@ export function Tools() {
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Space
|
||||
style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}
|
||||
>
|
||||
<button onClick={() => tools.zoomin()}>ZoomIn</button>
|
||||
@ -30,7 +35,22 @@ export function Tools() {
|
||||
<button onClick={() => history.redo()} disabled={!canRedo}>
|
||||
Redo
|
||||
</button>
|
||||
{playground.config.readonly ? (
|
||||
<IconButton
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
icon={<IconLock />}
|
||||
onClick={toggleReadonly}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
icon={<IconUnlock />}
|
||||
onClick={toggleReadonly}
|
||||
/>
|
||||
)}
|
||||
<span>{Math.floor(tools.zoom * 100)}%</span>
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
81
apps/demo-fixed-layout-simple/src/data/condition.ts
Normal file
81
apps/demo-fixed-layout-simple/src/data/condition.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
export const condition: FlowDocumentJSON = {
|
||||
nodes: [
|
||||
// 开始节点
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
data: {
|
||||
title: 'Start',
|
||||
content: 'start content',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
// 分支节点
|
||||
{
|
||||
id: 'condition_0',
|
||||
type: 'condition',
|
||||
data: {
|
||||
title: 'Condition',
|
||||
content: 'condition content',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'branch_0',
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'Branch 0',
|
||||
content: 'branch 1 content',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'custom_0',
|
||||
type: 'custom',
|
||||
data: {
|
||||
title: 'Custom',
|
||||
content: 'custom content',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'branch_1',
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'Branch 1',
|
||||
content: 'branch 1 content',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'break_0',
|
||||
type: 'break',
|
||||
data: {
|
||||
title: 'Break',
|
||||
content: 'Break content',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'branch_2',
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'Branch 2',
|
||||
content: 'branch 2 content',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// 结束节点
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
data: {
|
||||
title: 'End',
|
||||
content: 'end content',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
9
apps/demo-fixed-layout-simple/src/data/index.ts
Normal file
9
apps/demo-fixed-layout-simple/src/data/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { FlowDocumentJSON, FlowLayoutDefault } from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
import { mindmap } from './mindmap';
|
||||
import { condition } from './condition';
|
||||
|
||||
export const FLOW_LIST: Record<string, FlowDocumentJSON & { defaultLayout?: FlowLayoutDefault }> = {
|
||||
condition,
|
||||
mindmap: { ...mindmap, defaultLayout: FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT },
|
||||
};
|
||||
99
apps/demo-fixed-layout-simple/src/data/mindmap.ts
Normal file
99
apps/demo-fixed-layout-simple/src/data/mindmap.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
export const mindmap: FlowDocumentJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'multiInputs_0',
|
||||
type: 'multiInputs',
|
||||
blocks: [
|
||||
{
|
||||
id: 'input_0',
|
||||
type: 'input',
|
||||
data: {
|
||||
title: 'input_0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'input_1',
|
||||
type: 'input',
|
||||
data: {
|
||||
title: 'input_1',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: 'multiInputs_2',
|
||||
// type: 'multiInputs',
|
||||
// blocks: [
|
||||
// {
|
||||
// id: 'input_2',
|
||||
// type: 'input',
|
||||
// data: {
|
||||
// title: 'input_2'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// id: 'input',
|
||||
// type: 'input_3',
|
||||
// data: {
|
||||
// title: 'input_3'
|
||||
// },
|
||||
// }
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'multiOutputs_0',
|
||||
type: 'multiOutputs',
|
||||
data: {
|
||||
title: 'mindNode_0',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'output_0',
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'output_0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'multiOutputs_1',
|
||||
type: 'multiOutputs',
|
||||
data: {
|
||||
title: 'mindNode_1',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'output_1',
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'output_1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'output_2',
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'output_2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'output_3',
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'output_3',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'output_4',
|
||||
type: 'output',
|
||||
data: {
|
||||
title: 'output_4',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
0
apps/demo-fixed-layout-simple/src/data/slots.ts
Normal file
0
apps/demo-fixed-layout-simple/src/data/slots.ts
Normal file
0
apps/demo-fixed-layout-simple/src/data/trycatch.ts
Normal file
0
apps/demo-fixed-layout-simple/src/data/trycatch.ts
Normal file
@ -8,6 +8,7 @@ import { initialData } from './initial-data';
|
||||
import { useEditorProps } from './hooks/use-editor-props';
|
||||
import { Tools } from './components/tools';
|
||||
import { Minimap } from './components/minimap';
|
||||
import { FlowSelect } from './components/flow-select';
|
||||
|
||||
export const Editor = () => {
|
||||
const editorProps = useEditorProps(initialData, nodeRegistries);
|
||||
@ -17,6 +18,7 @@ export const Editor = () => {
|
||||
<EditorRenderer>{/* add child panel here */}</EditorRenderer>
|
||||
</div>
|
||||
<Tools />
|
||||
<FlowSelect />
|
||||
<Minimap />
|
||||
</FixedLayoutEditorProvider>
|
||||
);
|
||||
|
||||
@ -102,7 +102,7 @@ export function useEditorProps(
|
||||
enableChangeNode: true, // Listen Node engine data change
|
||||
onApply(ctx, opt) {
|
||||
// Listen change to trigger auto save
|
||||
// console.log('auto save: ', ctx.document.toJSON(), opt);
|
||||
console.log('auto save: ', ctx.document.toJSON(), opt);
|
||||
},
|
||||
},
|
||||
/**
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 360px;
|
||||
width: 240px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
@ -1,65 +1,8 @@
|
||||
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
import { condition as conditionDemo } from './data/condition';
|
||||
|
||||
/**
|
||||
* 配置流程数据,数据为 blocks 嵌套的格式
|
||||
* Initial Data
|
||||
*/
|
||||
export const initialData: FlowDocumentJSON = {
|
||||
nodes: [
|
||||
// 开始节点
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
data: {
|
||||
title: 'Start',
|
||||
content: 'start content',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
// 分支节点
|
||||
{
|
||||
id: 'condition_0',
|
||||
type: 'condition',
|
||||
data: {
|
||||
title: 'Condition',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'branch_0',
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'Branch 0',
|
||||
content: 'branch 1 content',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'custom_0',
|
||||
type: 'custom',
|
||||
data: {
|
||||
title: 'Custom',
|
||||
content: 'custrom content',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'branch_1',
|
||||
type: 'block',
|
||||
data: {
|
||||
title: 'Branch 1',
|
||||
content: 'branch 1 content',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// 结束节点
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
data: {
|
||||
title: 'End',
|
||||
content: 'end content',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
export const initialData: FlowDocumentJSON = conditionDemo;
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import { FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
|
||||
import {
|
||||
FlowNodeRegistry,
|
||||
FlowNodeEntity,
|
||||
FlowNodeBaseType,
|
||||
} from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
/**
|
||||
* 自定义节点注册
|
||||
@ -17,6 +21,7 @@ export const nodeRegistries: FlowNodeRegistry[] = [
|
||||
* - dynamicSplit: 扩展为分支节点
|
||||
* - end: 扩展为结束节点
|
||||
* - tryCatch: 扩展为 tryCatch 节点
|
||||
* - break: 分支断开
|
||||
* - default: 扩展为普通节点 (默认)
|
||||
*/
|
||||
extend: 'dynamicSplit',
|
||||
@ -72,4 +77,35 @@ export const nodeRegistries: FlowNodeRegistry[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'multiStart2',
|
||||
extend: 'dynamicSplit',
|
||||
meta: {
|
||||
isStart: true,
|
||||
},
|
||||
onCreate(node, json) {
|
||||
const doc = node.document;
|
||||
const addedNodes: FlowNodeEntity[] = [];
|
||||
const blocks = json.blocks || [];
|
||||
|
||||
if (blocks.length > 0) {
|
||||
// 水平布局
|
||||
const inlineBlocksNode = doc.addNode({
|
||||
id: `$inlineBlocks$${node.id}`,
|
||||
type: FlowNodeBaseType.INLINE_BLOCKS,
|
||||
originParent: node,
|
||||
parent: node,
|
||||
});
|
||||
addedNodes.push(inlineBlocksNode);
|
||||
blocks.forEach((blockData) => {
|
||||
doc.addBlock(node, blockData, addedNodes);
|
||||
});
|
||||
}
|
||||
return addedNodes;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'tree',
|
||||
extend: 'simpleSplit',
|
||||
},
|
||||
];
|
||||
|
||||
15
apps/demo-playground/.eslintrc.js
Normal file
15
apps/demo-playground/.eslintrc.js
Normal file
@ -0,0 +1,15 @@
|
||||
const { defineConfig } = require('@flowgram.ai/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
preset: 'web',
|
||||
packageRoot: __dirname,
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // 自动检测 React 版本
|
||||
},
|
||||
},
|
||||
});
|
||||
12
apps/demo-playground/index.html
Normal file
12
apps/demo-playground/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bundler="rspack">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Flow FreeLayoutEditor Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
52
apps/demo-playground/package.json
Normal file
52
apps/demo-playground/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@flowgram.ai/demo-playground",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"main": "./src/index.tsx",
|
||||
"files": [
|
||||
"src/",
|
||||
".eslintrc.js",
|
||||
".gitignore",
|
||||
"index.html",
|
||||
"package.json",
|
||||
"rsbuild.config.ts",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"build:fast": "exit 0",
|
||||
"build:watch": "exit 0",
|
||||
"clean": "rimraf dist",
|
||||
"dev": "cross-env MODE=app NODE_ENV=development rsbuild dev --open",
|
||||
"lint": "eslint ./src --cache",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"start": "cross-env NODE_ENV=development rsbuild dev --open",
|
||||
"test": "exit",
|
||||
"test:cov": "exit",
|
||||
"watch": "exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flowgram.ai/playground-react": "workspace:*",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@flowgram.ai/ts-config": "workspace:*",
|
||||
"@flowgram.ai/eslint-config": "workspace:*",
|
||||
"@rsbuild/core": "^1.2.16",
|
||||
"@rsbuild/plugin-react": "^1.1.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^18",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/styled-components": "^5",
|
||||
"eslint": "^8.54.0",
|
||||
"cross-env": "~7.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
14
apps/demo-playground/rsbuild.config.ts
Normal file
14
apps/demo-playground/rsbuild.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [pluginReact()],
|
||||
source: {
|
||||
entry: {
|
||||
index: './src/index.tsx',
|
||||
},
|
||||
},
|
||||
html: {
|
||||
title: 'demo-playground',
|
||||
},
|
||||
});
|
||||
66
apps/demo-playground/src/components/card.tsx
Normal file
66
apps/demo-playground/src/components/card.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { usePlayground, usePlaygroundDrag } from '@flowgram.ai/playground-react';
|
||||
|
||||
export function Card() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 200,
|
||||
height: 100,
|
||||
position: 'absolute',
|
||||
color: 'white',
|
||||
backgroundColor: 'red',
|
||||
left: 500,
|
||||
top: 500,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DragableCard() {
|
||||
const [pos, setPos] = useState({ x: 200, y: 100 });
|
||||
// 用于拖拽,拖拽到边缘时候会自动滚动画布
|
||||
const dragger = usePlaygroundDrag();
|
||||
const playground = usePlayground();
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
const startPos = { x: pos.x, y: pos.y };
|
||||
dragger.start(e, {
|
||||
onDragStart() {
|
||||
playground.config.grabDisable = true;
|
||||
// start drag
|
||||
},
|
||||
onDrag(dragEvent) {
|
||||
// 需要 除去当前的缩放比例
|
||||
setPos({
|
||||
x: startPos.x + (dragEvent.endPos.x - dragEvent.startPos.x) / dragEvent.scale,
|
||||
y: startPos.y + (dragEvent.endPos.y - dragEvent.startPos.y) / dragEvent.scale,
|
||||
});
|
||||
},
|
||||
onDragEnd() {
|
||||
playground.config.grabDisable = false;
|
||||
// end drag
|
||||
},
|
||||
});
|
||||
// e.stopPropagation();
|
||||
// e.preventDefault();
|
||||
},
|
||||
[pos]
|
||||
);
|
||||
return (
|
||||
<div
|
||||
onMouseDown={handleMouseDown}
|
||||
style={{
|
||||
cursor: 'move',
|
||||
width: 200,
|
||||
height: 100,
|
||||
position: 'absolute',
|
||||
color: 'white',
|
||||
backgroundColor: 'blue',
|
||||
left: pos.x,
|
||||
top: pos.y,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
28
apps/demo-playground/src/components/playground-tools.tsx
Normal file
28
apps/demo-playground/src/components/playground-tools.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import { usePlaygroundTools } from '@flowgram.ai/playground-react';
|
||||
|
||||
export const PlaygroundTools: React.FC<{ minZoom?: number; maxZoom?: number }> = (props) => {
|
||||
const tools = usePlaygroundTools(props);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 100,
|
||||
right: 100,
|
||||
bottom: 100,
|
||||
padding: 13,
|
||||
border: '1px solid #ccc',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 8,
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<button onClick={() => tools.toggleIneractiveType()}>{tools.interactiveType}</button>
|
||||
<button onClick={() => tools.zoomout()}>Zoom Out</button>
|
||||
<button onClick={() => tools.zoomin()}>Zoom In</button>
|
||||
<span>{Math.floor(tools.zoom * 100)}%</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
73
apps/demo-playground/src/index.tsx
Normal file
73
apps/demo-playground/src/index.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import {
|
||||
Command,
|
||||
PlaygroundReact,
|
||||
PlaygroundReactContent,
|
||||
PlaygroundReactProps,
|
||||
} from '@flowgram.ai/playground-react';
|
||||
|
||||
import { PlaygroundTools } from './components/playground-tools';
|
||||
import { Card, DragableCard } from './components/card';
|
||||
|
||||
// 加载画布样式
|
||||
import '@flowgram.ai/playground-react/index.css';
|
||||
|
||||
/**
|
||||
* 用于提供纯画布缩放能力
|
||||
*/
|
||||
export function App() {
|
||||
const playgroundProps = useMemo<PlaygroundReactProps>(
|
||||
() => ({
|
||||
// 是否增加背景
|
||||
background: true,
|
||||
playground: {
|
||||
ineractiveType: 'MOUSE', // 鼠标模式, MOUSE | PAD
|
||||
},
|
||||
// 自定义快捷键
|
||||
shortcuts(registry, ctx) {
|
||||
registry.addHandlers(
|
||||
/**
|
||||
* 放大
|
||||
*/
|
||||
{
|
||||
commandId: Command.Default.ZOOM_IN,
|
||||
shortcuts: ['meta =', 'ctrl ='],
|
||||
execute: () => {
|
||||
ctx.playground.config.zoomin();
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 缩小
|
||||
*/
|
||||
{
|
||||
commandId: Command.Default.ZOOM_OUT,
|
||||
shortcuts: ['meta -', 'ctrl -'],
|
||||
execute: () => {
|
||||
ctx.playground.config.zoomout();
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
/*
|
||||
* PlaygroundReact 画布 react 容器, background 属性可以关闭背景的点点点
|
||||
* PlaygroundReactContent 画布内容,会跟着缩放
|
||||
*/
|
||||
return (
|
||||
<PlaygroundReact {...playgroundProps}>
|
||||
<PlaygroundReactContent>
|
||||
<Card />
|
||||
<DragableCard />
|
||||
</PlaygroundReactContent>
|
||||
<PlaygroundTools />
|
||||
</PlaygroundReact>
|
||||
);
|
||||
}
|
||||
|
||||
const app = createRoot(document.getElementById('root')!);
|
||||
|
||||
app.render(<App />);
|
||||
23
apps/demo-playground/tsconfig.json
Normal file
23
apps/demo-playground/tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"experimentalDecorators": true,
|
||||
"target": "es2020",
|
||||
"module": "esnext",
|
||||
"strictPropertyInitialization": false,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": true,
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["es6", "dom", "es2020", "es2019.Array"]
|
||||
},
|
||||
"include": ["./src"],
|
||||
}
|
||||
@ -40,17 +40,17 @@ const json = ctx.document.linesManager.toJSON()
|
||||
import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'
|
||||
|
||||
// get input nodes (calculated through connection lines)
|
||||
node.geData(WorkflowNodeLinesData).inputNodes
|
||||
node.getData(WorkflowNodeLinesData).inputNodes
|
||||
// get all input nodes (recursively gets all upstream nodes)
|
||||
node.geData(WorkflowNodeLinesData).allInputNodes
|
||||
node.getData(WorkflowNodeLinesData).allInputNodes
|
||||
// get output nodes
|
||||
node.geData(WorkflowNodeLinesData).outputNodes
|
||||
node.getData(WorkflowNodeLinesData).outputNodes
|
||||
// get all output nodes
|
||||
node.geData(WorkflowNodeLinesData).allOutputNodes
|
||||
node.getData(WorkflowNodeLinesData).allOutputNodes
|
||||
// input lines
|
||||
node.geData(WorkflowNodeLinesData).inputLines
|
||||
node.getData(WorkflowNodeLinesData).inputLines
|
||||
// output lines
|
||||
node.geData(WorkflowNodeLinesData).outputLines
|
||||
node.getData(WorkflowNodeLinesData).outputLines
|
||||
```
|
||||
|
||||
## Line Configuration
|
||||
|
||||
@ -8,13 +8,13 @@ The lines in the free layout are managed by [WorkflowLinesManager](/api/core/wor
|
||||
import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'
|
||||
|
||||
// Get the input nodes of the current node (calculated through connection lines)
|
||||
node.geData(WorkflowNodeLinesData).inputNodes;
|
||||
node.getData(WorkflowNodeLinesData).inputNodes;
|
||||
// Get all input nodes (recursively get all upward)
|
||||
node.geData(WorkflowNodeLinesData).allInputNodes;
|
||||
node.getData(WorkflowNodeLinesData).allInputNodes;
|
||||
// Get the output nodes
|
||||
node.geData(WorkflowNodeLinesData).outputNodes;
|
||||
node.getData(WorkflowNodeLinesData).outputNodes;
|
||||
// Get all output nodes
|
||||
node.geData(WorkflowNodeLinesData).allOutputNodes;
|
||||
node.getData(WorkflowNodeLinesData).allOutputNodes;
|
||||
```
|
||||
|
||||
## Node listens to its own connection changes and refreshes
|
||||
|
||||
@ -43,17 +43,17 @@ const json = ctx.document.linesManager.toJSON()
|
||||
import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'
|
||||
|
||||
// 获取当前节点的输入节点(通过连接线计算)
|
||||
node.geData(WorkflowNodeLinesData).inputNodes
|
||||
node.getData(WorkflowNodeLinesData).inputNodes
|
||||
// 获取所有输入节点 (会往上递归获取所有)
|
||||
node.geData(WorkflowNodeLinesData).allInputNodes
|
||||
node.getData(WorkflowNodeLinesData).allInputNodes
|
||||
// 获取输出节点
|
||||
node.geData(WorkflowNodeLinesData).outputNodes
|
||||
node.getData(WorkflowNodeLinesData).outputNodes
|
||||
// 获取所有输出节点
|
||||
node.geData(WorkflowNodeLinesData).allOutputNodes
|
||||
node.getData(WorkflowNodeLinesData).allOutputNodes
|
||||
// 输入线条
|
||||
node.geData(WorkflowNodeLinesData).inputLines
|
||||
node.getData(WorkflowNodeLinesData).inputLines
|
||||
// 输出线条
|
||||
node.geData(WorkflowNodeLinesData).outputLines
|
||||
node.getData(WorkflowNodeLinesData).outputLines
|
||||
|
||||
```
|
||||
|
||||
|
||||
49
common/config/rush/pnpm-lock.yaml
generated
49
common/config/rush/pnpm-lock.yaml
generated
@ -480,6 +480,52 @@ importers:
|
||||
specifier: ^8.54.0
|
||||
version: 8.57.1
|
||||
|
||||
../../apps/demo-playground:
|
||||
dependencies:
|
||||
'@flowgram.ai/playground-react':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/client/playground-react
|
||||
react:
|
||||
specifier: ^18
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18
|
||||
version: 18.3.1(react@18.3.1)
|
||||
devDependencies:
|
||||
'@flowgram.ai/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/eslint-config
|
||||
'@flowgram.ai/ts-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/ts-config
|
||||
'@rsbuild/core':
|
||||
specifier: ^1.2.16
|
||||
version: 1.2.19
|
||||
'@rsbuild/plugin-react':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(@rsbuild/core@1.2.19)
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
'@types/node':
|
||||
specifier: ^18
|
||||
version: 18.19.68
|
||||
'@types/react':
|
||||
specifier: ^18
|
||||
version: 18.3.16
|
||||
'@types/react-dom':
|
||||
specifier: ^18
|
||||
version: 18.3.5(@types/react@18.3.16)
|
||||
'@types/styled-components':
|
||||
specifier: ^5
|
||||
version: 5.1.34
|
||||
cross-env:
|
||||
specifier: ~7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.54.0
|
||||
version: 8.57.1
|
||||
|
||||
../../apps/demo-react-16:
|
||||
dependencies:
|
||||
'@flowgram.ai/free-layout-editor':
|
||||
@ -1928,6 +1974,9 @@ importers:
|
||||
'@flowgram.ai/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../common/utils
|
||||
fast-equals:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.4
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
||||
@ -471,6 +471,7 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
const customDefaultRegistry = this.options.getNodeDefaultRegistry?.(type);
|
||||
let register = this.registers.get(type) || { type };
|
||||
const extendRegisters: FlowNodeRegistry[] = [];
|
||||
const extendKey = register.extend;
|
||||
// 继承重载
|
||||
if (register.extend && this.registers.has(register.extend)) {
|
||||
register = FlowNodeRegistry.merge(
|
||||
@ -505,6 +506,10 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
...register.meta,
|
||||
},
|
||||
} as T;
|
||||
// Save the "extend" attribute
|
||||
if (extendKey) {
|
||||
res.extend = extendKey;
|
||||
}
|
||||
this.nodeRegistryCache.set(typeKey, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -29,8 +29,13 @@ export enum FlowNodeBaseType {
|
||||
BLOCK_ORDER_ICON = 'blockOrderIcon', // 带顺序的图标节点,一般为 block 第一个分支节点
|
||||
GROUP = 'group', // 分组节点
|
||||
END = 'end', // 结束节点
|
||||
BREAK = 'break', // 分支结束
|
||||
CONDITION = 'condition', // 可以连接多条线的条件判断节点,目前只支持横向布局
|
||||
SUB_CANVAS = 'subCanvas', // 自由布局子画布
|
||||
MULTI_INPUTS = 'multiInputs', // 多输入
|
||||
MULTI_OUTPUTS = 'multiOutputs', // 多输出
|
||||
INPUT = 'input', // 输入节点
|
||||
OUTPUT = 'output', // 输出节点
|
||||
}
|
||||
|
||||
export enum FlowNodeSplitType {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { FlowNodeBaseType, type FlowNodeRegistry } from '@flowgram.ai/document';
|
||||
|
||||
/**
|
||||
* Break 节点, 用于分支断开
|
||||
*/
|
||||
export const BreakRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeBaseType.BREAK,
|
||||
extend: FlowNodeBaseType.END,
|
||||
};
|
||||
@ -11,3 +11,8 @@ export * from './root';
|
||||
export * from './empty';
|
||||
export * from './end';
|
||||
export * from './simple-split';
|
||||
export * from './break';
|
||||
export * from './input';
|
||||
export * from './output';
|
||||
export * from './multi-outputs';
|
||||
export * from './multi-inputs';
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
import {
|
||||
FlowNodeBaseType,
|
||||
type FlowNodeRegistry,
|
||||
type FlowTransitionLine,
|
||||
FlowTransitionLineEnum,
|
||||
LABEL_SIDE_TYPE,
|
||||
} from '@flowgram.ai/document';
|
||||
|
||||
/**
|
||||
* 输入节点
|
||||
*/
|
||||
export const InputRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeBaseType.INPUT,
|
||||
extend: FlowNodeBaseType.BLOCK,
|
||||
meta: {
|
||||
hidden: false,
|
||||
},
|
||||
getLines(transition) {
|
||||
const currentTransform = transition.transform;
|
||||
const { isVertical } = transition.entity;
|
||||
const lines: FlowTransitionLine[] = [];
|
||||
|
||||
const hasBranchDraggingAdder =
|
||||
currentTransform && currentTransform.entity.isInlineBlock && transition.renderData.draggable;
|
||||
|
||||
// 分支拖拽场景线条 push
|
||||
// 当有其余分支的时候,绘制一条两个分支之间的线条
|
||||
if (hasBranchDraggingAdder) {
|
||||
if (isVertical) {
|
||||
const currentOffsetRightX = currentTransform.firstChild?.bounds?.right || 0;
|
||||
const nextOffsetLeftX = currentTransform.next?.firstChild?.bounds?.left || 0;
|
||||
const currentInputPointY = currentTransform.inputPoint.y;
|
||||
if (currentTransform?.next) {
|
||||
lines.push({
|
||||
type: FlowTransitionLineEnum.DRAGGING_LINE,
|
||||
from: currentTransform.parent!.inputPoint,
|
||||
to: {
|
||||
x: (currentOffsetRightX + nextOffsetLeftX) / 2,
|
||||
y: currentInputPointY,
|
||||
},
|
||||
side: LABEL_SIDE_TYPE.NORMAL_BRANCH,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const currentOffsetRightY = currentTransform.firstChild?.bounds?.bottom || 0;
|
||||
const nextOffsetLeftY = currentTransform.next?.firstChild?.bounds?.top || 0;
|
||||
const currentInputPointX = currentTransform.inputPoint.x;
|
||||
if (currentTransform?.next) {
|
||||
lines.push({
|
||||
type: FlowTransitionLineEnum.DRAGGING_LINE,
|
||||
from: currentTransform.parent!.inputPoint,
|
||||
to: {
|
||||
x: currentInputPointX,
|
||||
y: (currentOffsetRightY + nextOffsetLeftY) / 2,
|
||||
},
|
||||
side: LABEL_SIDE_TYPE.NORMAL_BRANCH,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一个节点是 end 节点,不绘制 mergeLine
|
||||
if (!transition.isNodeEnd) {
|
||||
lines.push({
|
||||
type: FlowTransitionLineEnum.MERGE_LINE,
|
||||
from: currentTransform.outputPoint,
|
||||
to: currentTransform.parent!.outputPoint,
|
||||
side: LABEL_SIDE_TYPE.NORMAL_BRANCH,
|
||||
});
|
||||
}
|
||||
|
||||
return lines;
|
||||
},
|
||||
getLabels() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
import { FlowNodeBaseType, FlowNodeSplitType, type FlowNodeRegistry } from '@flowgram.ai/document';
|
||||
|
||||
/**
|
||||
* 多输入节点, 只能作为 开始节点
|
||||
* - multiInputs:
|
||||
* - inlineBlocks
|
||||
* - input
|
||||
* - input
|
||||
*/
|
||||
export const MultiInputsRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeBaseType.MULTI_INPUTS,
|
||||
extend: FlowNodeSplitType.SIMPLE_SPLIT,
|
||||
extendChildRegistries: [
|
||||
{
|
||||
type: FlowNodeBaseType.BLOCK_ICON,
|
||||
meta: {
|
||||
hidden: true,
|
||||
},
|
||||
getLines() {
|
||||
return [];
|
||||
},
|
||||
getLabels() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
{
|
||||
type: FlowNodeBaseType.INLINE_BLOCKS,
|
||||
getLabels() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
],
|
||||
getLabels() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { FlowNodeBaseType, type FlowNodeRegistry, FlowNodeSplitType } from '@flowgram.ai/document';
|
||||
|
||||
import { BlockRegistry } from './block';
|
||||
|
||||
/**
|
||||
* 多输出节点
|
||||
* - multiOutputs:
|
||||
* - blockIcon
|
||||
* - inlineBlocks
|
||||
* - output or multiOutputs
|
||||
* - output or multiOutputs
|
||||
*/
|
||||
export const MultiOuputsRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeBaseType.MULTI_OUTPUTS,
|
||||
extend: FlowNodeSplitType.SIMPLE_SPLIT,
|
||||
getLines: (transition, layout) => {
|
||||
if (transition.entity.parent?.flowNodeType === FlowNodeBaseType.INLINE_BLOCKS) {
|
||||
return BlockRegistry.getLines!(transition, layout);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { FlowNodeBaseType, type FlowNodeRegistry } from '@flowgram.ai/document';
|
||||
|
||||
/**
|
||||
* 输出节点, 一般作为 end 节点
|
||||
*/
|
||||
export const OuputRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeBaseType.OUTPUT,
|
||||
extend: FlowNodeBaseType.BLOCK,
|
||||
meta: {
|
||||
hidden: false,
|
||||
isNodeEnd: true,
|
||||
},
|
||||
};
|
||||
@ -7,12 +7,11 @@ import {
|
||||
} from '@flowgram.ai/document';
|
||||
|
||||
/**
|
||||
* 可以动态添加分支的分支节点, 无 BlockOrderIcon 节点
|
||||
* simpleSplit: (最原始的 id)
|
||||
* - simpleSplit: (最原始的 id)
|
||||
* blockIcon
|
||||
* inlineBlocks
|
||||
* block1
|
||||
* block2
|
||||
* node1
|
||||
* node2
|
||||
*/
|
||||
export const SimpleSplitRegistry: FlowNodeRegistry = {
|
||||
type: FlowNodeSplitType.SIMPLE_SPLIT,
|
||||
@ -24,23 +23,16 @@ export const SimpleSplitRegistry: FlowNodeRegistry = {
|
||||
) {
|
||||
const { document } = originParent;
|
||||
const parent = document.getNode(`$inlineBlocks$${originParent.id}`);
|
||||
// 块节点会生成一个空的 Block 节点用来切割 Block
|
||||
const proxyBlock = document.addNode({
|
||||
id: `$block$${blockData.id}`,
|
||||
type: FlowNodeBaseType.BLOCK,
|
||||
originParent,
|
||||
parent,
|
||||
});
|
||||
const realBlock = document.addNode(
|
||||
{
|
||||
...blockData,
|
||||
type: blockData.type || FlowNodeBaseType.BLOCK,
|
||||
parent: proxyBlock,
|
||||
parent,
|
||||
},
|
||||
addedNodes
|
||||
);
|
||||
addedNodes.push(proxyBlock, realBlock);
|
||||
return proxyBlock;
|
||||
addedNodes.push(realBlock);
|
||||
return realBlock;
|
||||
},
|
||||
// addChild(node, json, options = {}) {
|
||||
// const { index } = options;
|
||||
|
||||
@ -32,6 +32,11 @@ import {
|
||||
StaticSplitRegistry,
|
||||
TryCatchRegistry,
|
||||
SimpleSplitRegistry,
|
||||
BreakRegistry,
|
||||
MultiOuputsRegistry,
|
||||
MultiInputsRegistry,
|
||||
InputRegistry,
|
||||
OuputRegistry,
|
||||
} from './activities';
|
||||
|
||||
@injectable()
|
||||
@ -59,7 +64,12 @@ export class FlowRegisters
|
||||
TryCatchRegistry, // TryCatch
|
||||
EndRegistry, // 结束节点
|
||||
LoopRegistry, // 循环节点
|
||||
EmptyRegistry // 占位节点
|
||||
EmptyRegistry, // 占位节点
|
||||
BreakRegistry, // 分支断开
|
||||
MultiOuputsRegistry,
|
||||
MultiInputsRegistry,
|
||||
InputRegistry,
|
||||
OuputRegistry
|
||||
);
|
||||
/**
|
||||
* 注册节点数据 (ECS - Component)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useContext, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useContext, useMemo, useRef } from 'react';
|
||||
|
||||
import { useObserve } from '@flowgram.ai/reactive';
|
||||
import { useStartDragNode } from '@flowgram.ai/fixed-drag-plugin';
|
||||
@ -93,13 +93,17 @@ export interface NodeRenderReturnType {
|
||||
*/
|
||||
export function useNodeRender(nodeFromProps?: FlowNodeEntity): NodeRenderReturnType {
|
||||
const renderNode = nodeFromProps || useContext<FlowNodeEntity>(PlaygroundEntityContext);
|
||||
const nodeCache = useRef<FlowNodeEntity | undefined>();
|
||||
const renderData = renderNode.getData<FlowNodeRenderData>(FlowNodeRenderData)!;
|
||||
const { expanded, dragging, activated } = renderData;
|
||||
const { startDrag: startDragOrigin } = useStartDragNode();
|
||||
const playground = usePlayground();
|
||||
const isBlockOrderIcon = renderNode.flowNodeType === FlowNodeBaseType.BLOCK_ORDER_ICON;
|
||||
const isBlockIcon = renderNode.flowNodeType === FlowNodeBaseType.BLOCK_ICON;
|
||||
const node = isBlockOrderIcon || isBlockIcon ? renderNode.parent! : renderNode;
|
||||
// 在 BlockIcon 情况,如果在触发 fromJSON 时候更新表单数据导致刷新节点会存在 renderNode.parent 为 undefined,所以这里 nodeCache 进行缓存
|
||||
const node =
|
||||
(isBlockOrderIcon || isBlockIcon ? renderNode.parent! : renderNode) || nodeCache.current;
|
||||
nodeCache.current = node;
|
||||
const operationService = useService<FlowOperationService>(FlowOperationService);
|
||||
const deleteNode = useCallback(() => {
|
||||
operationService.deleteNode(node);
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
"dependencies": {
|
||||
"@flowgram.ai/reactive": "workspace:*",
|
||||
"@flowgram.ai/utils": "workspace:*",
|
||||
"fast-equals": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^4.0.2"
|
||||
},
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { cloneDeep, flatten, get } from 'lodash';
|
||||
import { clone, flatten, get } from 'lodash';
|
||||
import { shallowEqual } from 'fast-equals';
|
||||
import { Disposable, Emitter } from '@flowgram.ai/utils';
|
||||
import { ReactiveState } from '@flowgram.ai/reactive';
|
||||
|
||||
@ -78,11 +79,14 @@ export class FormModel<TValues = any> implements Disposable {
|
||||
}
|
||||
|
||||
get values() {
|
||||
return cloneDeep(this.store.values) || cloneDeep(this.initialValues);
|
||||
return clone(this.store.values) || clone(this.initialValues);
|
||||
}
|
||||
|
||||
set values(v) {
|
||||
const prevValues = this.values;
|
||||
if (shallowEqual(this.store.values || this.initialValues, v)) {
|
||||
return;
|
||||
}
|
||||
this.store.values = v;
|
||||
this.fireOnFormValuesChange({
|
||||
values: this.values,
|
||||
|
||||
@ -147,7 +147,10 @@ export class FormModelV2 extends FormModel implements Disposable {
|
||||
|
||||
updateFormValues(value: any) {
|
||||
if (this.nativeFormModel) {
|
||||
this.nativeFormModel.values = value;
|
||||
const finalValue = this.formMeta.formatOnInit
|
||||
? this.formMeta.formatOnInit(value, this.nodeContext)
|
||||
: value;
|
||||
this.nativeFormModel.values = finalValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -777,6 +777,12 @@
|
||||
"projectFolder": "apps/demo-vite",
|
||||
"versionPolicyName": "appPolicy",
|
||||
"tags": ["level-1", "team-flow", "demo"]
|
||||
},
|
||||
{
|
||||
"packageName": "@flowgram.ai/demo-playground",
|
||||
"projectFolder": "apps/demo-playground",
|
||||
"versionPolicyName": "appPolicy",
|
||||
"tags": ["level-1", "team-flow", "demo"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user