DreamNode.jsx
1 import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 import { Billboard, Html } from '@react-three/drei'; 3 import gsap from 'gsap'; 4 import DreamTalk from './DreamTalk'; 5 import DreamSong from './DreamSong'; 6 import { BLUE, RED } from '../constants/colors'; 7 import { useThree } from '@react-three/fiber'; 8 9 const DreamNode = ({ repoName, position, scale, metadata, dreamTalkMedia, dreamSongMedia, onNodeClick, onNodeRightClick, onFileRightClick, onHover, isCentered, onDrop }) => { 10 const { camera } = useThree(); 11 const firstDreamSongMedia = dreamSongMedia && dreamSongMedia.length > 0 ? dreamSongMedia[0] : null; 12 const [hovered, setHovered] = useState(false); 13 const [isFlipped, setIsFlipped] = useState(false); 14 const nodeRef = useRef(); 15 const groupRef = useRef(); 16 17 const handleFlip = useCallback(() => { 18 setIsFlipped(prevState => { 19 const newState = !prevState; 20 gsap.to(groupRef.current.rotation, { 21 y: newState ? Math.PI : 0, 22 duration: 1, 23 ease: "power2.inOut" 24 }); 25 return newState; 26 }); 27 }, []); 28 29 useEffect(() => { 30 if (!isCentered && isFlipped) { 31 handleFlip(); 32 } 33 }, [isCentered, isFlipped, handleFlip]); 34 35 useEffect(() => { 36 if (!isCentered) { 37 setIsFlipped(false); 38 gsap.to(groupRef.current.rotation, { 39 y: 0, 40 duration: 1, 41 ease: "power2.inOut" 42 }); 43 } 44 }, [isCentered]); 45 46 useEffect(() => { 47 document.body.style.cursor = hovered ? 'pointer' : 'auto'; 48 }, [hovered]); 49 50 useEffect(() => { 51 if (nodeRef.current) { 52 gsap.to(nodeRef.current.position, { 53 x: position.x, 54 y: position.y, 55 z: position.z, 56 duration: 2, 57 ease: "power2.inOut" 58 }); 59 gsap.to(nodeRef.current.scale, { 60 x: scale, 61 y: scale, 62 z: scale, 63 duration: .5, 64 ease: "power2.out" 65 }); 66 } 67 }, [position, scale]); 68 69 const borderColor = metadata?.type === 'person' ? RED : BLUE; 70 71 const handleMouseEnter = useCallback(() => { 72 setHovered(true); 73 onHover(repoName); 74 }, [repoName, onHover]); 75 76 const handleMouseLeave = useCallback(() => { 77 setHovered(false); 78 onHover(null); 79 }, [repoName, onHover]); 80 81 const handleClick = (clickedRepoName) => { 82 onNodeClick(clickedRepoName || repoName); 83 }; 84 85 const handleRightClick = (event) => { 86 event.preventDefault(); 87 event.stopPropagation(); 88 onNodeRightClick(repoName, event); 89 }; 90 91 const handleDragOver = (event) => { 92 event.preventDefault(); 93 event.stopPropagation(); 94 }; 95 96 const handleDrop = (event) => { 97 event.preventDefault(); 98 event.stopPropagation(); 99 if (onDrop) { 100 onDrop(event, repoName); 101 } 102 }; 103 104 return ( 105 <Billboard 106 ref={nodeRef} 107 follow={true} 108 lockX={false} 109 lockY={false} 110 lockZ={false} 111 onContextMenu={handleRightClick} 112 onDragOver={handleDragOver} 113 onDrop={handleDrop} 114 > 115 <group ref={groupRef}> 116 <Html 117 transform 118 position={[0, 0, 0.01]} 119 style={{ 120 width: '300px', 121 height: '300px', 122 display: 'flex', 123 flexDirection: 'column', 124 justifyContent: 'center', 125 alignItems: 'center', 126 cursor: 'pointer', 127 }} 128 > 129 <DreamTalk 130 repoName={repoName} 131 dreamTalkMedia={dreamTalkMedia} 132 metadata={metadata} 133 onClick={handleClick} 134 onRightClick={handleRightClick} 135 onMouseEnter={handleMouseEnter} 136 onMouseLeave={handleMouseLeave} 137 isHovered={hovered} 138 borderColor={borderColor} 139 onFlip={handleFlip} 140 /> 141 </Html> 142 <Html 143 transform 144 position={[0, 0, -0.01]} 145 rotation={[0, Math.PI, 0]} 146 style={{ 147 width: '300px', 148 height: '300px', 149 display: 'flex', 150 flexDirection: 'column', 151 justifyContent: 'center', 152 alignItems: 'center', 153 cursor: 'pointer', 154 }} 155 > 156 <DreamSong 157 repoName={repoName} 158 dreamSongMedia={dreamSongMedia} 159 metadata={metadata} 160 onClick={handleClick} 161 onRightClick={handleRightClick} 162 onFileRightClick={onNodeRightClick} 163 onMouseEnter={handleMouseEnter} 164 onMouseLeave={handleMouseLeave} 165 isHovered={hovered} 166 borderColor={borderColor} 167 onFlip={handleFlip} 168 /> 169 </Html> 170 </group> 171 </Billboard> 172 ); 173 }; 174 175 export default DreamNode;