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: live update of 3d graph #2499

Merged
merged 1 commit into from
Dec 4, 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
89 changes: 89 additions & 0 deletions src/components/Universe/CursorTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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'

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(() => {
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

// Prevent clipping at the bottom of the screen
if (top + tooltipHeight > window.innerHeight) {
top = window.innerHeight - tooltipHeight - 10 // 10px padding
}

// Prevent clipping on the right of the screen
if (left + tooltipWidth > window.innerWidth) {
left = window.innerWidth - tooltipWidth - 10 // 10px padding
}

// Prevent clipping on the left of the screen
if (left < 0) {
left = 10 // Minimum padding
}

// Prevent clipping at the top of the screen
if (top < 0) {
top = 10 // Minimum padding
}

tooltip.style.top = `${top}px`
tooltip.style.left = `${left}px`
}
}

window.addEventListener('mousemove', handleMouseMove)

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] : ''

return (
<TooltipContainer ref={tooltipRef}>
<Flex>
<TypeBadge type={node.node_type || ''} />
</Flex>
<Flex>{content}</Flex>
</TooltipContainer>
)
}

const TooltipContainer = styled(Flex)`
position: fixed;
background: ${colors.BG1};
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 */
`
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const NodeBadge = ({ position, userData, color }: BadgeProps) => {
<div className="badge-wrapper">
<TypeBadge type={userData?.node_type || ''} />
</div>
{truncateText(userData?.name, 20)}
{userData?.name ? <span>{truncateText(userData?.name, 20)}</span> : null}
</TagWrapper>
) : (
<Tag
Expand Down
24 changes: 22 additions & 2 deletions src/components/Universe/Graph/Cubes/Text/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Billboard, Plane, Svg, Text } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import { memo, useRef } from 'react'
import gsap from 'gsap'
import { memo, useEffect, useRef } from 'react'
import { Mesh, MeshBasicMaterial, Vector3 } from 'three'
import { Icons } from '~/components/Icons'
import { useNodeTypes } from '~/stores/useDataStore'
Expand Down Expand Up @@ -119,6 +120,25 @@ export const TextNode = memo(({ node, hide, ignoreDistance }: Props) => {
checkDistance()
})

useEffect(() => {
if (!ringRef.current) {
return
}

gsap.fromTo(
ringRef.current.scale, // Target
{ x: 1, y: 1, z: 1 }, // From values
{
x: 6,
y: 6,
z: 6, // To values
duration: 1.5, // Animation duration
yoyo: true,
repeat: 1,
},
)
}, [ringRef])

const nodeTypes = useNodeTypes()

