import { useState, useEffect, useCallback } from 'react'; import { Maximize2 } from 'lucide-react'; import { OGDialog, OGDialogContent } from '~/components/ui'; import { FileSources } from 'librechat-data-provider'; import ProgressCircle from './ProgressCircle'; import SourceIcon from './SourceIcon'; import { cn } from '~/utils'; type styleProps = { backgroundImage?: string; backgroundSize?: string; backgroundPosition?: string; backgroundRepeat?: string; }; interface CloseModalEvent { stopPropagation: () => void; preventDefault: () => void; } const ImagePreview = ({ imageBase64, url, progress = 1, className = '', source, alt = 'Preview image', }: { imageBase64?: string; url?: string; progress?: number; className?: string; source?: FileSources; alt?: string; }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); const [previousActiveElement, setPreviousActiveElement] = useState(null); const openModal = useCallback(() => { setPreviousActiveElement(document.activeElement); setIsModalOpen(true); }, []); const closeModal = useCallback( (e: CloseModalEvent): void => { setIsModalOpen(false); e.stopPropagation(); e.preventDefault(); if ( previousActiveElement instanceof HTMLElement && !previousActiveElement.closest('[data-skip-refocus="true"]') ) { previousActiveElement.focus(); } }, [previousActiveElement], ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === 'Escape') { closeModal(e); } }, [closeModal], ); useEffect(() => { if (isModalOpen) { document.addEventListener('keydown', handleKeyDown); document.body.style.overflow = 'hidden'; const closeButton = document.querySelector('[aria-label="Close full view"]') as HTMLElement; if (closeButton) { setTimeout(() => closeButton.focus(), 0); } } return () => { document.removeEventListener('keydown', handleKeyDown); document.body.style.overflow = 'unset'; }; }, [isModalOpen, handleKeyDown]); const baseStyle: styleProps = { backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', }; const imageUrl = imageBase64 ?? url ?? ''; const style: styleProps = imageUrl ? { ...baseStyle, backgroundImage: `url(${imageUrl})`, } : baseStyle; if (typeof style.backgroundImage !== 'string' || style.backgroundImage.length === 0) { return null; } const radius = 55; const circumference = 2 * Math.PI * radius; const offset = circumference - progress * circumference; const circleCSSProperties = { transition: 'stroke-dashoffset 0.3s linear', }; return ( <>
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} >
{alt} ); }; export default ImagePreview;