From 56dda66c1c559a89dee9212e1727c099363eeeac Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Tue, 22 Jun 2021 12:08:39 +0100
Subject: [PATCH] Add file pasting and drag-n-drop.

---
 .../common/messaging/MessageBox.tsx           |  7 ++
 src/context/revoltjs/FileUploads.tsx          | 70 ++++++++++++++++++-
 2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index ffcc45a..e938e73 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -255,6 +255,13 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
                         remove={async () => setUploadState({ type: "none" })}
                         onChange={files => setUploadState({ type: "attached", files })}
                         cancel={() => uploadState.type === 'uploading' && uploadState.cancel.cancel("cancel")}
+                        append={files => {
+                            if (uploadState.type === 'none') {
+                                setUploadState({ type: 'attached', files });
+                            } else if (uploadState.type === 'attached') {
+                                setUploadState({ type: 'attached', files: [ ...uploadState.files, ...files ] });
+                            }
+                        }}
                     />
                 </Action>
                 <TextAreaAutoSize
diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx
index d485250..df10fb4 100644
--- a/src/context/revoltjs/FileUploads.tsx
+++ b/src/context/revoltjs/FileUploads.tsx
@@ -4,7 +4,7 @@ import classNames from "classnames";
 import { AppContext } from "./RevoltClient";
 import styles from './FileUploads.module.scss';
 import Axios, { AxiosRequestConfig } from "axios";
-import { useContext, useState } from "preact/hooks";
+import { useContext, useEffect, useState } from "preact/hooks";
 import Preloader from "../../components/ui/Preloader";
 import { determineFileSize } from "../../lib/fileSize";
 import IconButton from '../../components/ui/IconButton';
@@ -17,8 +17,8 @@ type Props = {
     fileType: 'backgrounds' | 'icons' | 'avatars' | 'attachments' | 'banners'
 } & (
     { behaviour: 'ask', onChange: (file: File) => void } |
-    { behaviour: 'multi', onChange: (files: File[]) => void } |
-    { behaviour: 'upload', onUpload: (id: string) => Promise<void> }
+    { behaviour: 'upload', onUpload: (id: string) => Promise<void> } |
+    { behaviour: 'multi', onChange: (files: File[]) => void, append?: (files: File[]) => void }
 ) & (
     { style: 'icon' | 'banner', defaultPreview?: string, previewURL?: string, width?: number, height?: number } |
     { style: 'attachment', attached: boolean, uploading: boolean, cancel: () => void, size?: number }
@@ -107,6 +107,70 @@ export function FileUploader(props: Props) {
         }
     }
 
+    if (props.behaviour === 'multi' && props.append) {
+        useEffect(() => {
+            // File pasting.
+            function paste(e: ClipboardEvent) {
+                const items = e.clipboardData?.items;
+                if (typeof items === "undefined") return;
+                if (props.behaviour !== 'multi' || !props.append) return;
+
+                let files = [];
+                for (const item of items) {
+                    if (!item.type.startsWith("text/")) {
+                        const blob = item.getAsFile();
+                        if (blob) {
+                            if (blob.size > props.maxFileSize) {
+                                openScreen({ id: 'error', error: 'FileTooLarge' });
+                            }
+
+                            files.push(blob);
+                        }
+                    }
+                }
+
+                props.append(files);
+            }
+
+            // Let the browser know we can drop files.
+            function dragover(e: DragEvent) {
+                e.stopPropagation();
+                e.preventDefault();
+                if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
+            }
+
+            // File dropping.
+            function drop(e: DragEvent) {
+                e.preventDefault();
+                if (props.behaviour !== 'multi' || !props.append) return;
+
+                const dropped = e.dataTransfer?.files;
+                if (dropped) {
+                    let files = [];
+                    for (const item of dropped) {
+                        if (item.size > props.maxFileSize) {
+                            openScreen({ id: 'error', error: 'FileTooLarge' });
+                        }
+
+                        files.push(item);
+                    }
+
+                    props.append(files);
+                }
+            }
+
+            document.addEventListener("paste", paste);
+            document.addEventListener("dragover", dragover);
+            document.addEventListener("drop", drop);
+
+            return () => {
+                document.removeEventListener("paste", paste);
+                document.removeEventListener("dragover", dragover);
+                document.removeEventListener("drop", drop);
+            };
+        }, [ props.append ]);
+    }
+
     if (props.style === 'icon' || props.style === 'banner') {
         const { style, previewURL, defaultPreview, width, height } = props;
         return (
-- 
GitLab