use-nodes-layout.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useCallback } from 'react'
  2. import ELK from 'elkjs/lib/elk.bundled.js'
  3. import {
  4. useReactFlow,
  5. useStoreApi,
  6. } from 'reactflow'
  7. import { cloneDeep } from 'lodash-es'
  8. import type {
  9. Edge,
  10. Node,
  11. } from '../types'
  12. import { useWorkflowStore } from '../store'
  13. import { AUTO_LAYOUT_OFFSET } from '../constants'
  14. import { useNodesSyncDraft } from './use-nodes-sync-draft'
  15. const layoutOptions = {
  16. 'elk.algorithm': 'layered',
  17. 'elk.direction': 'RIGHT',
  18. 'elk.layered.spacing.nodeNodeBetweenLayers': '60',
  19. 'elk.spacing.nodeNode': '40',
  20. 'elk.layered.nodePlacement.strategy': 'SIMPLE',
  21. }
  22. const elk = new ELK()
  23. export const getLayoutedNodes = async (nodes: Node[], edges: Edge[]) => {
  24. const graph = {
  25. id: 'root',
  26. layoutOptions,
  27. children: nodes.map((n) => {
  28. return {
  29. ...n,
  30. width: n.width ?? 150,
  31. height: n.height ?? 50,
  32. targetPosition: 'left',
  33. sourcePosition: 'right',
  34. }
  35. }),
  36. edges: cloneDeep(edges),
  37. }
  38. const layoutedGraph = await elk.layout(graph as any)
  39. const layoutedNodes = nodes.map((node) => {
  40. const layoutedNode = layoutedGraph.children?.find(
  41. lgNode => lgNode.id === node.id,
  42. )
  43. return {
  44. ...node,
  45. position: {
  46. x: (layoutedNode?.x ?? 0) + AUTO_LAYOUT_OFFSET.x,
  47. y: (layoutedNode?.y ?? 0) + AUTO_LAYOUT_OFFSET.y,
  48. },
  49. }
  50. })
  51. return {
  52. layoutedNodes,
  53. }
  54. }
  55. export const useNodesLayout = () => {
  56. const store = useStoreApi()
  57. const reactflow = useReactFlow()
  58. const workflowStore = useWorkflowStore()
  59. const { handleSyncWorkflowDraft } = useNodesSyncDraft()
  60. const handleNodesLayout = useCallback(async () => {
  61. workflowStore.setState({ nodeAnimation: true })
  62. const {
  63. getNodes,
  64. edges,
  65. setNodes,
  66. } = store.getState()
  67. const { setViewport } = reactflow
  68. const nodes = getNodes()
  69. const {
  70. layoutedNodes,
  71. } = await getLayoutedNodes(nodes, edges)
  72. setNodes(layoutedNodes)
  73. const zoom = 0.7
  74. setViewport({
  75. x: 0,
  76. y: 0,
  77. zoom,
  78. })
  79. setTimeout(() => {
  80. handleSyncWorkflowDraft()
  81. })
  82. }, [store, reactflow, handleSyncWorkflowDraft, workflowStore])
  83. return {
  84. handleNodesLayout,
  85. }
  86. }