index.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import React from "react";
  2. import {
  3. ReactZoomPanPinchRef,
  4. TransformComponent,
  5. TransformWrapper,
  6. } from "react-zoom-pan-pinch";
  7. import { Canvas, Edge, ElkRoot, useSelection } 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 useNodeTools from "src/hooks/store/useNodeTools";
  15. interface LayoutProps {
  16. isWidget: boolean;
  17. }
  18. const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
  19. position: absolute;
  20. width: 100%;
  21. height: ${({ isWidget }) => (isWidget ? "100vh" : "calc(100vh - 36px)")};
  22. background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
  23. :active {
  24. cursor: move;
  25. }
  26. rect {
  27. fill: ${({ theme }) => theme.BACKGROUND_NODE};
  28. }
  29. `;
  30. const MemoizedGraph = React.memo(function Layout({ isWidget }: LayoutProps) {
  31. const json = useConfig((state) => state.json);
  32. const [nodes, edges, newNodes, newEdges, selectedNode] = useNodeTools(
  33. (state) => [
  34. state.nodes,
  35. state.edges,
  36. state.newNodes,
  37. state.newEdges,
  38. state.selectedNode,
  39. ],
  40. shallow
  41. );
  42. const setNodeTools = useNodeTools((state) => state.setNodeTools);
  43. const [size, setSize] = React.useState({
  44. width: 2000,
  45. height: 2000,
  46. });
  47. const setConfig = useConfig((state) => state.setConfig);
  48. const [expand, layout] = useConfig(
  49. (state) => [state.expand, state.layout],
  50. shallow
  51. );
  52. React.useEffect(() => {
  53. const { nodes, edges } = getEdgeNodes(json, expand);
  54. setNodeTools("nodes", nodes);
  55. setNodeTools("edges", edges);
  56. setNodeTools("newNodes", nodes);
  57. setNodeTools("newEdges", edges);
  58. }, [json, expand]);
  59. const onInit = (ref: ReactZoomPanPinchRef) => {
  60. setConfig("zoomPanPinch", ref);
  61. };
  62. const onLayoutChange = (layout: ElkRoot) => {
  63. if (layout.width && layout.height)
  64. setSize({ width: layout.width, height: layout.height });
  65. };
  66. const { selections, onClick, removeSelection } = useSelection({
  67. nodes,
  68. edges,
  69. onSelection: (s) => {
  70. if (!isWidget) {
  71. if (s[0] === selectedNode) {
  72. removeSelection(selectedNode);
  73. } else {
  74. setNodeTools("selectedNode", s[0]);
  75. }
  76. }
  77. },
  78. });
  79. return (
  80. <StyledEditorWrapper isWidget={isWidget}>
  81. <TransformWrapper
  82. onInit={onInit}
  83. maxScale={1.8}
  84. minScale={0.4}
  85. initialScale={0.7}
  86. wheel={{
  87. step: 0.05,
  88. }}
  89. >
  90. <TransformComponent
  91. wrapperStyle={{
  92. width: "100%",
  93. height: "100%",
  94. overflow: "hidden",
  95. }}
  96. >
  97. <Canvas
  98. nodes={nodes}
  99. edges={edges}
  100. maxWidth={size.width + 100}
  101. maxHeight={size.height + 100}
  102. direction={layout}
  103. key={layout}
  104. onLayoutChange={onLayoutChange}
  105. selections={selections}
  106. node={(props) =>
  107. newNodes.find((n) => n.id === props.id) ? (
  108. <CustomNode
  109. onClick={(e) => onClick && onClick(e, props)}
  110. {...props}
  111. />
  112. ) : (
  113. <></>
  114. )
  115. }
  116. edge={(props) =>
  117. newEdges.find((e) => e.id === props.id) ? <Edge /> : <></>
  118. }
  119. zoomable={false}
  120. readonly
  121. />
  122. </TransformComponent>
  123. </TransformWrapper>
  124. </StyledEditorWrapper>
  125. );
  126. });
  127. export const Graph = ({ isWidget = false }: { isWidget?: boolean }) => {
  128. const [newNodes, selectedNode, copySelectedNode] = useNodeTools(
  129. (state) => [state.nodes, state.selectedNode, state.copySelectedNode],
  130. shallow
  131. );
  132. const setNodeTools = useNodeTools((state) => state.setNodeTools);
  133. const selectedNodeObject = newNodes.find((n) => n.id === selectedNode);
  134. return (
  135. <>
  136. <MemoizedGraph isWidget={isWidget} />
  137. {!isWidget && (
  138. <NodeModal
  139. selectedNode={selectedNodeObject?.text}
  140. visible={copySelectedNode}
  141. closeModal={() => setNodeTools("copySelectedNode", false)}
  142. />
  143. )}
  144. </>
  145. );
  146. };