DreamTalk.jsx
1 import React, { useRef, useEffect, useState } from 'react'; 2 import { BLACK, WHITE, BLUE } from '../constants/colors'; 3 4 const CenterOverlay = ({ repoName, isVisible }) => ( 5 <div style={{ 6 position: 'absolute', 7 top: '25%', 8 left: '25%', 9 width: '50%', 10 height: '50%', 11 borderRadius: '50%', 12 backgroundColor: isVisible ? 'rgba(0, 0, 0, 0.3)' : 'transparent', 13 display: 'flex', 14 justifyContent: 'center', 15 alignItems: 'center', 16 opacity: isVisible ? 1 : 0, 17 transition: 'opacity 0.3s ease', 18 pointerEvents: 'none', 19 }}> 20 {isVisible && ( 21 <div style={{ 22 color: WHITE, 23 fontSize: '14px', 24 textAlign: 'center', 25 padding: '5px', 26 }}> 27 {repoName} 28 </div> 29 )} 30 </div> 31 ); 32 33 const DreamTalk = ({ repoName, dreamTalkMedia, metadata, onClick, onRightClick, onMouseEnter, onMouseLeave, isHovered, borderColor, onFlip }) => { 34 const containerRef = useRef(null); 35 const mediaRef = useRef(null); 36 const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); 37 const [isCenterHovered, setIsCenterHovered] = useState(false); 38 const [currentMediaIndex, setCurrentMediaIndex] = useState(0); 39 const [isComponentHovered, setIsComponentHovered] = useState(false); 40 41 useEffect(() => { 42 console.log('Current media index:', currentMediaIndex); 43 }, [currentMediaIndex]); 44 45 useEffect(() => { 46 if (containerRef.current) { 47 const updateDimensions = () => { 48 const container = containerRef.current; 49 const size = Math.min(container.offsetWidth, container.offsetHeight); 50 setDimensions({ width: size, height: size }); 51 }; 52 53 updateDimensions(); 54 window.addEventListener('resize', updateDimensions); 55 return () => window.removeEventListener('resize', updateDimensions); 56 } 57 }, []); 58 59 const renderMedia = () => { 60 if (!dreamTalkMedia || dreamTalkMedia.length === 0 || !dreamTalkMedia[currentMediaIndex]) { 61 return null; 62 } 63 64 const currentMedia = dreamTalkMedia[currentMediaIndex]; 65 66 const commonStyle = { 67 width: '100%', 68 height: '100%', 69 objectFit: 'cover', 70 }; 71 72 switch (currentMedia.type) { 73 case 'image/jpeg': 74 case 'image/png': 75 case 'image/gif': 76 case 'image/webp': 77 return <img key={`img-${currentMediaIndex}`} ref={mediaRef} src={currentMedia.data} alt={repoName} style={commonStyle} />; 78 case 'audio/mpeg': 79 case 'audio/wav': 80 return ( 81 <div key={`audio-${currentMediaIndex}`} ref={mediaRef} style={{ ...commonStyle, display: 'flex', alignItems: 'center', justifyContent: 'center', background: BLACK }}> 82 <audio controls src={currentMedia.data} style={{ width: '90%', maxWidth: '200px' }} /> 83 </div> 84 ); 85 case 'video/mp4': 86 case 'video/webm': 87 return <video key={`video-${currentMediaIndex}`} ref={mediaRef} controls src={currentMedia.data} style={commonStyle} />; 88 default: 89 return null; 90 } 91 }; 92 93 const handlePrevMedia = (e) => { 94 e.stopPropagation(); 95 setCurrentMediaIndex((prevIndex) => { 96 const newIndex = prevIndex > 0 ? prevIndex - 1 : dreamTalkMedia.length - 1; 97 console.log('Previous media index:', newIndex); 98 return newIndex; 99 }); 100 }; 101 102 const handleNextMedia = (e) => { 103 e.stopPropagation(); 104 setCurrentMediaIndex((prevIndex) => { 105 const newIndex = prevIndex < dreamTalkMedia.length - 1 ? prevIndex + 1 : 0; 106 console.log('Next media index:', newIndex); 107 return newIndex; 108 }); 109 }; 110 111 useEffect(() => { 112 console.log('Current media index changed:', currentMediaIndex); 113 console.log('Current media:', dreamTalkMedia[currentMediaIndex]); 114 }, [currentMediaIndex, dreamTalkMedia]); 115 116 const handleMouseEnter = () => { 117 setIsComponentHovered(true); 118 onMouseEnter(); 119 }; 120 121 const handleMouseLeave = () => { 122 setIsComponentHovered(false); 123 onMouseLeave(); 124 }; 125 126 return ( 127 <div 128 ref={containerRef} 129 className="dream-talk" 130 onClick={() => onClick(repoName)} 131 onContextMenu={(e) => { 132 e.preventDefault(); 133 onRightClick(e); 134 }} 135 onMouseEnter={handleMouseEnter} 136 onMouseLeave={handleMouseLeave} 137 style={{ 138 position: 'relative', 139 overflow: 'hidden', 140 width: '100%', 141 height: '100%', 142 borderRadius: '50%', 143 border: `5px solid ${borderColor}`, 144 color: WHITE, 145 boxSizing: 'border-box', 146 background: BLACK, 147 }} 148 > 149 <div style={{ 150 position: 'absolute', 151 top: '50%', 152 left: '50%', 153 transform: 'translate(-50%, -50%)', 154 width: `${dimensions.width * 0.8}px`, 155 height: `${dimensions.height * 0.8}px`, 156 borderRadius: '50%', 157 overflow: 'hidden', 158 }}> 159 {renderMedia()} 160 </div> 161 <div style={{ 162 position: 'absolute', 163 top: '50%', 164 left: '50%', 165 transform: 'translate(-50%, -50%)', 166 width: `${dimensions.width * 0.8}px`, 167 height: `${dimensions.height * 0.8}px`, 168 background: 'radial-gradient(circle, rgba(0,0,0,0) 50%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,1) 70%)', 169 pointerEvents: 'none', 170 borderRadius: '49%', 171 }} /> 172 {dreamTalkMedia && dreamTalkMedia.length > 0 && isComponentHovered && ( 173 <div style={{ 174 position: 'absolute', 175 top: '50%', 176 left: '50%', 177 transform: 'translate(-50%, -50%)', 178 width: `${dimensions.width * 0.8}px`, 179 height: `${dimensions.height * 0.8}px`, 180 display: 'flex', 181 justifyContent: 'center', 182 alignItems: 'center', 183 background: 'rgba(0, 0, 0, 0.7)', 184 color: WHITE, 185 fontSize: '16px', 186 fontWeight: 'bold', 187 borderRadius: '50%', 188 transition: 'opacity 0.3s ease', 189 }}> 190 {repoName} 191 </div> 192 )} 193 <div 194 style={{ 195 position: 'absolute', 196 top: '25%', 197 left: '25%', 198 width: '50%', 199 height: '50%', 200 borderRadius: '50%', 201 cursor: 'pointer', 202 }} 203 onMouseEnter={() => setIsCenterHovered(true)} 204 onMouseLeave={() => setIsCenterHovered(false)} 205 /> 206 <CenterOverlay repoName={repoName} isVisible={isCenterHovered} /> 207 <div style={{ 208 position: 'absolute', 209 top: 0, 210 left: 0, 211 width: '100%', 212 height: '100%', 213 display: 'flex', 214 flexDirection: 'column', 215 justifyContent: 'center', 216 alignItems: 'center', 217 padding: '10px', 218 boxSizing: 'border-box', 219 background: !dreamTalkMedia || dreamTalkMedia.length === 0 ? 'rgba(0, 0, 0, 0.7)' : 'transparent', 220 opacity: !dreamTalkMedia || dreamTalkMedia.length === 0 ? 1 : 0, 221 transition: 'opacity 0.3s ease, background 0.3s ease', 222 overflow: 'hidden', 223 }}> 224 <div style={{ 225 maxHeight: '100%', 226 overflow: 'auto', 227 display: 'flex', 228 flexDirection: 'column', 229 alignItems: 'center', 230 width: '100%', 231 }}> 232 <h2 style={{ 233 fontSize: '16px', 234 margin: '5px 0', 235 padding: '3px', 236 textAlign: 'center', 237 wordWrap: 'break-word', 238 overflowWrap: 'break-word', 239 maxWidth: '100%', 240 }}> 241 {repoName} 242 </h2> 243 {metadata && metadata.description && ( 244 <p style={{ 245 fontSize: '12px', 246 margin: '3px 0', 247 textAlign: 'center', 248 maxWidth: '100%', 249 overflow: 'hidden', 250 textOverflow: 'ellipsis', 251 display: '-webkit-box', 252 WebkitLineClamp: 3, 253 WebkitBoxOrient: 'vertical', 254 }}> 255 {metadata.description} 256 </p> 257 )} 258 </div> 259 </div> 260 {dreamTalkMedia && dreamTalkMedia.length > 1 && ( 261 <> 262 <button 263 onClick={handlePrevMedia} 264 style={{ 265 position: 'absolute', 266 left: '10px', 267 top: '50%', 268 transform: 'translateY(-50%)', 269 background: 'rgba(0, 0, 0, 0.5)', 270 color: WHITE, 271 border: 'none', 272 borderRadius: '50%', 273 width: '30px', 274 height: '30px', 275 fontSize: '20px', 276 cursor: 'pointer', 277 display: 'flex', 278 justifyContent: 'center', 279 alignItems: 'center', 280 }} 281 > 282 ‹ 283 </button> 284 <button 285 onClick={handleNextMedia} 286 style={{ 287 position: 'absolute', 288 right: '10px', 289 top: '50%', 290 transform: 'translateY(-50%)', 291 background: 'rgba(0, 0, 0, 0.5)', 292 color: WHITE, 293 border: 'none', 294 borderRadius: '50%', 295 width: '30px', 296 height: '30px', 297 fontSize: '20px', 298 cursor: 'pointer', 299 display: 'flex', 300 justifyContent: 'center', 301 alignItems: 'center', 302 }} 303 > 304 › 305 </button> 306 </> 307 )} 308 <div 309 style={{ 310 position: 'absolute', 311 bottom: '10px', 312 left: '50%', 313 transform: 'translateX(-50%)', 314 opacity: 0, 315 transition: 'opacity 0.3s ease', 316 }} 317 className="flip-button-container" 318 > 319 <button 320 onClick={(e) => { 321 e.stopPropagation(); 322 onFlip(); 323 }} 324 style={{ 325 background: BLUE, 326 color: WHITE, 327 border: 'none', 328 borderRadius: '5px', 329 padding: '5px 10px', 330 cursor: 'pointer', 331 }} 332 > 333 Flip 334 </button> 335 </div> 336 {dreamTalkMedia && dreamTalkMedia.length > 0 && ( 337 <div style={{ 338 position: 'absolute', 339 bottom: '5px', 340 left: '50%', 341 transform: 'translateX(-50%)', 342 color: WHITE, 343 fontSize: '12px', 344 background: 'rgba(0, 0, 0, 0.5)', 345 padding: '2px 5px', 346 borderRadius: '10px', 347 }}> 348 {currentMediaIndex + 1} / {dreamTalkMedia.length} 349 </div> 350 )} 351 <style> 352 {` 353 .dream-talk:hover .flip-button-container { 354 opacity: 1; 355 } 356 `} 357 </style> 358 </div> 359 ); 360 }; 361 362 export default React.memo(DreamTalk);