From 2446a3685d86a59c02bdcbe9857ecf5585905dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Fri, 13 Dec 2024 19:40:28 +0300 Subject: [PATCH] feat: added detail panel for hovered node --- .../CameraRecenterControl/index.tsx | 6 +- .../CursorTooltip}/HoverCard/index.tsx | 46 +++++----- .../Universe/CursorTooltip/index.tsx | 83 +++++++------------ .../Graph/Connections/LineComponent.tsx | 2 + src/components/Universe/Overlay/index.tsx | 55 ++---------- src/components/Universe/index.tsx | 2 + .../components/Scene/Board/Node/index.tsx | 2 +- 7 files changed, 68 insertions(+), 128 deletions(-) rename src/components/{mindset/components => Universe/CursorTooltip}/HoverCard/index.tsx (58%) diff --git a/src/components/App/ActionsToolbar/CameraRecenterControl/index.tsx b/src/components/App/ActionsToolbar/CameraRecenterControl/index.tsx index c8eb0bbfe..fb5e5fec8 100644 --- a/src/components/App/ActionsToolbar/CameraRecenterControl/index.tsx +++ b/src/components/App/ActionsToolbar/CameraRecenterControl/index.tsx @@ -4,10 +4,8 @@ import CameraCenterIcon from '~/components/Icons/CameraCenterIcon' import { useGraphStore } from '~/stores/useGraphStore' export const CameraRecenterControl = () => { - const [cameraFocusTrigger, setCameraFocusTrigger] = useGraphStore((s) => [ - s.cameraFocusTrigger, - s.setCameraFocusTrigger, - ]) + const cameraFocusTrigger = useGraphStore((s) => s.cameraFocusTrigger) + const setCameraFocusTrigger = useGraphStore((s) => s.setCameraFocusTrigger) return ( ( - +export const HoverCard = ({ node }: Props) => { + const { getNodeKeysByType } = useSchemaStore((s) => s) + + const keyProperty = getNodeKeysByType(node.node_type) || '' + + const description = node?.properties ? node?.properties[keyProperty] : '' + + return ( - {title} + + <TypeBadge type={node.node_type} /> + {description && {description}} - -) - -const Portal = styled.div` - position: fixed; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 1000; -` + ) +} const TooltipContainer = styled(Flex)` width: 390px; @@ -34,20 +36,17 @@ const TooltipContainer = styled(Flex)` border-radius: 8px; padding: 15px; padding-bottom: 3px !important; - position: fixed; flex-direction: column; gap: 4px; - top: calc(-230px); - left: 100%; - z-index: 1000; - margin-left: 450px; pointer-events: auto; + align-items: flex-start; ` const ContentWrapper = styled(Flex)` margin-top: 0; flex-direction: column; gap: 4px; + align-items: flex-start; ` const Title = styled(Text)` @@ -67,4 +66,11 @@ const Description = styled(Text)` color: ${colors.white}; margin: 0; opacity: 0.8; + white-space: normal; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + line-clamp: 3; + -webkit-line-clamp: 3; ` diff --git a/src/components/Universe/CursorTooltip/index.tsx b/src/components/Universe/CursorTooltip/index.tsx index 725896f8e..787408ee0 100644 --- a/src/components/Universe/CursorTooltip/index.tsx +++ b/src/components/Universe/CursorTooltip/index.tsx @@ -1,52 +1,43 @@ import { useEffect, useRef } from 'react' import styled from 'styled-components' import { Flex } from '~/components/common/Flex' -import { TypeBadge } from '~/components/common/TypeBadge' import { useHoveredNode } from '~/stores/useGraphStore' -import { useSchemaStore } from '~/stores/useSchemaStore' import { colors } from '~/utils' +import { HoverCard } from './HoverCard/index' export const CursorTooltip = () => { const tooltipRef = useRef(null) - const node = useHoveredNode() - const getIndexByType = useSchemaStore((s) => s.getIndexByType) - - const indexKey = node ? getIndexByType(node.node_type) : '' + useEffect(() => { + if (tooltipRef.current) { + tooltipRef.current.style.display = node ? 'block' : 'none' + } + }, [node, tooltipRef]) useEffect(() => { const handleMouseMove = (e: MouseEvent) => { - if (tooltipRef.current) { - const tooltip = tooltipRef.current - const tooltipWidth = tooltip.offsetWidth - const tooltipHeight = tooltip.offsetHeight - - let top = e.clientY - 20 // 20px above the cursor - let left = e.clientX - 20 // 20px to the left of the cursor + const tooltip = tooltipRef.current - // Prevent clipping at the bottom of the screen - if (top + tooltipHeight > window.innerHeight) { - top = window.innerHeight - tooltipHeight - 10 // 10px padding - } + if (!tooltip) { + return + } - // Prevent clipping on the right of the screen - if (left + tooltipWidth > window.innerWidth) { - left = window.innerWidth - tooltipWidth - 10 // 10px padding - } + tooltip.style.top = `${e.clientY + 10}px` + tooltip.style.left = `${e.clientX + 10}px` - // Prevent clipping on the left of the screen - if (left < 0) { - left = 10 // Minimum padding - } + // Prevent clipping at screen edges + const tooltipWidth = tooltip.offsetWidth + const tooltipHeight = tooltip.offsetHeight + const maxX = window.innerWidth - tooltipWidth - 10 + const maxY = window.innerHeight - tooltipHeight - 10 - // Prevent clipping at the top of the screen - if (top < 0) { - top = 10 // Minimum padding - } + if (e.clientX + 10 + tooltipWidth > window.innerWidth) { + tooltip.style.left = `${maxX}px` + } - tooltip.style.top = `${top}px` - tooltip.style.left = `${left}px` + if (e.clientY + 10 + tooltipHeight > window.innerHeight) { + tooltip.style.top = `${maxY}px` } } @@ -55,23 +46,9 @@ export const CursorTooltip = () => { return () => { window.removeEventListener('mousemove', handleMouseMove) } - }, []) - - // Ensure node exists before rendering tooltip - if (!node) { - return null - } - - const content = node.properties && indexKey && node.properties[indexKey] ? node.properties[indexKey] : '' + }, []) // Empty array ensures the listener is added only once - return ( - - - - - {content} - - ) + return {node && } } const TooltipContainer = styled(Flex)` @@ -80,10 +57,10 @@ const TooltipContainer = styled(Flex)` color: white; padding: 5px; border-radius: 3px; - pointer-events: none; /* Prevent interference with mouse events */ - z-index: 1000; /* Ensure it's on top */ - max-width: 200px; /* Optional: prevent overly large tooltips */ - white-space: nowrap; /* Optional: prevent text wrapping */ - overflow: hidden; /* Optional: prevent text overflow */ - text-overflow: ellipsis; /* Optional: add ellipsis for overflowing text */ + pointer-events: none; /* Tooltip won't block mouse events */ + z-index: 1000; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: none; /* Start hidden */ ` diff --git a/src/components/Universe/Graph/Connections/LineComponent.tsx b/src/components/Universe/Graph/Connections/LineComponent.tsx index e36a6fe86..b1f3dfef7 100644 --- a/src/components/Universe/Graph/Connections/LineComponent.tsx +++ b/src/components/Universe/Graph/Connections/LineComponent.tsx @@ -42,6 +42,8 @@ const _LineComponent = ({ isSelected, position, label, target, source }: LineCom const line = lineRef.current const activeNode = selectedNode || hoveredNode + line.visible = !activeNode + if (activeNode?.ref_id === source || activeNode?.ref_id === target) { line.visible = true diff --git a/src/components/Universe/Overlay/index.tsx b/src/components/Universe/Overlay/index.tsx index 229740c02..2d8c811aa 100644 --- a/src/components/Universe/Overlay/index.tsx +++ b/src/components/Universe/Overlay/index.tsx @@ -1,49 +1,11 @@ -import { useCallback, useEffect, useState } from 'react' import styled from 'styled-components' import { ActionsToolbar } from '~/components/App/ActionsToolbar' -import { useGraphStore } from '~/stores/useGraphStore' -import { Tooltip } from '../Graph/Cubes/Cube/components/Tooltip' -export const Overlay = () => { - const [hoveredNode, isHovering] = useGraphStore((s) => [s.hoveredNode, s.isHovering]) - const [isVisible, setIsVisible] = useState(false) - const [isTooltipHovered, setIsTooltipHovered] = useState(false) - - useEffect(() => { - let timer: NodeJS.Timeout | null = null - - if (isHovering || isTooltipHovered) { - setIsVisible(true) - } else { - timer = setTimeout(() => setIsVisible(false), 300) - } - - return () => { - if (timer) { - clearTimeout(timer) - } - } - }, [isHovering, isTooltipHovered]) - - const handleTooltipMouseEnter = useCallback(() => { - setIsTooltipHovered(true) - }, []) - - const handleTooltipMouseLeave = useCallback(() => { - setIsTooltipHovered(false) - }, []) - - return ( - - {hoveredNode && isVisible && ( - - - - )} - - - ) -} +export const Overlay = () => ( + + + +) const OverlayWrap = styled('div')(({ theme }) => ({ position: 'absolute', @@ -65,10 +27,3 @@ const OverlayWrap = styled('div')(({ theme }) => ({ top: 50, }, })) - -const TooltipWrapper = styled.div` - position: absolute; - top: 65px; - right: 55px; - z-index: 100; -` diff --git a/src/components/Universe/index.tsx b/src/components/Universe/index.tsx index 3a5ce713e..d2a16d578 100644 --- a/src/components/Universe/index.tsx +++ b/src/components/Universe/index.tsx @@ -21,6 +21,7 @@ import { Flex } from '../common/Flex' import { outlineEffectColor } from './constants' import { Controls } from './Controls' import { initialCameraPosition } from './Controls/CameraAnimations/constants' +import { CursorTooltip } from './CursorTooltip' import { Graph } from './Graph' import { Lights } from './Lights' import { Overlay } from './Overlay' @@ -147,6 +148,7 @@ const _Universe = () => { {universeQuestionIsOpen && } {isLoading && } + ) diff --git a/src/components/mindset/components/Scene/Board/Node/index.tsx b/src/components/mindset/components/Scene/Board/Node/index.tsx index 7a674e423..1620e6f3e 100644 --- a/src/components/mindset/components/Scene/Board/Node/index.tsx +++ b/src/components/mindset/components/Scene/Board/Node/index.tsx @@ -2,7 +2,7 @@ import { Html } from '@react-three/drei' import { useThree } from '@react-three/fiber' import { memo, useState } from 'react' import { Flex } from '~/components/common/Flex' -import { HoverCard } from '../../../HoverCard' +import { HoverCard } from '../../../../../Universe/CursorTooltip/HoverCard' import { RoundedRectangle } from '../RoundedRectangle' import { Content } from './Content' import { Image } from './Image'