import { useCallback, useEffect, useRef, useState } from 'react'; const FADE_DURATION = 300; // milliseconds const DISPLAY_DURATION = 10000; // milliseconds (10 seconds) type ImageOverlayProps = { imageUrls: string[]; onComplete?: () => void; isAudioPlaying?: boolean; }; export function ImageOverlay({ imageUrls, onComplete, isAudioPlaying = false }: ImageOverlayProps) { const [currentIndex, setCurrentIndex] = useState(-1); const [shownImages, setShownImages] = useState>(new Set()); const [isVisible, setIsVisible] = useState(false); const [showControls, setShowControls] = useState(false); const previousAudioPlaying = useRef(isAudioPlaying); const timerRef = useRef(null); const displayStartTimeRef = useRef(null); const clearTimer = useCallback(() => { if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } }, []); // Start display timer when image becomes visible useEffect(() => { if (isVisible && currentIndex >= 0) { displayStartTimeRef.current = Date.now(); } }, [isVisible, currentIndex]); // Handle image transitions when audio stops and minimum time has passed useEffect(() => { if (previousAudioPlaying.current && !isAudioPlaying && currentIndex >= 0) { const timeElapsed = displayStartTimeRef.current ? Date.now() - displayStartTimeRef.current : 0; const remainingTime = Math.max(0, DISPLAY_DURATION - timeElapsed); clearTimer(); if (remainingTime > 0) { // If minimum display time hasn't elapsed, wait for the remaining time timerRef.current = setTimeout(() => { const currentUrl = imageUrls[currentIndex]; setIsVisible(false); setTimeout(() => { setShownImages(prev => new Set(Array.from(prev).concat([currentUrl]))); setCurrentIndex(-1); }, FADE_DURATION); }, remainingTime); } else { // If minimum time has elapsed, start fade immediately const currentUrl = imageUrls[currentIndex]; setIsVisible(false); setTimeout(() => { setShownImages(prev => new Set(Array.from(prev).concat([currentUrl]))); setCurrentIndex(-1); }, FADE_DURATION); } } previousAudioPlaying.current = isAudioPlaying; }, [isAudioPlaying, currentIndex, imageUrls, clearTimer]); // Start timer when new image is shown useEffect(() => { if (currentIndex >= 0 && !isAudioPlaying) { clearTimer(); timerRef.current = setTimeout(() => { const currentUrl = imageUrls[currentIndex]; setIsVisible(false); setTimeout(() => { setShownImages(prev => new Set(Array.from(prev).concat([currentUrl]))); setCurrentIndex(-1); }, FADE_DURATION); }, DISPLAY_DURATION); return () => clearTimer(); } }, [currentIndex, imageUrls, isAudioPlaying, clearTimer]); // Handle image transitions useEffect(() => { if (currentIndex === -1) { const nextIndex = imageUrls.findIndex(url => !shownImages.has(url)); if (nextIndex !== -1) { setCurrentIndex(nextIndex); setIsVisible(true); } else if (shownImages.size > 0) { onComplete?.(); } } }, [imageUrls, shownImages, currentIndex, onComplete]); const handleImageSelect = (index: number) => { clearTimer(); displayStartTimeRef.current = Date.now(); setCurrentIndex(index); // Start with image invisible for manual selection too setIsVisible(false); setTimeout(() => setIsVisible(true), 50); }; // Cleanup on unmount useEffect(() => { return () => clearTimer(); }, [clearTimer]); if (!imageUrls.length) { return null; } return (
{ setShowControls(true); }} onMouseLeave={() => { setShowControls(false); }} > {currentIndex !== -1 && ( Generated content )} {/* Image selection controls */}
); }