index.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import React from "react";
  2. import {
  3. ReactZoomPanPinchRef,
  4. TransformComponent,
  5. TransformWrapper,
  6. } from "react-zoom-pan-pinch";
  7. import {
  8. Canvas,
  9. Edge,
  10. ElkRoot,
  11. NodeData,
  12. } from "reaflow";
  13. import { CustomNode } from "src/components/CustomNode";
  14. import { NodeModal } from "src/containers/Modals/NodeModal";
  15. import { getEdgeNodes } from "src/containers/Editor/LiveEditor/helpers";
  16. import useConfig from "src/hooks/store/useConfig";
  17. import styled from "styled-components";
  18. import shallow from "zustand/shallow";
  19. import useGraph from "src/hooks/store/useGraph";
  20. interface LayoutProps {
  21. isWidget: boolean;
  22. openModal: () => void;
  23. setSelectedNode: (node: object) => void;
  24. }
  25. const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
  26. position: absolute;
  27. width: 100%;
  28. height: ${({ isWidget }) => (isWidget ? "100vh" : "calc(100vh - 36px)")};
  29. background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
  30. :active {
  31. cursor: move;
  32. }
  33. rect {
  34. fill: ${({ theme }) => theme.BACKGROUND_NODE};
  35. }
  36. `;
  37. const MemoizedGraph = React.memo(function Layout({
  38. isWidget,
  39. openModal,
  40. setSelectedNode,
  41. }: LayoutProps) {
  42. const json = useConfig((state) => state.json);
  43. const nodes = useGraph((state) => state.nodes);
  44. const edges = useGraph((state) => state.edges);
  45. const setGraphValue = useGraph((state) => state.setGraphValue);
  46. const [size, setSize] = React.useState({
  47. width: 2000,
  48. height: 2000,
  49. });
  50. const setConfig = useConfig((state) => state.setConfig);
  51. const [expand, layout] = useConfig(
  52. (state) => [state.expand, state.layout],
  53. shallow
  54. );
  55. const handleNodeClick = React.useCallback(
  56. (e: React.MouseEvent<SVGElement>, data: NodeData) => {
  57. setSelectedNode(data.text);
  58. openModal();
  59. },
  60. [openModal, setSelectedNode]
  61. );
  62. const onInit = (ref: ReactZoomPanPinchRef) => {
  63. setConfig("zoomPanPinch", ref);
  64. };
  65. const onLayoutChange = (layout: ElkRoot) => {
  66. if (layout.width && layout.height)
  67. setSize({ width: layout.width, height: layout.height });
  68. };
  69. React.useEffect(() => {
  70. const { nodes, edges } = getEdgeNodes(json, expand);
  71. setGraphValue("nodes", nodes);
  72. setGraphValue("edges", edges);
  73. }, [expand, json, setGraphValue]);
  74. return (
  75. <StyledEditorWrapper isWidget={isWidget}>
  76. <TransformWrapper
  77. onInit={onInit}
  78. maxScale={1.8}
  79. minScale={0.4}
  80. initialScale={0.7}
  81. wheel={{
  82. step: 0.05,
  83. }}
  84. doubleClick={{
  85. disabled: true,
  86. }}
  87. >
  88. <TransformComponent
  89. wrapperStyle={{
  90. width: "100%",
  91. height: "100%",
  92. overflow: "hidden",
  93. }}
  94. >
  95. <Canvas
  96. nodes={nodes}
  97. edges={edges}
  98. maxWidth={size.width + 100}
  99. maxHeight={size.height + 100}
  100. direction={layout}
  101. key={layout}
  102. onLayoutChange={onLayoutChange}
  103. node={(props) => (
  104. <CustomNode {...props} onClick={handleNodeClick} />
  105. )}
  106. edge={(props) => (
  107. <Edge {...props} containerClassName={`edge-${props.id}`} />
  108. )}
  109. zoomable={false}
  110. readonly
  111. />
  112. </TransformComponent>
  113. </TransformWrapper>
  114. </StyledEditorWrapper>
  115. );
  116. });
  117. export const Graph = ({ isWidget = false }: { isWidget?: boolean }) => {
  118. const [isModalVisible, setModalVisible] = React.useState(false);
  119. const [selectedNode, setSelectedNode] = React.useState<object>({});
  120. const openModal = React.useCallback(() => setModalVisible(true), []);
  121. const collapsedNodes = useGraph((state) => state.collapsedNodes);
  122. const collapsedEdges = useGraph((state) => state.collapsedEdges);
  123. React.useEffect(() => {
  124. const nodeList = collapsedNodes.map((id) => `[id*="node-${id}"]`);
  125. const edgeList = collapsedEdges.map((id) => `[class*="edge-${id}"]`);
  126. const nodes = document.querySelectorAll('[id*="node-"]');
  127. const edges = document.querySelectorAll('[class*="edge-"]');
  128. nodes.forEach((node) => node.classList.remove("hide"));
  129. edges.forEach((edges) => edges.classList.remove("hide"));
  130. if (nodeList.length) {
  131. const selectedNodes = document.querySelectorAll(nodeList.join(","));
  132. const selectedEdges = document.querySelectorAll(edgeList.join(","));
  133. selectedNodes.forEach((node) => node.classList.add("hide"));
  134. selectedEdges.forEach((edge) => edge.classList.add("hide"));
  135. }
  136. }, [collapsedNodes, collapsedEdges]);
  137. return (
  138. <>
  139. <MemoizedGraph
  140. openModal={openModal}
  141. setSelectedNode={setSelectedNode}
  142. isWidget={isWidget}
  143. />
  144. {!isWidget && (
  145. <NodeModal
  146. selectedNode={selectedNode}
  147. visible={isModalVisible}
  148. closeModal={() => setModalVisible(false)}
  149. />
  150. )}
  151. </>
  152. );
  153. };