JFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr{ gilour
import {AnimatePresence, m} from 'framer-motion'; import {Fragment, ReactNode, useContext, useMemo} from 'react'; import clsx from 'clsx'; import {getPreviewForEntry} from './available-previews'; import {FileEntry} from '../file-entry'; import {FilePreviewContext} from './file-preview-context'; import {IconButton} from '../../ui/buttons/icon-button'; import {ChevronLeftIcon} from '../../icons/material/ChevronLeft'; import {ChevronRightIcon} from '../../icons/material/ChevronRight'; import {FileDownloadIcon} from '../../icons/material/FileDownload'; import {downloadFileFromUrl} from '../utils/download-file-from-url'; import {useFileEntryUrls} from '../hooks/file-entry-urls'; import {Trans} from '../../i18n/trans'; import {Button} from '../../ui/buttons/button'; import {CloseIcon} from '../../icons/material/Close'; import {FileThumbnail} from '../file-type-icon/file-thumbnail'; import {useMediaQuery} from '../../utils/hooks/use-media-query'; import {KeyboardArrowLeftIcon} from '../../icons/material/KeyboardArrowLeft'; import {KeyboardArrowRightIcon} from '../../icons/material/KeyboardArrowRight'; import {useControlledState} from '@react-stately/utils'; import {opacityAnimation} from '../../ui/animation/opacity-animation'; export interface FilePreviewContainerProps { entries: FileEntry[]; activeIndex?: number; defaultActiveIndex?: number; onActiveIndexChange?: (index: number) => void; onClose?: () => void; showHeader?: boolean; headerActionsLeft?: ReactNode; className?: string; allowDownload?: boolean; } export function FilePreviewContainer({ entries, onClose, showHeader = true, className, headerActionsLeft, allowDownload = true, ...props }: FilePreviewContainerProps) { const isMobile = useMediaQuery('(max-width: 1024px)'); const [activeIndex, setActiveIndex] = useControlledState( props.activeIndex, props.defaultActiveIndex || 0, props.onActiveIndexChange ); const activeEntry = entries[activeIndex]; const contextValue = useMemo(() => { return {entries, activeIndex}; }, [entries, activeIndex]); const Preview = getPreviewForEntry(activeEntry); if (!activeEntry) { onClose?.(); return null; } const canOpenNext = entries.length - 1 > activeIndex; const openNext = () => { setActiveIndex(activeIndex + 1); }; const canOpenPrevious = activeIndex > 0; const openPrevious = () => { setActiveIndex(activeIndex - 1); }; return ( <FilePreviewContext.Provider value={contextValue}> {showHeader && ( <Header actionsLeft={headerActionsLeft} isMobile={isMobile} onClose={onClose} onNext={canOpenNext ? openNext : undefined} onPrevious={canOpenPrevious ? openPrevious : undefined} allowDownload={allowDownload} /> )} <div className={clsx('overflow-hidden relative flex-auto', className)}> {isMobile && ( <IconButton size="lg" className="text-muted absolute left-0 top-1/2 transform -translate-y-1/2 z-10" disabled={!canOpenPrevious} onClick={openPrevious} > <KeyboardArrowLeftIcon /> </IconButton> )} <AnimatePresence initial={false}> <m.div className="absolute inset-0 flex items-center justify-center" key={activeEntry.id} {...opacityAnimation} > <Preview className="max-h-[calc(100%-30px)]" entry={activeEntry} allowDownload={allowDownload} /> </m.div> </AnimatePresence> {isMobile && ( <IconButton size="lg" className="text-muted absolute right-0 top-1/2 transform -translate-y-1/2 z-10" disabled={!canOpenNext} onClick={openNext} > <KeyboardArrowRightIcon /> </IconButton> )} </div> </FilePreviewContext.Provider> ); } interface HeaderProps { onNext?: () => void; onPrevious?: () => void; onClose?: () => void; isMobile: boolean | null; actionsLeft?: ReactNode; allowDownload?: boolean; } function Header({ onNext, onPrevious, onClose, isMobile, actionsLeft, allowDownload, }: HeaderProps) { const {entries, activeIndex} = useContext(FilePreviewContext); const activeEntry = entries[activeIndex]; const {downloadUrl} = useFileEntryUrls(activeEntry); const desktopDownloadButton = ( <Button startIcon={<FileDownloadIcon />} variant="text" onClick={() => { if (downloadUrl) { downloadFileFromUrl(downloadUrl); } }} > <Trans message="Download" /> </Button> ); const mobileDownloadButton = ( <IconButton onClick={() => { if (downloadUrl) { downloadFileFromUrl(downloadUrl); } }} > <FileDownloadIcon /> </IconButton> ); const downloadButton = isMobile ? mobileDownloadButton : desktopDownloadButton; return ( <div className="flex items-center justify-between gap-20 bg-paper border-b flex-shrink-0 text-sm min-h-50 px-10 text-muted"> <div className="flex items-center gap-4 w-1/3 justify-start"> {actionsLeft} {allowDownload ? downloadButton : undefined} </div> <div className="flex items-center gap-10 w-1/3 justify-center flex-nowrap text-main"> <FileThumbnail file={activeEntry} iconClassName="w-16 h-16" showImage={false} /> <div className="whitespace-nowrap overflow-hidden overflow-ellipsis"> {activeEntry.name} </div> </div> <div className="w-1/3 flex items-center gap-10 justify-end whitespace-nowrap"> {!isMobile && ( <Fragment> <IconButton disabled={!onPrevious} onClick={onPrevious}> <ChevronLeftIcon /> </IconButton> <div>{activeIndex + 1}</div> <div>/</div> <div>{entries.length}</div> <IconButton disabled={!onNext} onClick={onNext}> <ChevronRightIcon /> </IconButton> <div className="bg-divider w-1 h-24 mx-20" /> </Fragment> )} <IconButton radius="rounded-none" onClick={onClose}> <CloseIcon /> </IconButton> </div> </div> ); }