graphModel.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { useState, useRef, useEffect } from 'react'
  2. import { Cell, Graph, Node } from '@antv/x6'
  3. import { Dnd } from '@antv/x6-plugin-dnd'
  4. import { Transform } from '@antv/x6-plugin-transform'
  5. import { Snapline } from '@antv/x6-plugin-snapline'
  6. import { Clipboard } from '@antv/x6-plugin-clipboard'
  7. import { Selection } from '@repo/x6-plugin-selection/src/index'
  8. import { History } from '@antv/x6-plugin-history'
  9. import { Keyboard } from '@antv/x6-plugin-keyboard'
  10. import { Export } from '@antv/x6-plugin-export'
  11. import { useModel } from 'umi'
  12. import '@/components/PageContainer'
  13. import { handleGraphEvent } from '@/events/flowEvent'
  14. import { pageMenu, nodeMenu} from '@/utils/contentMenu';
  15. import { bindKeys } from '@/utils/fastKey'
  16. export default function GraphModel() {
  17. const [graph, setGraph] = useState<Graph>();
  18. const [dnd, setDnd] = useState<Dnd>();
  19. const pageNodeRef = useRef<Node>();
  20. const dndRef = useRef(dnd);
  21. // 画布
  22. const graphRef = useRef<Graph>();
  23. const { pageState } = useModel('appModel');
  24. // 当前选中的节点
  25. const [selectedCell, setSelectedCell] = useState<Cell[]>([]);
  26. const [canRedo, setCanRedo] = useState(false);
  27. const [canUndo, setCanUndo] = useState(false);
  28. /**初始化页面节点 */
  29. useEffect(() => {
  30. if(pageState.width && graphRef.current && !pageNodeRef.current) {
  31. const graph = graphRef.current;
  32. pageNodeRef.current = graph?.addNode({
  33. shape: 'page-container-node',
  34. width: pageState.width,
  35. height: pageState.height,
  36. zIndex: -1,
  37. data: {
  38. isPage: true,
  39. ignoreDrag: true,
  40. ...pageState
  41. },
  42. tools: [
  43. {
  44. name: 'contextmenu',
  45. args: {
  46. menu: pageMenu,
  47. },
  48. },
  49. ]
  50. });
  51. };
  52. }, [pageState.width, graphRef.current])
  53. useEffect(() => {
  54. pageNodeRef.current?.setData({
  55. background: pageState.backgroundColor,
  56. ...pageState
  57. });
  58. pageNodeRef.current?.setSize({
  59. width: pageState.width,
  60. height: pageState.height
  61. })
  62. }, [pageState]);
  63. const enabledTransform = (node: Node) => {
  64. const data = node.getData<{ isPage: boolean, lock: boolean }>();
  65. return !data?.isPage && !data?.lock;
  66. }
  67. /**初始化画布 */
  68. const initGraph = (instance: Graph) => {
  69. // 添加插件
  70. instance
  71. .use(
  72. new Transform({
  73. resizing: {
  74. enabled: enabledTransform,
  75. minWidth: 20,
  76. minHeight: 20
  77. },
  78. rotating: {
  79. enabled: enabledTransform,
  80. grid: 1
  81. },
  82. }),
  83. )
  84. .use(
  85. new Selection({
  86. enabled: true,
  87. multiple: true,
  88. rubberband: true,
  89. movable: true,
  90. showNodeSelectionBox: true,
  91. // showEdgeSelectionBox: true,
  92. pointerEvents: 'none',
  93. strict: true,
  94. filter: (cell: Cell) => {
  95. const data = cell.getData<{ isPage: boolean, lock: boolean }>();
  96. return !data?.isPage && !data?.lock;
  97. },
  98. }),
  99. )
  100. .use(new Snapline({
  101. sharp: true,
  102. resizing: true
  103. }))
  104. .use(new Keyboard({
  105. enabled: true,
  106. global: true,
  107. }))
  108. .use(new Clipboard())
  109. .use(new History({
  110. enabled: true,
  111. beforeAddCommand: (event, args) => {
  112. // @ts-ignore 排除不用创建的节点
  113. if(args?.cell?.data?.noCreate) return false;
  114. // @ts-ignore 排除页面节点
  115. return !(event === 'cell:added' && args?.cell?.getData()?.isPage);
  116. },
  117. }))
  118. .use(new Export());
  119. setGraph(instance);
  120. graphRef.current = instance;
  121. // 选中的节点/边发生改变(增删)时触发
  122. instance.on('selection:changed', ({selected}: {added: Cell[]; removed: Cell[]; selected: Cell[];}) => {
  123. setSelectedCell(selected);
  124. })
  125. instance.on('history:change', () => {
  126. setCanRedo(instance.canRedo());
  127. setCanUndo(instance.canUndo());
  128. })
  129. // 通用事件处理
  130. handleGraphEvent(instance);
  131. // 绑定快捷键
  132. bindKeys(instance);
  133. }
  134. /**初始化拖拽 */
  135. const initDnd = (instance: Dnd) => {
  136. setDnd(instance);
  137. dndRef.current = instance;
  138. }
  139. /**组件库拖拽生成 */
  140. const startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, node: Node.Metadata) => {
  141. if(!dndRef.current || !graphRef.current) return;
  142. // 往画布添加节点
  143. const n = graphRef.current.createNode(node);
  144. // 右键菜单
  145. n.addTools({
  146. name: 'contextmenu',
  147. args: {
  148. menu: nodeMenu,
  149. },
  150. });
  151. dndRef.current.start(n, e.nativeEvent as any)
  152. };
  153. // 撤销
  154. const onUndo = () => {
  155. graphRef.current?.undo();
  156. }
  157. // 重做
  158. const onRedo = () => {
  159. graphRef.current?.redo();
  160. }
  161. return {
  162. graph,
  163. dnd,
  164. initGraph,
  165. initDnd,
  166. startDrag,
  167. selectedCell,
  168. canRedo,
  169. canUndo,
  170. onUndo,
  171. onRedo
  172. };
  173. };