From f35a454614cd4e5f4bb970e57850e3d28be96405 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Mon, 9 May 2022 20:52:10 +0530 Subject: [PATCH] fix: don't close popover when previewing file (#1017) --- .../Components/ApplicationView/index.tsx | 93 +++++++++---------- .../AttachedFilesButton.tsx | 13 ++- .../Components/Files/FilePreviewModal.tsx | 66 ++++++------- .../Files/FilePreviewModalProvider.tsx | 53 ----------- .../javascripts/UIModels/AppState/AppState.ts | 2 + .../AppState/FilePreviewModalState.ts | 34 +++++++ 6 files changed, 126 insertions(+), 135 deletions(-) delete mode 100644 app/assets/javascripts/Components/Files/FilePreviewModalProvider.tsx create mode 100644 app/assets/javascripts/UIModels/AppState/FilePreviewModalState.ts diff --git a/app/assets/javascripts/Components/ApplicationView/index.tsx b/app/assets/javascripts/Components/ApplicationView/index.tsx index e1379234d..0e5e9e115 100644 --- a/app/assets/javascripts/Components/ApplicationView/index.tsx +++ b/app/assets/javascripts/Components/ApplicationView/index.tsx @@ -22,7 +22,7 @@ import { PremiumModalProvider } from '@/Hooks/usePremiumModal' import { ConfirmSignoutContainer } from '@/Components/ConfirmSignoutModal' import { TagsContextMenu } from '@/Components/Tags/TagContextMenu' import { ToastContainer } from '@standardnotes/stylekit' -import { FilePreviewModalProvider } from '@/Components/Files/FilePreviewModalProvider' +import { FilePreviewModal } from '../Files/FilePreviewModal' type Props = { application: WebApplication @@ -169,54 +169,53 @@ export class ApplicationView extends PureComponent { const renderAppContents = !this.state.needsUnlock && this.state.launched return ( - - -
- {renderAppContents && ( -
- - - -
- )} - {renderAppContents && ( - <> -
- - - - - )} - {this.state.challenges.map((challenge) => { - return ( -
- -
- ) - })} - {renderAppContents && ( - <> - - - - +
+ {renderAppContents && ( +
+ + + +
+ )} + {renderAppContents && ( + <> +
+ + + + + )} + {this.state.challenges.map((challenge) => { + return ( +
+ - - - )} -
- - +
+ ) + })} + {renderAppContents && ( + <> + + + + + + + + )} +
+
) } } diff --git a/app/assets/javascripts/Components/AttachedFilesPopover/AttachedFilesButton.tsx b/app/assets/javascripts/Components/AttachedFilesPopover/AttachedFilesButton.tsx index 8c878d802..ca55425fd 100644 --- a/app/assets/javascripts/Components/AttachedFilesPopover/AttachedFilesButton.tsx +++ b/app/assets/javascripts/Components/AttachedFilesPopover/AttachedFilesButton.tsx @@ -15,7 +15,6 @@ import { StreamingFileReader } from '@standardnotes/filepicker' import { PopoverFileItemAction, PopoverFileItemActionType } from './PopoverFileItemAction' import { AttachedFilesPopover } from './AttachedFilesPopover' import { usePremiumModal } from '@/Hooks/usePremiumModal' -import { useFilePreviewModal } from '../Files/FilePreviewModalProvider' import { PopoverTabs } from './PopoverTabs' type Props = { @@ -30,7 +29,6 @@ const isHandlingFileDrag = (event: DragEvent) => export const AttachedFilesButton: FunctionComponent = observer( ({ application, appState, onClickPreprocessing }) => { const premiumModal = usePremiumModal() - const filePreviewModal = useFilePreviewModal() const note: SNNote | undefined = Object.values(appState.notes.selectedNotes)[0] @@ -45,6 +43,14 @@ export const AttachedFilesButton: FunctionComponent = observer( const containerRef = useRef(null) const [closeOnBlur, keepMenuOpen] = useCloseOnBlur(containerRef, setOpen) + useEffect(() => { + if (appState.filePreviewModal.isOpen) { + keepMenuOpen(true) + } else { + keepMenuOpen(false) + } + }, [appState.filePreviewModal.isOpen, keepMenuOpen]) + const [currentTab, setCurrentTab] = useState(PopoverTabs.AttachedFiles) const [allFiles, setAllFiles] = useState([]) const [attachedFiles, setAttachedFiles] = useState([]) @@ -205,7 +211,8 @@ export const AttachedFilesButton: FunctionComponent = observer( await renameFile(file, action.payload.name) break case PopoverFileItemActionType.PreviewFile: - filePreviewModal.activate(file, currentTab === PopoverTabs.AllFiles ? allFiles : attachedFiles) + keepMenuOpen(true) + appState.filePreviewModal.activate(file, currentTab === PopoverTabs.AllFiles ? allFiles : attachedFiles) break } diff --git a/app/assets/javascripts/Components/Files/FilePreviewModal.tsx b/app/assets/javascripts/Components/Files/FilePreviewModal.tsx index 32682380c..2fe6eccaf 100644 --- a/app/assets/javascripts/Components/Files/FilePreviewModal.tsx +++ b/app/assets/javascripts/Components/Files/FilePreviewModal.tsx @@ -1,7 +1,6 @@ import { WebApplication } from '@/UIModels/Application' import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays' import { DialogContent, DialogOverlay } from '@reach/dialog' -import { SNFile } from '@standardnotes/snjs' import { addToast, NoPreviewIllustration, ToastType } from '@standardnotes/stylekit' import { FunctionComponent } from 'preact' import { useCallback, useEffect, useRef, useState } from 'preact/hooks' @@ -12,18 +11,21 @@ import { FilePreviewInfoPanel } from './FilePreviewInfoPanel' import { isFileTypePreviewable } from './isFilePreviewable' import { PreviewComponent } from './PreviewComponent' import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants' -import { useFilePreviewModal } from './FilePreviewModalProvider' import { KeyboardKey } from '@/Services/IOService' +import { AppState } from '@/UIModels/AppState' +import { observer } from 'mobx-react-lite' type Props = { application: WebApplication - files: SNFile[] - file: SNFile - onDismiss: () => void + appState: AppState } -export const FilePreviewModal: FunctionComponent = ({ application, files, file, onDismiss }) => { - const context = useFilePreviewModal() +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) @@ -37,7 +39,7 @@ export const FilePreviewModal: FunctionComponent = ({ application, files, try { const chunks: Uint8Array[] = [] setFileDownloadProgress(0) - await application.files.downloadFile(file, async (decryptedChunk, progress) => { + await application.files.downloadFile(currentFile, async (decryptedChunk, progress) => { chunks.push(decryptedChunk) if (progress) { setFileDownloadProgress(Math.round(progress.percentComplete)) @@ -47,7 +49,7 @@ export const FilePreviewModal: FunctionComponent = ({ application, files, setObjectUrl( URL.createObjectURL( new Blob([finalDecryptedBytes], { - type: file.mimeType, + type: currentFile.mimeType, }), ), ) @@ -56,32 +58,32 @@ export const FilePreviewModal: FunctionComponent = ({ application, files, } finally { setIsLoadingFile(false) } - }, [application.files, file]) + }, [application.files, currentFile]) useEffect(() => { setIsLoadingFile(true) - }, [file.uuid]) + }, [currentFile.uuid]) useEffect(() => { - const isPreviewable = isFileTypePreviewable(file.mimeType) + const isPreviewable = isFileTypePreviewable(currentFile.mimeType) setIsFilePreviewable(isPreviewable) if (!isPreviewable) { setIsLoadingFile(false) } - if (currentFileIdRef.current !== file.uuid && isPreviewable) { + if (currentFileIdRef.current !== currentFile.uuid && isPreviewable) { getObjectUrl().catch(console.error) } - currentFileIdRef.current = file.uuid + currentFileIdRef.current = currentFile.uuid return () => { if (objectUrl) { URL.revokeObjectURL(objectUrl) } } - }, [file, getObjectUrl, objectUrl]) + }, [currentFile, getObjectUrl, objectUrl]) const keyDownHandler = (event: KeyboardEvent) => { if (event.key !== KeyboardKey.Left && event.key !== KeyboardKey.Right) { @@ -90,22 +92,22 @@ export const FilePreviewModal: FunctionComponent = ({ application, files, event.preventDefault() - const currentFileIndex = files.findIndex((fileFromArray) => fileFromArray.uuid === file.uuid) + const currentFileIndex = otherFiles.findIndex((fileFromArray) => fileFromArray.uuid === currentFile.uuid) switch (event.key) { case KeyboardKey.Left: { - const previousFileIndex = currentFileIndex - 1 >= 0 ? currentFileIndex - 1 : files.length - 1 - const previousFile = files[previousFileIndex] + const previousFileIndex = currentFileIndex - 1 >= 0 ? currentFileIndex - 1 : otherFiles.length - 1 + const previousFile = otherFiles[previousFileIndex] if (previousFile) { - context.setCurrentFile(previousFile) + setCurrentFile(previousFile) } break } case KeyboardKey.Right: { - const nextFileIndex = currentFileIndex + 1 < files.length ? currentFileIndex + 1 : 0 - const nextFile = files[nextFileIndex] + const nextFileIndex = currentFileIndex + 1 < otherFiles.length ? currentFileIndex + 1 : 0 + const nextFile = otherFiles[nextFileIndex] if (nextFile) { - context.setCurrentFile(nextFile) + setCurrentFile(nextFile) } break } @@ -116,7 +118,7 @@ export const FilePreviewModal: FunctionComponent = ({ application, files, @@ -137,11 +139,11 @@ export const FilePreviewModal: FunctionComponent = ({ application, files,
{getFileIconComponent( - application.iconsController.getIconForFileType(file.mimeType), + application.iconsController.getIconForFileType(currentFile.mimeType), 'w-6 h-6 flex-shrink-0', )}
- {file.name} + {currentFile.name}
) : objectUrl ? ( - + ) : (
@@ -210,7 +212,7 @@ export const FilePreviewModal: FunctionComponent = ({ application, files,
)} - {showFileInfoPanel && } + {showFileInfoPanel && }
) -} +}) diff --git a/app/assets/javascripts/Components/Files/FilePreviewModalProvider.tsx b/app/assets/javascripts/Components/Files/FilePreviewModalProvider.tsx deleted file mode 100644 index 92760bdb0..000000000 --- a/app/assets/javascripts/Components/Files/FilePreviewModalProvider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { WebApplication } from '@/UIModels/Application' -import { SNFile } from '@standardnotes/snjs' -import { createContext, FunctionComponent } from 'preact' -import { useContext, useState } from 'preact/hooks' -import { FilePreviewModal } from './FilePreviewModal' - -type FilePreviewActivateFunction = (file: SNFile, files: SNFile[]) => void - -type FilePreviewModalContextData = { - activate: FilePreviewActivateFunction - setCurrentFile: (file: SNFile) => void -} - -const FilePreviewModalContext = createContext(null) - -export const useFilePreviewModal = (): FilePreviewModalContextData => { - const value = useContext(FilePreviewModalContext) - - if (!value) { - throw new Error('FilePreviewModalProvider not found.') - } - - return value -} - -export const FilePreviewModalProvider: FunctionComponent<{ - application: WebApplication -}> = ({ application, children }) => { - const [isOpen, setIsOpen] = useState(false) - const [currentFile, setCurrentFile] = useState() - const [files, setFiles] = useState([]) - - const activate: FilePreviewActivateFunction = (file, files) => { - setCurrentFile(file) - setFiles(files) - setIsOpen(true) - } - - const close = () => { - setIsOpen(false) - } - - return ( - <> - - {isOpen && currentFile && ( - - )} - {children} - - - ) -} diff --git a/app/assets/javascripts/UIModels/AppState/AppState.ts b/app/assets/javascripts/UIModels/AppState/AppState.ts index b05455041..5402695f2 100644 --- a/app/assets/javascripts/UIModels/AppState/AppState.ts +++ b/app/assets/javascripts/UIModels/AppState/AppState.ts @@ -32,6 +32,7 @@ import { SubscriptionState } from './SubscriptionState' import { SyncState } from './SyncState' import { TagsState } from './TagsState' import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice' +import { FilePreviewModalState } from './FilePreviewModalState' export enum AppStateEvent { TagChanged, @@ -84,6 +85,7 @@ export class AppState { readonly notesView: NotesViewState readonly subscription: SubscriptionState readonly files: FilesState + readonly filePreviewModal = new FilePreviewModalState() isSessionsModalVisible = false diff --git a/app/assets/javascripts/UIModels/AppState/FilePreviewModalState.ts b/app/assets/javascripts/UIModels/AppState/FilePreviewModalState.ts new file mode 100644 index 000000000..44cd7635f --- /dev/null +++ b/app/assets/javascripts/UIModels/AppState/FilePreviewModalState.ts @@ -0,0 +1,34 @@ +import { SNFile } from '@standardnotes/snjs/dist/@types' +import { action, makeObservable, observable } from 'mobx' + +export class FilePreviewModalState { + isOpen = false + currentFile: SNFile | undefined = undefined + otherFiles: SNFile[] = [] + + constructor() { + makeObservable(this, { + isOpen: observable, + currentFile: observable, + otherFiles: observable, + + activate: action, + dismiss: action, + setCurrentFile: action, + }) + } + + setCurrentFile = (currentFile: SNFile) => { + this.currentFile = currentFile + } + + activate = (currentFile: SNFile, otherFiles: SNFile[]) => { + this.currentFile = currentFile + this.otherFiles = otherFiles + this.isOpen = true + } + + dismiss = () => { + this.isOpen = false + } +}