Skip to content

Commit

Permalink
Merge pull request #2499 from stakwork/feature/3d-mindset-v2
Browse files Browse the repository at this point in the history
feat: live update of 3d graph
  • Loading branch information
Rassl authored Dec 4, 2024
2 parents ff6b17d + 5de7a6e commit 62f896d
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 25 deletions.
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 @@ export const Graph = () => {

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 / build

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 { getNode } from '~/network/fetchSourcesData'
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 @@ -72,9 +76,42 @@ export const MindSet = () => {
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 @@ export const MindSet = () => {
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 @@ export const MindSet = () => {

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

0 comments on commit 62f896d

Please sign in to comment.