TextNode.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import React from "react";
  2. import { MdLink, MdLinkOff } from "react-icons/md";
  3. import { CustomNodeProps } from "src/components/CustomNode";
  4. import useGraph from "src/store/useGraph";
  5. import useStored from "src/store/useStored";
  6. import styled from "styled-components";
  7. import * as Styled from "./styles";
  8. const StyledExpand = styled.button`
  9. pointer-events: all;
  10. display: inline-flex;
  11. align-items: center;
  12. justify-content: center;
  13. color: ${({ theme }) => theme.TEXT_NORMAL};
  14. background: rgba(0, 0, 0, 0.1);
  15. height: 100%;
  16. width: 40px;
  17. border-left: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  18. &:hover {
  19. background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
  20. }
  21. `;
  22. const StyledTextNodeWrapper = styled.span<{ hasCollapse: boolean }>`
  23. display: flex;
  24. justify-content: ${({ hasCollapse }) => (hasCollapse ? "space-between" : "center")};
  25. align-items: center;
  26. height: 100%;
  27. width: 100%;
  28. `;
  29. const StyledImageWrapper = styled.div`
  30. padding: 5px;
  31. `;
  32. const StyledImage = styled.img`
  33. border-radius: 2px;
  34. object-fit: contain;
  35. background: ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  36. `;
  37. const Node: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
  38. const {
  39. id,
  40. text,
  41. width,
  42. height,
  43. data: { isParent, childrenCount, type },
  44. } = node;
  45. const ref = React.useRef(null);
  46. const hideCollapse = useStored(state => state.hideCollapse);
  47. const showChildrenCount = useStored(state => state.childrenCount);
  48. const imagePreview = useStored(state => state.imagePreview);
  49. const expandNodes = useGraph(state => state.expandNodes);
  50. const collapseNodes = useGraph(state => state.collapseNodes);
  51. const isExpanded = useGraph(state => state.collapsedParents.includes(id));
  52. const isImage =
  53. !Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && imagePreview;
  54. // const { inViewport } = useInViewport(ref);
  55. const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => {
  56. e.stopPropagation();
  57. if (!isExpanded) collapseNodes(id);
  58. else expandNodes(id);
  59. };
  60. return (
  61. <Styled.StyledForeignObject
  62. width={width}
  63. height={height}
  64. x={0}
  65. y={0}
  66. hasCollapse={isParent && hasCollapse}
  67. ref={ref}
  68. >
  69. {isImage ? (
  70. <StyledImageWrapper>
  71. <StyledImage src={text} width="70" height="70" loading="lazy" />
  72. </StyledImageWrapper>
  73. ) : (
  74. <StyledTextNodeWrapper
  75. hasCollapse={isParent && hideCollapse}
  76. data-x={x}
  77. data-y={y}
  78. data-key={JSON.stringify(text)}
  79. >
  80. <Styled.StyledKey parent={isParent} type={type}>
  81. <Styled.StyledLinkItUrl>
  82. {JSON.stringify(text).replaceAll('"', "")}
  83. </Styled.StyledLinkItUrl>
  84. </Styled.StyledKey>
  85. {isParent && childrenCount > 0 && showChildrenCount && (
  86. <Styled.StyledChildrenCount>({childrenCount})</Styled.StyledChildrenCount>
  87. )}
  88. {isParent && hasCollapse && hideCollapse && (
  89. <StyledExpand onClick={handleExpand}>
  90. {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
  91. </StyledExpand>
  92. )}
  93. </StyledTextNodeWrapper>
  94. )}
  95. </Styled.StyledForeignObject>
  96. );
  97. };
  98. function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
  99. return prev.node.text === next.node.text && prev.node.width === next.node.width;
  100. }
  101. export const TextNode = React.memo(Node, propsAreEqual);