/ src / components / DreamNode.jsx
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;