| import React from "react"; | |
| import styled from "styled-components"; | |
| import { MdLink, MdLinkOff } from "react-icons/md"; | |
| import { CustomNodeProps } from "src/containers/Views/GraphView/CustomNode"; | |
| import useToggleHide from "src/hooks/useToggleHide"; | |
| import { isContentImage } from "src/lib/utils/graph/calculateNodeSize"; | |
| import useConfig from "src/store/useConfig"; | |
| import useGraph from "src/store/useGraph"; | |
| import { TextRenderer } from "./TextRenderer"; | |
| import * as Styled from "./styles"; | |
| const StyledExpand = styled.button` | |
| pointer-events: all; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: ${({ theme }) => theme.TEXT_NORMAL}; | |
| background: rgba(0, 0, 0, 0.1); | |
| height: 100%; | |
| width: 40px; | |
| border-left: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT}; | |
| &:hover { | |
| background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0); | |
| } | |
| `; | |
| const StyledTextNodeWrapper = styled.span<{ $hasCollapse: boolean }>` | |
| display: flex; | |
| justify-content: ${({ $hasCollapse }) => ($hasCollapse ? "space-between" : "center")}; | |
| align-items: center; | |
| height: 100%; | |
| width: 100%; | |
| `; | |
| const StyledImageWrapper = styled.div` | |
| padding: 5px; | |
| `; | |
| const StyledImage = styled.img` | |
| border-radius: 2px; | |
| object-fit: contain; | |
| background: ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT}; | |
| `; | |
| const Node: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => { | |
| const { | |
| id, | |
| text, | |
| width, | |
| height, | |
| data: { isParent, childrenCount, type }, | |
| } = node; | |
| const { validateHiddenNodes } = useToggleHide(); | |
| const collapseButtonVisible = useConfig(state => state.collapseButtonVisible); | |
| const childrenCountVisible = useConfig(state => state.childrenCountVisible); | |
| const imagePreviewEnabled = useConfig(state => state.imagePreviewEnabled); | |
| const expandNodes = useGraph(state => state.expandNodes); | |
| const collapseNodes = useGraph(state => state.collapseNodes); | |
| const isExpanded = useGraph(state => state.collapsedParents.includes(id)); | |
| const isImage = imagePreviewEnabled && isContentImage(text as string); | |
| const value = JSON.stringify(text).replaceAll('"', ""); | |
| const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => { | |
| e.stopPropagation(); | |
| if (!isExpanded) collapseNodes(id); | |
| else expandNodes(id); | |
| validateHiddenNodes(); | |
| }; | |
| return ( | |
| <Styled.StyledForeignObject width={width} height={height} x={0} y={0}> | |
| {isImage ? ( | |
| <StyledImageWrapper> | |
| <StyledImage src={text as string} width="70" height="70" loading="lazy" /> | |
| </StyledImageWrapper> | |
| ) : ( | |
| <StyledTextNodeWrapper | |
| data-x={x} | |
| data-y={y} | |
| data-key={JSON.stringify(text)} | |
| $hasCollapse={isParent && collapseButtonVisible} | |
| > | |
| <Styled.StyledKey $value={value} $parent={isParent} $type={type}> | |
| <TextRenderer>{value}</TextRenderer> | |
| </Styled.StyledKey> | |
| {isParent && childrenCount > 0 && childrenCountVisible && ( | |
| <Styled.StyledChildrenCount>({childrenCount})</Styled.StyledChildrenCount> | |
| )} | |
| {isParent && hasCollapse && collapseButtonVisible && ( | |
| <StyledExpand aria-label="Expand" onClick={handleExpand}> | |
| {isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />} | |
| </StyledExpand> | |
| )} | |
| </StyledTextNodeWrapper> | |
| )} | |
| </Styled.StyledForeignObject> | |
| ); | |
| }; | |
| function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) { | |
| return ( | |
| prev.node.text === next.node.text && | |
| prev.node.width === next.node.width && | |
| prev.node.data.childrenCount === next.node.data.childrenCount | |
| ); | |
| } | |
| export const TextNode = React.memo(Node, propsAreEqual); | |