s3: improves drag-and-drop behavior

Fixes #3992

...to the extent possible. Adds handlers for better releasing drag state
This commit is contained in:
Tyler Brown Cifu Shuster 2020-11-26 09:32:21 -08:00
parent fb6488bc68
commit 1eb6c47c83
5 changed files with 66 additions and 25 deletions

View File

@ -1,15 +1,39 @@
import { useState, useCallback, useMemo, useEffect } from "react";
function validateDragEvent(e: DragEvent): FileList | null {
const files = e.dataTransfer?.files;
console.log(files);
if(!files?.length) {
return null;
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = [];
let valid = false;
if (e.dataTransfer?.files) {
Array.from(e.dataTransfer.files).forEach(f => files.push(f));
}
return files || null;
if (e.dataTransfer?.items) {
Array.from(e.dataTransfer.items || [])
.filter((i) => i.kind === 'file')
.forEach(f => {
valid = true; // Valid if file exists, but on DragOver, won't reveal its contents for security
const data = f.getAsFile();
if (data) {
files.push(data);
}
});
}
if (files.length) {
return [...new Set(files)];
}
if (navigator.userAgent.includes('Safari')) {
if (e.dataTransfer?.effectAllowed === 'all') {
valid = true;
} else if (e.dataTransfer?.files.length) {
return e.dataTransfer.files;
}
}
if (valid) {
return true;
}
return null;
}
export function useFileDrag(dragged: (f: FileList) => void) {
export function useFileDrag(dragged: (f: FileList | File[]) => void) {
const [dragging, setDragging] = useState(false);
const onDragEnter = useCallback(
@ -25,11 +49,11 @@ export function useFileDrag(dragged: (f: FileList) => void) {
const onDrop = useCallback(
(e: DragEvent) => {
setDragging(false);
e.preventDefault();
const files = validateDragEvent(e);
if (!files) {
if (!files || files === true) {
return;
}
e.preventDefault();
dragged(files);
},
[setDragging, dragged]
@ -37,6 +61,9 @@ export function useFileDrag(dragged: (f: FileList) => void) {
const onDragOver = useCallback(
(e: DragEvent) => {
if (!validateDragEvent(e)) {
return;
}
e.preventDefault();
setDragging(true);
},
@ -53,6 +80,18 @@ export function useFileDrag(dragged: (f: FileList) => void) {
[setDragging]
);
useEffect(() => {
const mouseleave = (e) => {
if (!e.relatedTarget && !e.toElement) {
setDragging(false);
}
};
document.body.addEventListener('mouseout', mouseleave);
return () => {
document.body.removeEventListener('mouseout', mouseleave);
}
}, [setDragging]);
const bind = {
onDragLeave,
onDragOver,

View File

@ -68,7 +68,7 @@ export function ChatResource(props: ChatResourceProps) {
const chatInput = useRef<ChatInput>();
const onFileDrag = useCallback(
(files: FileList) => {
(files: FileList | File[]) => {
if (!chatInput.current) {
return;
}

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import ChatEditor from './chat-editor';
import { S3Upload } from '~/views/components/s3-upload' ;
import { uxToHex } from '~/logic/lib/util';
import { fileListFromFileArray, uxToHex } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil';
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
import GlobalApi from '~/logic/api/global';
@ -150,16 +150,14 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
this.uploadFiles(event.clipboardData.files);
}
uploadFiles(files: FileList) {
uploadFiles(files: FileList | File[]) {
if (!this.readyToUpload()) {
return;
}
if (!this.s3Uploader.current || !this.s3Uploader.current.inputRef.current)
return;
this.s3Uploader.current.inputRef.current.files = files;
const fire = document.createEvent('HTMLEvents');
fire.initEvent('change', true, true);
this.s3Uploader.current?.inputRef.current?.dispatchEvent(fire);
if (!this.s3Uploader.current) {
return;
}
this.s3Uploader.current.uploadFiles(files);
}
render() {

View File

@ -51,6 +51,7 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
this.s3 = new S3Client();
this.setCredentials(props.credentials, props.configuration);
this.inputRef = React.createRef();
this.uploadFiles = this.uploadFiles.bind(this);
}
isReady(creds, config): boolean {
@ -84,13 +85,9 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
);
}
onChange(): void {
uploadFiles(files: FileList | File[]) {
const { props } = this;
if (!this.inputRef.current) { return; }
let files = this.inputRef.current.files;
if (!files || files.length <= 0) { return; }
let file = files.item(0);
let file = ('item' in files) ? files.item(0) : files[0];
if (!file) { return; }
const fileParts = file.name.split('.');
const fileName = fileParts.slice(0, -1);
@ -118,6 +115,13 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
}, 200);
}
onChange(): void {
if (!this.inputRef.current) { return; }
let files = this.inputRef.current.files;
if (!files || files.length <= 0) { return; }
this.uploadFiles(files);
}
onClick() {
if (!this.inputRef.current) { return; }
this.inputRef.current.click();