import React, { startTransition, useCallback, useContext, useMemo, useRef, useState, } from "react"; import { AnimatePresence, motion, PanInfo } from "framer-motion"; import { ConfigContext } from "../Controller/ConfigController"; import Loading from "./Loading"; import { defaultNodeConfig } from "../config/nodeConfig"; import { EdgeFrontProps, NodeFrontProps } from "../../KnowledgeGraph"; import useExplore from "../hooks/Node/useExplore"; import { useDispatch } from "react-redux"; import { filterHighlightEdges, moveNodeAndEdge, notHighlight, onMoveEnd, } from "../Controller/graphSlice"; import { useAppSelector } from "../hooks"; import useCalcEdge from "../hooks/Edge/useCalcEdge"; import { FcInfo, FcPlus } from "react-icons/fc"; import useIsShowText from "../hooks/Graph/useIsShowText"; import { useGraphBounds } from "../Controller/GraphBoundsController"; function UnmemoNode({ node }: { node: NodeFrontProps }) { const { config } = useContext(ConfigContext)!; const { typeConfig, showNodeMenu, onClickAddon, onClickInfo, edgeConfig } = config; const { name, type, position } = node; const nodeConfig = typeConfig && typeConfig[type]; const edges = useAppSelector((state) => state.graph.edges); const [isHover, setIsHover] = useState(false); const fill = nodeConfig?.fill || defaultNodeConfig.fill; const hoverStyle = nodeConfig?.hoverStyle || defaultNodeConfig.hoverStyle; const nameColor = nodeConfig?.nameColor || defaultNodeConfig.nameColor; const nameSize = nodeConfig?.nameSize || defaultNodeConfig.nameSize; const typeColor = nodeConfig?.typeColor || defaultNodeConfig.typeColor; const typeSize = nodeConfig?.typeSize || defaultNodeConfig.typeSize; const radius = nodeConfig?.radius || defaultNodeConfig.radius; const { exploreFunc, loading } = useExplore(); const dispatch = useDispatch(); const { calcD } = useCalcEdge(); const { x1, x2, y1, y2 } = useGraphBounds(); const isShowText = useIsShowText(); const isInScreen = useMemo(() => { if ( position.x + radius >= x1 && position.x <= x2 && position.y >= y1 && position.y <= y2 ) { return true; } else { return false; } }, [position.x, position.y, radius, x1, x2, y1, y2]); const modifyEdges = useCallback( (info: PanInfo) => { if (edgesElementRef.current) { edgesElementRef.current.forEach(({ element, direction, edge }) => { const fromNode = edge.fromNode!; const toNode = edge.toNode!; const d = calcD( direction === "from" ? { ...edge, fromNode: { ...fromNode, position: { x: fromNode.position.x + info.offset.x, y: fromNode.position.y + info.offset.y, }, } as NodeFrontProps, } : { ...edge, toNode: { ...toNode, position: { x: toNode.position.x + info.offset.x, y: toNode.position.y + info.offset.y, }, } as NodeFrontProps, } ); if (d) { element.setAttribute("d", d); const iconOpen = document.getElementById(edge.id + "icon-open"); const iconClose = document.getElementById(edge.id + "icon-close"); const arrow = document.getElementById(edge.id + "arrow"); if (iconOpen) { iconOpen.style.offsetPath = `path("${d}")`; } if (iconClose) { iconClose.style.offsetPath = `path("${d}")`; } if (arrow) { arrow.style.offsetPath = `path("${d}")`; } } }); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [] ); const needCalcEdges = useMemo( () => edges.filter((edge) => edge.fromId === node.id || edge.toId === node.id), [edges, node.id] ); const edgesElementRef = useRef< { element: SVGPathElement; direction: "from" | "to"; edge: EdgeFrontProps; }[] >(null!); return ( <> {node.id && ( { dispatch(filterHighlightEdges(node)); setIsHover(true); }} onHoverEnd={() => { dispatch(notHighlight(null)); setIsHover(false); }} drag dragPropagation={false} dragSnapToOrigin={false} dragMomentum={false} whileDrag={{ scale: 1.2, }} style={{ willChange: "transform", }} onDragStart={(e) => { edgesElementRef.current = needCalcEdges.map((edge) => { let direction: "from" | "to" = "from"; if (edge.fromId === node.id) { direction = "from"; } else { direction = "to"; } return { direction, element: document.getElementById( edge.id )! as unknown as SVGPathElement, edge, }; }); }} onDrag={(e, info) => { requestAnimationFrame(() => { modifyEdges(info); }); }} onDragEnd={(e, info) => { dispatch( moveNodeAndEdge({ node, dx: info.offset.x, dy: info.offset.y }) ); dispatch(onMoveEnd(undefined)); dispatch(notHighlight(null)); setIsHover(false); }} initial={{ x: position.x, y: position.y, opacity: 0, }} animate={{ x: position.x, y: position.y, opacity: isInScreen ? 1 : 0, visibility: node.visible && isInScreen ? "visible" : "hidden", }} transition={{ duration: 0.3, visibility: { duration: 0, }, opacity: { duration: 0.6, }, }} onDoubleClick={(e) => { if (!loading) { exploreFunc({ node }); } }} > {(isHover || node.isHovered) && ( )} {loading && ( )} {isShowText && ( <> {name} {type} )} {showNodeMenu && (isHover || node.isHovered) && ( { onClickInfo && onClickInfo(node); }} > )} {showNodeMenu && (isHover || node.isHovered) && ( { onClickAddon && onClickAddon(node); }} > )} )} ); } export default React.memo(UnmemoNode);