Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added detail panel for hovered node #2538

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<CameraCenterButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import styled from 'styled-components'
import { Flex } from '~/components/common/Flex'
import { Text } from '~/components/common/Text'
import { TypeBadge } from '~/components/common/TypeBadge'
import { useSchemaStore } from '~/stores/useSchemaStore'
import { Node } from '~/types'
import { colors } from '~/utils/colors'

type Props = {
title?: string
description?: string
node: Node
}

export const HoverCard = ({ title, description }: Props) => (
<Portal>
export const HoverCard = ({ node }: Props) => {
const { getNodeKeysByType } = useSchemaStore((s) => s)

const keyProperty = getNodeKeysByType(node.node_type) || ''

const description = node?.properties ? node?.properties[keyProperty] : ''

return (
<TooltipContainer>
<ContentWrapper>
<Title>{title}</Title>
<Title>
<TypeBadge type={node.node_type} />
</Title>
{description && <Description>{description}</Description>}
</ContentWrapper>
</TooltipContainer>
</Portal>
)

const Portal = styled.div`
position: fixed;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
`
)
}

const TooltipContainer = styled(Flex)`
width: 390px;
Expand All @@ -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)`
Expand All @@ -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;
`
83 changes: 30 additions & 53 deletions src/components/Universe/CursorTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement | null>(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`
}
}

Expand All @@ -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 (
<TooltipContainer ref={tooltipRef}>
<Flex>
<TypeBadge type={node.node_type || ''} />
</Flex>
<Flex>{content}</Flex>
</TooltipContainer>
)
return <TooltipContainer ref={tooltipRef}>{node && <HoverCard node={node} />}</TooltipContainer>
}

const TooltipContainer = styled(Flex)`
Expand All @@ -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 */
`
2 changes: 2 additions & 0 deletions src/components/Universe/Graph/Connections/LineComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
55 changes: 5 additions & 50 deletions src/components/Universe/Overlay/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<OverlayWrap>
{hoveredNode && isVisible && (
<TooltipWrapper onMouseEnter={handleTooltipMouseEnter} onMouseLeave={handleTooltipMouseLeave}>
<Tooltip node={hoveredNode} />
</TooltipWrapper>
)}
<ActionsToolbar />
</OverlayWrap>
)
}
export const Overlay = () => (
<OverlayWrap>
<ActionsToolbar />
</OverlayWrap>
)

const OverlayWrap = styled('div')(({ theme }) => ({
position: 'absolute',
Expand All @@ -65,10 +27,3 @@ const OverlayWrap = styled('div')(({ theme }) => ({
top: 50,
},
}))

const TooltipWrapper = styled.div`
position: absolute;
top: 65px;
right: 55px;
z-index: 100;
`
2 changes: 2 additions & 0 deletions src/components/Universe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -147,6 +148,7 @@ const _Universe = () => {
</Suspense>
{universeQuestionIsOpen && <UniverseQuestion />}
{isLoading && <Preloader fullSize={false} />}
<CursorTooltip />
<Overlay />
</Wrapper>
)
Expand Down
10 changes: 2 additions & 8 deletions src/components/mindset/components/Scene/Board/Node/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Html } from '@react-three/drei'
import { useThree } from '@react-three/fiber'
import { memo, useState } from 'react'
import { memo } from 'react'
import { Flex } from '~/components/common/Flex'
import { HoverCard } from '../../../HoverCard'
import { RoundedRectangle } from '../RoundedRectangle'
import { Content } from './Content'
import { Image } from './Image'
Expand All @@ -16,12 +15,10 @@ type Props = {
name: string
type: string
color: string
description?: string
}

export const Node = memo(({ width, height, position, url, onButtonClick, name, type, color, description }: Props) => {
export const Node = memo(({ width, height, position, url, onButtonClick, name, type, color }: Props) => {
const { camera } = useThree()
const [isHovered, setIsHovered] = useState(false)

return (
<group position={position}>
Expand All @@ -33,8 +30,6 @@ export const Node = memo(({ width, height, position, url, onButtonClick, name, t
<Html position={[-width / 2, height / 2, 0]}>
<Flex
onClick={() => onButtonClick()}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
fontSize: '12px',
color: 'white',
Expand All @@ -49,7 +44,6 @@ export const Node = memo(({ width, height, position, url, onButtonClick, name, t
}}
>
<Content name={`${name}`} type={type || ''} url={url} />
{isHovered && <HoverCard description={description} title={name} />}
</Flex>
</Html>
</group>
Expand Down
2 changes: 0 additions & 2 deletions src/components/mindset/components/Scene/Board/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@
<Fragment key={node.ref_id}>
<Node
color="#353A46"
description={node?.properties?.description}
height={nodeHeight}
name={node?.properties?.name || ''}
onButtonClick={console.log}

Check warning on line 127 in src/components/mindset/components/Scene/Board/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected console statement
position={[node.x, node.y, node.z]}
type={node.node_type}
url={node?.properties?.image_url || 'logo.png'}
Expand All @@ -137,10 +136,9 @@
<Node
key={`${relatedNode.ref_id}-${node.ref_id}`}
color="#353A46"
description={relatedNode?.properties?.description}
height={nodeHeight}
name={relatedNode?.properties?.name || ''}
onButtonClick={console.log}

Check warning on line 141 in src/components/mindset/components/Scene/Board/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected console statement
position={[relatedNode.x, relatedNode.y, relatedNode.z]}
type={relatedNode.node_type}
url={relatedNode?.properties?.image_url || 'logo.png'}
Expand Down
Loading