import { WebApplication } from '@/Application/Application' import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays' import { DialogContent, DialogOverlay } from '@reach/dialog' import { addToast, ToastType } from '@standardnotes/stylekit' import { NoPreviewIllustration } from '@standardnotes/icons' import { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { getFileIconComponent } from '@/Components/AttachedFilesPopover/getFileIconComponent' import Button from '@/Components/Button/Button' import Icon from '@/Components/Icon/Icon' import FilePreviewInfoPanel from './FilePreviewInfoPanel' import { isFileTypePreviewable } from './isFilePreviewable' import PreviewComponent from './PreviewComponent' import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants' import { KeyboardKey } from '@/Services/IOService' import { ViewControllerManager } from '@/Services/ViewControllerManager' import { observer } from 'mobx-react-lite' type Props = { application: WebApplication viewControllerManager: ViewControllerManager } const FilePreviewModal: FunctionComponent = observer(({ application, viewControllerManager }) => { const { currentFile, setCurrentFile, otherFiles, dismiss } = viewControllerManager.filePreviewModalController if (!currentFile) { return null } const [objectUrl, setObjectUrl] = useState() const [isFilePreviewable, setIsFilePreviewable] = useState(false) const [isLoadingFile, setIsLoadingFile] = useState(true) const [fileDownloadProgress, setFileDownloadProgress] = useState(0) const [showFileInfoPanel, setShowFileInfoPanel] = useState(false) const currentFileIdRef = useRef() const closeButtonRef = useRef(null) const getObjectUrl = useCallback(async () => { try { const chunks: Uint8Array[] = [] setFileDownloadProgress(0) await application.files.downloadFile(currentFile, async (decryptedChunk, progress) => { chunks.push(decryptedChunk) if (progress) { setFileDownloadProgress(Math.round(progress.percentComplete)) } }) const finalDecryptedBytes = concatenateUint8Arrays(chunks) setObjectUrl( URL.createObjectURL( new Blob([finalDecryptedBytes], { type: currentFile.mimeType, }), ), ) } catch (error) { console.error(error) } finally { setIsLoadingFile(false) } }, [application.files, currentFile]) useEffect(() => { setIsLoadingFile(true) }, [currentFile.uuid]) useEffect(() => { const isPreviewable = isFileTypePreviewable(currentFile.mimeType) setIsFilePreviewable(isPreviewable) if (!isPreviewable) { setObjectUrl('') setIsLoadingFile(false) } if (currentFileIdRef.current !== currentFile.uuid && isPreviewable) { getObjectUrl().catch(console.error) } currentFileIdRef.current = currentFile.uuid return () => { if (objectUrl) { URL.revokeObjectURL(objectUrl) } } }, [currentFile, getObjectUrl, objectUrl]) const keyDownHandler: KeyboardEventHandler = useCallback( (event) => { if (event.key !== KeyboardKey.Left && event.key !== KeyboardKey.Right) { return } event.preventDefault() const currentFileIndex = otherFiles.findIndex((fileFromArray) => fileFromArray.uuid === currentFile.uuid) switch (event.key) { case KeyboardKey.Left: { const previousFileIndex = currentFileIndex - 1 >= 0 ? currentFileIndex - 1 : otherFiles.length - 1 const previousFile = otherFiles[previousFileIndex] if (previousFile) { setCurrentFile(previousFile) } break } case KeyboardKey.Right: { const nextFileIndex = currentFileIndex + 1 < otherFiles.length ? currentFileIndex + 1 : 0 const nextFile = otherFiles[nextFileIndex] if (nextFile) { setCurrentFile(nextFile) } break } } }, [currentFile.uuid, otherFiles, setCurrentFile], ) const IconComponent = useMemo( () => getFileIconComponent( application.iconsController.getIconForFileType(currentFile.mimeType), 'w-6 h-6 flex-shrink-0', ), [application.iconsController, currentFile.mimeType], ) return (
{IconComponent}
{currentFile.name}
{objectUrl && ( )}
{isLoadingFile ? (
{fileDownloadProgress}%
Loading file...
) : objectUrl ? ( ) : (
This file can't be previewed.
{isFilePreviewable ? ( <>
There was an error loading the file. Try again, or download the file and open it using another application.
) : ( <>
To view this file, download it and open it using another application.
)}
)}
{showFileInfoPanel && }
) }) FilePreviewModal.displayName = 'FilePreviewModal' const FilePreviewModalWrapper: FunctionComponent = ({ application, viewControllerManager }) => { return viewControllerManager.filePreviewModalController.isOpen ? ( ) : null } export default observer(FilePreviewModalWrapper)