From 5b42eedd978e0de6be51fae7889a85e6b4eda7c9 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sat, 12 Mar 2022 19:30:51 +0530 Subject: [PATCH] feat: listen to file dnd event on window instead of just popover (#921) --- .../AttachedFilesButton.tsx | 116 ++++++++++++-- .../AttachedFilesPopover.tsx | 40 +++-- .../PopoverDragNDropWrapper.tsx | 143 ------------------ 3 files changed, 138 insertions(+), 161 deletions(-) delete mode 100644 app/assets/javascripts/components/AttachedFilesPopover/PopoverDragNDropWrapper.tsx diff --git a/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesButton.tsx b/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesButton.tsx index 0a21ccea81d..01f40bea70c 100644 --- a/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesButton.tsx +++ b/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesButton.tsx @@ -15,12 +15,12 @@ import { useCloseOnClickOutside } from '../utils'; import { ChallengeReason, ContentType, SNFile } from '@standardnotes/snjs'; import { confirmDialog } from '@/services/alertService'; import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit'; -import { parseFileName } from '@standardnotes/filepicker'; +import { parseFileName, StreamingFileReader } from '@standardnotes/filepicker'; import { PopoverFileItemAction, PopoverFileItemActionType, } from './PopoverFileItemAction'; -import { PopoverDragNDropWrapper } from './PopoverDragNDropWrapper'; +import { AttachedFilesPopover, PopoverTabs } from './AttachedFilesPopover'; type Props = { application: WebApplication; @@ -68,7 +68,7 @@ export const AttachedFilesButton: FunctionComponent = observer( }; }, [application, reloadAttachedFilesCount]); - const toggleAttachedFilesMenu = async () => { + const toggleAttachedFilesMenu = useCallback(async () => { const rect = buttonRef.current?.getBoundingClientRect(); if (rect) { const { clientHeight } = document.documentElement; @@ -98,7 +98,7 @@ export const AttachedFilesButton: FunctionComponent = observer( setOpen(newOpenState); } - }; + }, [onClickPreprocessing, open]); const deleteFile = async (file: SNFile) => { const shouldDelete = await confirmDialog({ @@ -123,9 +123,12 @@ export const AttachedFilesButton: FunctionComponent = observer( appState.files.downloadFile(file); }; - const attachFileToNote = async (file: SNFile) => { - await application.items.associateFileWithNote(file, note); - }; + const attachFileToNote = useCallback( + async (file: SNFile) => { + await application.items.associateFileWithNote(file, note); + }, + [application.items, note] + ); const detachFileFromNote = async (file: SNFile) => { await application.items.disassociateFileWithNote(file, note); @@ -210,6 +213,98 @@ export const AttachedFilesButton: FunctionComponent = observer( return true; }; + const [isDraggingFiles, setIsDraggingFiles] = useState(false); + const [currentTab, setCurrentTab] = useState(PopoverTabs.AttachedFiles); + const dragCounter = useRef(0); + + const handleDrag = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + const handleDragIn = useCallback( + (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragCounter.current = dragCounter.current + 1; + + if (event.dataTransfer?.items.length) { + setIsDraggingFiles(true); + if (!open) { + toggleAttachedFilesMenu(); + } + } + }, + [open, toggleAttachedFilesMenu] + ); + + const handleDragOut = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragCounter.current = dragCounter.current - 1; + + if (dragCounter.current > 0) { + return; + } + + setIsDraggingFiles(false); + }; + + const handleDrop = useCallback( + (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + setIsDraggingFiles(false); + + if (event.dataTransfer?.items.length) { + Array.from(event.dataTransfer.items).forEach(async (item) => { + const fileOrHandle = StreamingFileReader.available() + ? ((await item.getAsFileSystemHandle()) as FileSystemFileHandle) + : item.getAsFile(); + + if (!fileOrHandle) { + return; + } + + const uploadedFiles = await appState.files.uploadNewFile( + fileOrHandle + ); + + if (!uploadedFiles) { + return; + } + + if (currentTab === PopoverTabs.AttachedFiles) { + uploadedFiles.forEach((file) => { + attachFileToNote(file); + }); + } + }); + + event.dataTransfer.clearData(); + dragCounter.current = 0; + } + }, + [appState.files, attachFileToNote, currentTab] + ); + + useEffect(() => { + window.addEventListener('dragenter', handleDragIn); + window.addEventListener('dragleave', handleDragOut); + window.addEventListener('dragover', handleDrag); + window.addEventListener('drop', handleDrop); + + return () => { + window.removeEventListener('dragenter', handleDragIn); + window.removeEventListener('dragleave', handleDragOut); + window.removeEventListener('dragover', handleDrag); + window.removeEventListener('drop', handleDrop); + }; + }, [handleDragIn, handleDrop]); + return (
@@ -245,11 +340,14 @@ export const AttachedFilesButton: FunctionComponent = observer( className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed" > {open && ( - )} diff --git a/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesPopover.tsx b/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesPopover.tsx index 887e3dcbeab..3d7e2377501 100644 --- a/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesPopover.tsx +++ b/app/assets/javascripts/components/AttachedFilesPopover/AttachedFilesPopover.tsx @@ -1,16 +1,30 @@ -import { ContentType, SNFile } from '@standardnotes/snjs'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { ContentType, SNFile, SNNote } from '@standardnotes/snjs'; import { FilesIllustration } from '@standardnotes/stylekit'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; import { StateUpdater, useCallback, useEffect, useState } from 'preact/hooks'; import { Button } from '../Button'; import { Icon } from '../Icon'; -import { PopoverTabs, PopoverWrapperProps } from './PopoverDragNDropWrapper'; import { PopoverFileItem } from './PopoverFileItem'; -import { PopoverFileItemActionType } from './PopoverFileItemAction'; +import { + PopoverFileItemAction, + PopoverFileItemActionType, +} from './PopoverFileItemAction'; -type Props = PopoverWrapperProps & { +export enum PopoverTabs { + AttachedFiles, + AllFiles, +} + +type Props = { + application: WebApplication; + appState: AppState; currentTab: PopoverTabs; + handleFileAction: (action: PopoverFileItemAction) => Promise; + isDraggingFiles: boolean; + note: SNNote; setCurrentTab: StateUpdater; }; @@ -18,9 +32,10 @@ export const AttachedFilesPopover: FunctionComponent = observer( ({ application, appState, - note, - fileActionHandler, currentTab, + handleFileAction, + isDraggingFiles, + note, setCurrentTab, }) => { const [attachedFiles, setAttachedFiles] = useState([]); @@ -74,7 +89,7 @@ export const AttachedFilesPopover: FunctionComponent = observer( } if (currentTab === PopoverTabs.AttachedFiles) { uploadedFiles.forEach((file) => { - fileActionHandler({ + handleFileAction({ type: PopoverFileItemActionType.AttachFileToNote, payload: file, }); @@ -83,7 +98,14 @@ export const AttachedFilesPopover: FunctionComponent = observer( }; return ( -
+