import { WebApplication } from '@/UIModels/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 } from 'preact' import { useCallback, useEffect, useRef, useState } from 'preact/hooks' import { getFileIconComponent } from '@/Components/AttachedFilesPopover/PopoverFileItem' import { Button } from '@/Components/Button/Button' import { Icon } from '@/Components/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 { AppState } from '@/UIModels/AppState' import { observer } from 'mobx-react-lite' type Props = { application: WebApplication appState: AppState } export const FilePreviewModal: FunctionComponent = observer(({ application, appState }) => { const { currentFile, setCurrentFile, otherFiles, dismiss, isOpen } = appState.filePreviewModal if (!currentFile || !isOpen) { 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 = (event: KeyboardEvent) => { 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 } } } return (
{getFileIconComponent( application.iconsController.getIconForFileType(currentFile.mimeType), 'w-6 h-6 flex-shrink-0', )}
{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 && }
) })