index.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import React from "react";
  2. import styled from "styled-components";
  3. import {
  4. TransformWrapper,
  5. TransformComponent,
  6. ReactZoomPanPinchRef,
  7. } from "react-zoom-pan-pinch";
  8. import { Canvas } from "reaflow";
  9. import { getEdgeNodes } from "./helpers";
  10. import { CustomNode } from "./CustomNode";
  11. import { useLoading } from "src/hooks/useLoading";
  12. import { useConfig } from "src/hocs/config";
  13. import { Tools } from "../Editor/Tools";
  14. import { ConfigActionType } from "src/reducer/reducer";
  15. const StyledLiveEditor = styled.div`
  16. position: relative;
  17. `;
  18. const StyledEditorWrapper = styled.div`
  19. position: absolute;
  20. `;
  21. const StyledControls = styled.div`
  22. position: fixed;
  23. display: grid;
  24. grid-template-columns: 1fr 1fr;
  25. grid-template-rows: 1fr 1fr;
  26. gap: 8px;
  27. bottom: 8px;
  28. right: 8px;
  29. opacity: 0.9;
  30. button:hover {
  31. opacity: 0.7;
  32. }
  33. `;
  34. const wheelOptions = {
  35. step: 0.4,
  36. };
  37. export const LiveEditor: React.FC = React.memo(function LiveEditor() {
  38. const {
  39. states: { json, settings },
  40. dispatch,
  41. } = useConfig();
  42. const pageLoaded = useLoading();
  43. const wrapperRef = React.useRef<ReactZoomPanPinchRef | null>(null);
  44. const [data, setData] = React.useState({
  45. nodes: [],
  46. edges: [],
  47. });
  48. React.useEffect(() => {
  49. const { nodes, edges } = getEdgeNodes(json, settings.expand);
  50. setData({ nodes, edges });
  51. }, [json, settings.expand]);
  52. React.useEffect(() => {
  53. const wrapperRect = wrapperRef.current?.instance.wrapperComponent;
  54. const node = document.querySelector(
  55. `span[data-key*='${settings.searchNode}' i]`
  56. );
  57. document
  58. .querySelector("foreignObject.searched")
  59. ?.classList.remove("searched");
  60. if (wrapperRect && node && node.parentElement) {
  61. const newScale = 1;
  62. const x = Number(node.getAttribute("data-x"));
  63. const y = Number(node.getAttribute("data-y"));
  64. const newPositionX =
  65. (wrapperRect.offsetLeft - x) * newScale +
  66. node.getBoundingClientRect().width;
  67. const newPositionY =
  68. (wrapperRect.offsetTop - y) * newScale +
  69. node.getBoundingClientRect().height;
  70. node.parentElement.parentElement
  71. ?.closest("foreignObject")
  72. ?.classList.toggle("searched");
  73. wrapperRef.current?.setTransform(newPositionX, newPositionY, newScale);
  74. }
  75. }, [settings.searchNode]);
  76. const onCanvasClick = () => {
  77. const input = document.querySelector("input:focus") as HTMLInputElement;
  78. if (input) input.blur();
  79. };
  80. const onInit = (ref: ReactZoomPanPinchRef) =>
  81. dispatch({
  82. type: ConfigActionType.SET_ZOOM_PAN_PICNH_REF,
  83. payload: ref,
  84. });
  85. if (pageLoaded)
  86. return (
  87. <StyledLiveEditor>
  88. <Tools />
  89. <StyledEditorWrapper>
  90. <TransformWrapper
  91. maxScale={1.8}
  92. minScale={0.4}
  93. initialScale={0.8}
  94. ref={wrapperRef}
  95. limitToBounds={false}
  96. wheel={wheelOptions}
  97. onInit={onInit}
  98. >
  99. <TransformComponent>
  100. <Canvas
  101. nodes={data.nodes}
  102. node={CustomNode}
  103. edges={data.edges}
  104. maxWidth={20000}
  105. maxHeight={20000}
  106. center={false}
  107. zoomable={false}
  108. fit={true}
  109. direction={settings.layout}
  110. readonly
  111. key={settings.layout}
  112. onCanvasClick={onCanvasClick}
  113. />
  114. </TransformComponent>
  115. </TransformWrapper>
  116. </StyledEditorWrapper>
  117. </StyledLiveEditor>
  118. );
  119. return null;
  120. });