const primaryColor = normalizedSchemasByType[node.node_type]?.primary_color
Expand All @@ -143,7 +163,7 @@ export const TextNode = memo(({ node, hide, ignoreDistance }: Props) => {
<meshBasicMaterial color={color} opacity={0.5} transparent />
</mesh>

{node.properties?.image_url && node.node_type === 'Person' && texture ? (
{node.properties?.image_url && ['Person', 'Episode'].includes(node.node_type) && texture ? (
<Plane args={[10 * 2, 10 * 2]} scale={2}>
<shaderMaterial
fragmentShader={`
Expand Down
6 changes: 4 additions & 2 deletions src/components/Universe/Graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@

const sphereRadius = Math.min(5000, boundingSphere.radius)

setGraphRadius(sphereRadius)
cameraSettled.current = true
if (false) {

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / craco-build-run

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addTweet.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addWebpage.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/admin/signin.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addYoutube.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addNode/addNodeType.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/seeLatest/latest.cy.ts)

Unexpected constant condition

Check warning on line 85 in src/components/Universe/Graph/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/trendingTopics/trendingTopics.cy.ts)

Unexpected constant condition
setGraphRadius(sphereRadius)
cameraSettled.current = true
}
}

if (groupRef.current) {
Expand Down
6 changes: 4 additions & 2 deletions src/components/mindset/components/Marker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ export const Marker = memo(({ type, left, img }: Props) => {

Marker.displayName = 'Marker'

const Badge = ({ iconStart, color, label }: BadgeProps) => (
const Badge = memo(({ iconStart, color, label }: BadgeProps) => (
<EpisodeWrapper color={color}>
{iconStart && <img alt={label} className="badge__img" src={iconStart} />}
</EpisodeWrapper>
)
))

Badge.displayName = 'Badge'

const EpisodeWrapper = styled(Flex).attrs({
direction: 'row',
Expand Down
2 changes: 0 additions & 2 deletions src/components/mindset/components/MediaPlayer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => {
const handleReady = () => {
if (playerRef) {
setStatus('ready')
togglePlay()
}
}

Expand All @@ -156,7 +155,6 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => {
<PlayerWrapper isFullScreen={false} onClick={handlePlayerClick}>
<ReactPlayer
ref={playerRefCallback}
controls
height="219px"
onBuffer={() => setStatus('buffering')}
onBufferEnd={() => setStatus('ready')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ type Props = {
}

export const ProgressBar = ({ duration, markers, handleProgressChange, playingTIme }: Props) => {
const thumbWidth = (10 / duration) * 100
const width = (10 / duration) * 100

return (
<ProgressWrapper>
<ProgressSlider max={duration} onChange={handleProgressChange} thumbWidth={thumbWidth} value={playingTIme} />
<ProgressSlider max={duration} onChange={handleProgressChange} value={playingTIme} width={width} />
{markers.map((node) => {
const position = ((node?.start || 0) / duration) * 100
const type = node?.node_type || ''
Expand All @@ -34,7 +34,7 @@ const ProgressWrapper = styled(Flex)`
flex: 1 1 100%;
`

const ProgressSlider = styled(Slider)<{ thumbWidth: number }>`
const ProgressSlider = styled(Slider)<{ width: number }>`
&& {
z-index: 20;
color: ${colors.white};
Expand All @@ -45,7 +45,7 @@ const ProgressSlider = styled(Slider)<{ thumbWidth: number }>`
border: none;
}
.MuiSlider-thumb {
width: ${({ thumbWidth }) => `${thumbWidth}%`};
width: ${({ width }) => `${width}%`};
height: 54px;
border-radius: 8px;
background-color: ${colors.primaryBlue};
Expand Down
2 changes: 1 addition & 1 deletion src/components/mindset/components/PlayerContols/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const PlayerControl = ({ markers }: Props) => {

setCurrentTime(time)
}
}, 100)
}, 500)

return () => clearInterval(interval)
}, [playerRef, setCurrentTime])
Expand Down
102 changes: 92 additions & 10 deletions src/components/mindset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@
import { useDataStore } from '~/stores/useDataStore'
import { useMindsetStore } from '~/stores/useMindsetStore'
import { usePlayerStore } from '~/stores/usePlayerStore'
import { FetchDataResponse, NodeExtended } from '~/types'
import { FetchDataResponse, Link, Node, NodeExtended } from '~/types'
import { Header } from './components/Header'
import { LandingPage } from './components/LandingPage'
import { PlayerControl } from './components/PlayerContols'
import { Scene } from './components/Scene'
import { SideBar } from './components/Sidebar'

export const MindSet = () => {
const { addNewNode, isFetching, runningProjectId, dataInitial } = useDataStore((s) => s)
const [showTwoD, setShowTwoD] = useState(true)
const { addNewNode, isFetching, runningProjectId } = useDataStore((s) => s)
const [dataInitial, setDataInitial] = useState<FetchDataResponse | null>(null)
const [showTwoD, setShowTwoD] = useState(false)
const { selectedEpisodeId, setSelectedEpisode } = useMindsetStore((s) => s)
const socket: Socket | undefined = useSocket()
const requestRef = useRef<number | null>(null)
const previousTimeRef = useRef<number | null>(null)
const nodesAndEdgesRef = useRef<FetchDataResponse | null>(null)

const queueRef = useRef<FetchDataResponse | null>(null)
const timerRef = useRef<NodeJS.Timeout | null>(null)
Expand Down Expand Up @@ -64,7 +68,7 @@
)

const handleNodeUpdated = useCallback((node: NodeExtended) => {
console.log(node, 'uuuuuupdate')

Check warning on line 71 in src/components/mindset/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected console statement

Check warning on line 71 in src/components/mindset/index.tsx

View workflow job for this annotation

GitHub Actions / craco-build-run

Unexpected console statement
}, [])

useEffect(() => {
Expand All @@ -72,9 +76,42 @@
try {
const data = await fetchNodeEdges(selectedEpisodeId, 0, 50)

if (data) {
handleNewNodeCreated(data)
setDataInitial(data)

const [episodesAndClips, remainingNodes] = (data?.nodes || []).reduce<[Node[], Node[]]>(
([matches, remaining], node) => {
if (['Episode', 'Show'].includes(node.node_type)) {
matches.push(node)
} else {
remaining.push(node)
}

return [matches, remaining]
},
[[], []],
)

const refIds = new Set(episodesAndClips.map((n) => n.ref_id))

const [matchingLinks, remainingLinks] = (data?.edges || []).reduce<[Link[], Link[]]>(
([matches, remaining], link) => {
if (refIds.has(link.source) && refIds.has(link.target)) {
matches.push(link)
} else {
remaining.push(link)
}

return [matches, remaining]
},
[[], []],
)

nodesAndEdgesRef.current = {
nodes: remainingNodes || [],
edges: remainingLinks || [],
}

handleNewNodeCreated({ nodes: episodesAndClips, edges: matchingLinks })
} catch (error) {
console.error(error)
}
Expand Down Expand Up @@ -112,16 +149,61 @@
console.error('Socket connection error:', error)
})

socket.on('new_node_created', handleNewNodeCreated)
socket.on('node_updated', handleNodeUpdated)
if (runningProjectId) {
socket.on('new_node_created', handleNewNodeCreated)
socket.on('node_updated', handleNodeUpdated)
}
}

return () => {
if (socket) {
socket.off()
}
}
}, [socket, handleNodeUpdated, handleNewNodeCreated])
}, [socket, handleNodeUpdated, handleNewNodeCreated, runningProjectId])

