From 0acb2db9b68c3a10fcfaba292a07c5947f7a6d10 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Fri, 1 Mar 2024 14:28:40 +0800 Subject: [PATCH] layout --- web/app/(commonLayout)/workflow/page.tsx | 30 ++++++++++++++--------- web/app/components/workflow/hooks.ts | 31 +++++++++++++++++++++--- web/app/components/workflow/index.tsx | 12 +++++---- web/app/components/workflow/style.css | 3 +++ web/app/components/workflow/utils.ts | 24 ++++++++++++++++++ web/package.json | 2 ++ web/yarn.lock | 22 ++++++++++++++++- 7 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 web/app/components/workflow/style.css diff --git a/web/app/(commonLayout)/workflow/page.tsx b/web/app/(commonLayout)/workflow/page.tsx index a9fd6f66a8..4c5319ee77 100644 --- a/web/app/(commonLayout)/workflow/page.tsx +++ b/web/app/(commonLayout)/workflow/page.tsx @@ -8,13 +8,13 @@ const initialNodes = [ { id: '1', type: 'custom', - position: { x: 130, y: 130 }, + position: { x: 0, y: 0 }, data: { type: 'start' }, }, { id: '2', type: 'custom', - position: { x: 434, y: 130 }, + position: { x: 0, y: 0 }, data: { type: 'if-else', branches: [ @@ -32,21 +32,28 @@ const initialNodes = [ { id: '3', type: 'custom', - position: { x: 738, y: 130 }, + position: { x: 0, y: 0 }, data: { type: 'question-classifier', sortIndexInBranches: 0 }, }, { id: '4', type: 'custom', - position: { x: 738, y: 330 }, - data: { type: 'variable-assigner', sortIndexInBranches: 1 }, + position: { x: 0, y: 0 }, + data: { + type: 'if-else', + sortIndexInBranches: 1, + branches: [ + { + id: 'if-true', + name: 'IS TRUE', + }, + { + id: 'if-false', + name: 'IS FALSE', + }, + ], + }, }, - // { - // id: '5', - // type: 'custom', - // position: { x: 1100, y: 130 }, - // data: { type: 'llm' }, - // }, ] const initialEdges = [ @@ -57,7 +64,6 @@ const initialEdges = [ sourceHandle: 'source', target: '2', targetHandle: 'target', - deletable: false, }, { id: '1', diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts index dd4cbf8c8b..0f4e1162d3 100644 --- a/web/app/components/workflow/hooks.ts +++ b/web/app/components/workflow/hooks.ts @@ -16,10 +16,32 @@ import type { SelectedNode, } from './types' import { NodeInitialData } from './constants' +import { getLayoutByDagre } from './utils' export const useWorkflow = () => { const store = useStoreApi() + const handleLayout = useCallback(async () => { + const { + getNodes, + edges, + setNodes, + } = store.getState() + + const layout = getLayoutByDagre(getNodes(), edges) + + const newNodes = produce(getNodes(), (draft) => { + draft.forEach((node) => { + const nodeWithPosition = layout.node(node.id) + node.position = { + x: nodeWithPosition.x, + y: nodeWithPosition.y, + } + }) + }) + setNodes(newNodes) + }, [store]) + const handleEnterNode = useCallback((_, node) => { const { getNodes, @@ -112,7 +134,8 @@ export const useWorkflow = () => { return filtered }) setEdges(newEdges) - }, [store]) + handleLayout() + }, [store, handleLayout]) const handleEnterEdge = useCallback((_, edge) => { const { @@ -152,7 +175,8 @@ export const useWorkflow = () => { draft.splice(index, 1) }) setEdges(newEdges) - }, [store]) + handleLayout() + }, [store, handleLayout]) const handleUpdateNodeData = useCallback(({ id, data }: SelectedNode) => { const { @@ -182,7 +206,7 @@ export const useWorkflow = () => { data: NodeInitialData[nodeType], position: { x: currentNode.position.x + 304, - y: currentNode.position.y, + y: 0, }, selected: true, } @@ -289,5 +313,6 @@ export const useWorkflow = () => { handleAddNextNode, handleChangeCurrentNode, handleDeleteNode, + handleLayout, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 498aac64ea..1f33736b61 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -9,6 +9,7 @@ import ReactFlow, { useNodesState, } from 'reactflow' import 'reactflow/dist/style.css' +import './style.css' import type { Edge, Node, @@ -41,11 +42,6 @@ const Workflow: FC = memo(({ const [edges, _, onEdgesChange] = useEdgesState(initialEdges) const nodesInitialized = useNodesInitialized() - useEffect(() => { - if (nodesInitialized) - console.log('initialed') - }, [nodesInitialized]) - const { handleEnterNode, handleLeaveNode, @@ -53,8 +49,14 @@ const Workflow: FC = memo(({ handleEnterEdge, handleLeaveEdge, handleDeleteEdge, + handleLayout, } = useWorkflow() + useEffect(() => { + if (nodesInitialized) + handleLayout() + }, [nodesInitialized, handleLayout]) + useKeyPress('Backspace', handleDeleteEdge) return ( diff --git a/web/app/components/workflow/style.css b/web/app/components/workflow/style.css new file mode 100644 index 0000000000..3cabb3e7ff --- /dev/null +++ b/web/app/components/workflow/style.css @@ -0,0 +1,3 @@ +.react-flow__node { + transition: transform 0.2s ease-in-out; +} \ No newline at end of file diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 257ded1027..812604d4ef 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -3,6 +3,7 @@ import { getConnectedEdges, getOutgoers, } from 'reactflow' +import dagre from 'dagre' import type { Edge, Node, @@ -118,3 +119,26 @@ export const getNodesPositionMap = (nodes: Node[], edges: Edge[]) => { return positionMap } + +export const getLayoutByDagre = (nodes: Node[], edges: Edge[]) => { + const dagreGraph = new dagre.graphlib.Graph() + dagreGraph.setGraph({ + rankdir: 'LR', + align: 'UL', + nodesep: 40, + ranksep: 64, + }) + nodes.forEach((node) => { + dagreGraph.setNode(node.id, { width: node.width, height: node.height }) + }) + + edges.forEach((edge) => { + dagreGraph.setEdge(edge.source, edge.target, { + weight: edge?.data?.weight || 1, + }) + }) + + dagre.layout(dagreGraph) + + return dagreGraph +} diff --git a/web/package.json b/web/package.json index fe9c60778b..6abe8ec8c0 100644 --- a/web/package.json +++ b/web/package.json @@ -32,6 +32,7 @@ "classnames": "^2.3.2", "copy-to-clipboard": "^3.3.3", "crypto-js": "^4.2.0", + "dagre": "^0.8.5", "dayjs": "^1.11.7", "echarts": "^5.4.1", "echarts-for-react": "^3.0.2", @@ -87,6 +88,7 @@ "@faker-js/faker": "^7.6.0", "@rgrove/parse-xml": "^4.1.0", "@types/crypto-js": "^4.1.1", + "@types/dagre": "^0.7.52", "@types/js-cookie": "^3.0.3", "@types/lodash-es": "^4.17.7", "@types/negotiator": "^0.6.1", diff --git a/web/yarn.lock b/web/yarn.lock index 058a347b8b..c3ef17f1bc 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -919,6 +919,11 @@ "@types/d3-transition" "*" "@types/d3-zoom" "*" +"@types/dagre@^0.7.52": + version "0.7.52" + resolved "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.52.tgz" + integrity sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw== + "@types/debug@^4.0.0": version "4.1.8" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz" @@ -2289,6 +2294,14 @@ dagre-d3-es@7.0.10: d3 "^7.8.2" lodash-es "^4.17.21" +dagre@^0.8.5: + version "0.8.5" + resolved "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" @@ -3374,6 +3387,13 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" @@ -4384,7 +4404,7 @@ lodash.values@^4.3.0: resolved "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz" integrity sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q== -lodash@^4.17.21: +lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==