useGraph.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
  2. import { CanvasDirection } from "reaflow/dist/layout/elkLayout";
  3. import { parser } from "src/utils/core/jsonParser";
  4. import { getChildrenEdges } from "src/utils/getChildrenEdges";
  5. import { getOutgoers } from "src/utils/getOutgoers";
  6. import { create } from "zustand";
  7. import useJson from "./useJson";
  8. const initialStates = {
  9. zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
  10. direction: "RIGHT" as CanvasDirection,
  11. loading: true,
  12. graphCollapsed: false,
  13. foldNodes: false,
  14. fullscreen: false,
  15. nodes: [] as NodeData[],
  16. edges: [] as EdgeData[],
  17. collapsedNodes: [] as string[],
  18. collapsedEdges: [] as string[],
  19. collapsedParents: [] as string[],
  20. selectedNode: {} as NodeData,
  21. path: "",
  22. };
  23. export type Graph = typeof initialStates;
  24. interface GraphActions {
  25. setGraph: (json?: string, options?: Partial<Graph>[]) => void;
  26. setLoading: (loading: boolean) => void;
  27. setDirection: (direction: CanvasDirection) => void;
  28. setZoomPanPinch: (ref: ReactZoomPanPinchRef) => void;
  29. expandNodes: (nodeId: string) => void;
  30. collapseNodes: (nodeId: string) => void;
  31. collapseGraph: () => void;
  32. expandGraph: () => void;
  33. toggleFold: (value: boolean) => void;
  34. toggleFullscreen: (value: boolean) => void;
  35. zoomIn: () => void;
  36. zoomOut: () => void;
  37. centerView: () => void;
  38. setSelectedNode: ({ nodeData}: { nodeData: NodeData }) => void;
  39. }
  40. const useGraph = create<Graph & GraphActions>((set, get) => ({
  41. ...initialStates,
  42. setSelectedNode: ({ nodeData}) => set({ selectedNode: nodeData}),
  43. setGraph: (data, options) => {
  44. const { nodes, edges } = parser(data ?? useJson.getState().json);
  45. set({
  46. nodes,
  47. edges,
  48. collapsedParents: [],
  49. collapsedNodes: [],
  50. collapsedEdges: [],
  51. graphCollapsed: false,
  52. loading: true,
  53. ...options,
  54. });
  55. },
  56. setDirection: direction => set({ direction }),
  57. setLoading: loading => set({ loading }),
  58. expandNodes: nodeId => {
  59. const [childrenNodes, matchingNodes] = getOutgoers(
  60. nodeId,
  61. get().nodes,
  62. get().edges,
  63. get().collapsedParents
  64. );
  65. const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
  66. let nodesConnectedToParent = childrenEdges.reduce((nodes: string[], edge) => {
  67. edge.from && !nodes.includes(edge.from) && nodes.push(edge.from);
  68. edge.to && !nodes.includes(edge.to) && nodes.push(edge.to);
  69. return nodes;
  70. }, []);
  71. const matchingNodesConnectedToParent = matchingNodes.filter(node =>
  72. nodesConnectedToParent.includes(node)
  73. );
  74. const nodeIds = childrenNodes.map(node => node.id).concat(matchingNodesConnectedToParent);
  75. const edgeIds = childrenEdges.map(edge => edge.id);
  76. const collapsedParents = get().collapsedParents.filter(cp => cp !== nodeId);
  77. const collapsedNodes = get().collapsedNodes.filter(nodeId => !nodeIds.includes(nodeId));
  78. const collapsedEdges = get().collapsedEdges.filter(edgeId => !edgeIds.includes(edgeId));
  79. set({
  80. collapsedParents,
  81. collapsedNodes,
  82. collapsedEdges,
  83. graphCollapsed: !!collapsedNodes.length,
  84. });
  85. },
  86. collapseNodes: nodeId => {
  87. const [childrenNodes] = getOutgoers(nodeId, get().nodes, get().edges);
  88. const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
  89. const nodeIds = childrenNodes.map(node => node.id);
  90. const edgeIds = childrenEdges.map(edge => edge.id);
  91. set({
  92. collapsedParents: get().collapsedParents.concat(nodeId),
  93. collapsedNodes: get().collapsedNodes.concat(nodeIds),
  94. collapsedEdges: get().collapsedEdges.concat(edgeIds),
  95. graphCollapsed: !!get().collapsedNodes.concat(nodeIds).length,
  96. });
  97. },
  98. collapseGraph: () => {
  99. const edges = get().edges;
  100. const tos = edges.map(edge => edge.to);
  101. const froms = edges.map(edge => edge.from);
  102. const parentNodesIds = froms.filter(id => !tos.includes(id));
  103. const secondDegreeNodesIds = edges
  104. .filter(edge => parentNodesIds.includes(edge.from))
  105. .map(edge => edge.to);
  106. const collapsedParents = get()
  107. .nodes.filter(node => !parentNodesIds.includes(node.id) && node.data.isParent)
  108. .map(node => node.id);
  109. const collapsedNodes = get()
  110. .nodes.filter(
  111. node => !parentNodesIds.includes(node.id) && !secondDegreeNodesIds.includes(node.id)
  112. )
  113. .map(node => node.id);
  114. set({
  115. collapsedParents,
  116. collapsedNodes,
  117. collapsedEdges: get()
  118. .edges.filter(edge => !parentNodesIds.includes(edge.from))
  119. .map(edge => edge.id),
  120. graphCollapsed: true,
  121. });
  122. },
  123. expandGraph: () => {
  124. set({
  125. collapsedNodes: [],
  126. collapsedEdges: [],
  127. collapsedParents: [],
  128. graphCollapsed: false,
  129. });
  130. },
  131. zoomIn: () => {
  132. const zoomPanPinch = get().zoomPanPinch;
  133. if (zoomPanPinch) {
  134. zoomPanPinch.setTransform(
  135. zoomPanPinch?.state.positionX,
  136. zoomPanPinch?.state.positionY,
  137. zoomPanPinch?.state.scale + 0.4
  138. );
  139. }
  140. },
  141. zoomOut: () => {
  142. const zoomPanPinch = get().zoomPanPinch;
  143. if (zoomPanPinch) {
  144. zoomPanPinch.setTransform(
  145. zoomPanPinch?.state.positionX,
  146. zoomPanPinch?.state.positionY,
  147. zoomPanPinch?.state.scale - 0.4
  148. );
  149. }
  150. },
  151. centerView: () => {
  152. const zoomPanPinch = get().zoomPanPinch;
  153. const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
  154. if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
  155. },
  156. toggleFold: foldNodes => {
  157. set({ foldNodes });
  158. get().setGraph();
  159. },
  160. toggleFullscreen: fullscreen => set({ fullscreen }),
  161. setZoomPanPinch: zoomPanPinch => set({ zoomPanPinch }),
  162. }));
  163. export default useGraph;