useEffect(() => {
const update = (time: number) => {
const { playerRef } = usePlayerStore.getState()

if (previousTimeRef.current !== null) {
const deltaTime = time - previousTimeRef.current

if (deltaTime > 2000) {
if (nodesAndEdgesRef.current && playerRef) {
const { nodes, edges } = nodesAndEdgesRef.current
const currentTime = playerRef?.getCurrentTime()

const edgesWithTimestamp = edges.filter(
(edge) => edge?.properties?.start !== undefined && (edge?.properties?.start as number) < currentTime,
)

const newNodes = nodes.filter((node) =>
edgesWithTimestamp.some((edge) => edge.target === node.ref_id || edge.source === node.ref_id),
)

if (newNodes.length || edgesWithTimestamp.length) {
addNewNode({ nodes: newNodes, edges: edgesWithTimestamp })
}
}

previousTimeRef.current = time
}
} else {
previousTimeRef.current = time
}

requestRef.current = requestAnimationFrame(update)
}

requestRef.current = requestAnimationFrame(update)

return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current)
}
}
}, [nodesAndEdgesRef, addNewNode])

useEffect(() => {
if (runningProjectId) {
Expand All @@ -135,12 +217,12 @@

const markers = useMemo(() => {
if (dataInitial) {
const edgesMention: Array<{ source: string; target: string; start: number }> = dataInitial.links
const edgesMention: Array<{ source: string; target: string; start: number }> = dataInitial.edges
.filter((e) => e?.properties?.start)
.map((edge) => ({ source: edge.source, target: edge.target, start: edge.properties?.start as number }))

const nodesWithTimestamps = dataInitial.nodes
.filter((node) => dataInitial.links.some((ed) => ed.source === node.ref_id || ed.target === node.ref_id))
.filter((node) => dataInitial.edges.some((ed) => ed.source === node.ref_id || ed.target === node.ref_id))
.map((node) => {
const edge = edgesMention.find((ed) => node.ref_id === ed.source || node.ref_id === ed.target)

Expand Down
Loading
Loading