index.tsx 4.7 KB

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