import React, { memo, useState, useEffect, useRef } from "react"; import Author from "./Author"; import ImportButton from "./ImportButton"; import DownloadDropdown from "./DownloadDropdown"; import PreviewButton from "./PreviewButton"; import { useGenerator } from "../../context/GeneratorContext"; const EyeIcon = ({ className = "w-5 h-5" }) => ( ); const HeartIcon = ({ filled, className = "w-5 h-5" }) => ( ); function ImageCard({ imageSrc, imageAlt = "Image", previewUrl, author = {}, importUrl, downloadOptions = [], loading = "lazy", providerId, imageId, imageData = {}, providerBadge, variant = "grid", index = 0, gifStillUrl, // Still version of GIF (for Giphy) gifAnimatedUrl, // Animated version of GIF (for Giphy) }) { const { url: authorUrl, avatar, name } = author; const generator = useGenerator(); const selectionMode = generator?.selectionMode ?? false; const isSelected = generator?.isSelected(providerId, imageId) ?? false; const isFavorite = generator?.isFavorite(providerId, imageId) ?? false; const toggleSelection = generator?.toggleSelection; const toggleFavorite = generator?.toggleFavorite; const setPreviewImage = generator?.setPreviewImage; const [imageLoaded, setImageLoaded] = useState(false); const [imageError, setImageError] = useState(false); const [isHovered, setIsHovered] = useState(false); // Determine which image source to use (animated on hover for GIFs, otherwise normal) const currentImageSrc = (gifStillUrl && gifAnimatedUrl) ? (isHovered ? gifAnimatedUrl : gifStillUrl) : imageSrc; const previewImageList = generator?.previewImageList; const setPreviewImageList = generator?.setPreviewImageList; // Register this card's data in the preview list on mount/update const cardData = useRef(null); cardData.current = { imageSrc, imageAlt, previewUrl, author, importUrl, downloadOptions, providerId, imageId, gifStillUrl, gifAnimatedUrl, providerBadge, index }; useEffect(() => { const handleNav = (e) => { const { direction, currentIndex } = e.detail; const list = generator?.previewImageList; if (!list || list.length === 0) return; let nextIdx; if (direction === "next") nextIdx = currentIndex + 1 >= list.length ? 0 : currentIndex + 1; else nextIdx = currentIndex - 1 < 0 ? list.length - 1 : currentIndex - 1; const nextImage = list[nextIdx]; if (nextImage && generator?.setPreviewImage) generator.setPreviewImage({ ...nextImage, index: nextIdx }); }; window.addEventListener("previewNavigate", handleNav); return () => window.removeEventListener("previewNavigate", handleNav); }, [generator]); const handleQuickPreview = (e) => { e.preventDefault(); e.stopPropagation(); if (!setPreviewImage) return; // Use data from previewImageList (which has full-size URLs) if available const list = generator?.previewImageList; if (list && list.length > 0 && index != null) { const listItem = list[index]; if (listItem) { setPreviewImage({ ...listItem, index }); return; } } // Fallback to card props setPreviewImage({ imageSrc, imageAlt, previewUrl, author, importUrl, downloadOptions, providerId, imageId, gifStillUrl, gifAnimatedUrl, providerBadge, index }); }; const handleFavorite = (e) => { e.preventDefault(); e.stopPropagation(); if (toggleFavorite) toggleFavorite(providerId, imageId, { imageSrc, imageAlt, author, importUrl, downloadOptions, previewUrl }); }; const handleToggleSelect = (e) => { e.preventDefault(); e.stopPropagation(); if (toggleSelection) toggleSelection(providerId, imageId, { importUrl, imageSrc, imageAlt, author, previewUrl }); }; const thumbnail = (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Loading Skeleton */} {!imageLoaded && !imageError && (
)} {/* Actual Image */} {imageAlt} setImageLoaded(true)} onError={() => setImageError(true)} className={`w-full h-full object-cover rounded-xl transition-all duration-500 group-hover:scale-105 ${ imageLoaded ? 'opacity-100' : 'opacity-0' }`} /> {/* Error State */} {imageError && (
)} {/* GIF Badge - show when it's an animated GIF */} {gifStillUrl && gifAnimatedUrl && (
GIF
)}
{setPreviewImage && ( )} {selectionMode && toggleSelection && providerId != null && imageId != null && ( )} {toggleFavorite && providerId != null && imageId != null && ( )} {providerBadge && !selectionMode && ( {providerBadge} )}
); if (variant === "list") { return (
{selectionMode && toggleSelection && providerId != null && imageId != null && ( )}
{imageAlt}

{imageAlt}

{setPreviewImage && ( )} {toggleFavorite && providerId != null && imageId != null && ( )} {importUrl && } {downloadOptions.length > 0 && }
); } return (
{thumbnail} {/* Action bar is outside overflow-hidden so the dropdown popup is never clipped. focus-within reveals it for keyboard users too: hover alone left a Tab-focused Import button and download menu fully transparent. */}
{importUrl && } {downloadOptions.length > 0 && }
); } export default memo(ImageCard);