Viewing File: /home/markqprx/iniasli.pro/client/uploads/preview/file-preview-container.tsx

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>
  );
}
Back to Directory File Manager