mirror of
https://github.com/urbit/shrub.git
synced 2024-12-20 09:21:42 +03:00
s3: improves drag-and-drop behavior
Fixes #3992 ...to the extent possible. Adds handlers for better releasing drag state
This commit is contained in:
parent
fb6488bc68
commit
1eb6c47c83
@ -1,15 +1,39 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
import { useState, useCallback, useMemo, useEffect } from "react";
|
||||||
|
|
||||||
function validateDragEvent(e: DragEvent): FileList | null {
|
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
|
||||||
const files = e.dataTransfer?.files;
|
const files: File[] = [];
|
||||||
console.log(files);
|
let valid = false;
|
||||||
if(!files?.length) {
|
if (e.dataTransfer?.files) {
|
||||||
return null;
|
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 [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
const onDragEnter = useCallback(
|
const onDragEnter = useCallback(
|
||||||
@ -25,11 +49,11 @@ export function useFileDrag(dragged: (f: FileList) => void) {
|
|||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(e: DragEvent) => {
|
(e: DragEvent) => {
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
e.preventDefault();
|
|
||||||
const files = validateDragEvent(e);
|
const files = validateDragEvent(e);
|
||||||
if (!files) {
|
if (!files || files === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
e.preventDefault();
|
||||||
dragged(files);
|
dragged(files);
|
||||||
},
|
},
|
||||||
[setDragging, dragged]
|
[setDragging, dragged]
|
||||||
@ -37,6 +61,9 @@ export function useFileDrag(dragged: (f: FileList) => void) {
|
|||||||
|
|
||||||
const onDragOver = useCallback(
|
const onDragOver = useCallback(
|
||||||
(e: DragEvent) => {
|
(e: DragEvent) => {
|
||||||
|
if (!validateDragEvent(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
},
|
},
|
||||||
@ -53,6 +80,18 @@ export function useFileDrag(dragged: (f: FileList) => void) {
|
|||||||
[setDragging]
|
[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 = {
|
const bind = {
|
||||||
onDragLeave,
|
onDragLeave,
|
||||||
onDragOver,
|
onDragOver,
|
||||||
|
@ -68,7 +68,7 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
const chatInput = useRef<ChatInput>();
|
const chatInput = useRef<ChatInput>();
|
||||||
|
|
||||||
const onFileDrag = useCallback(
|
const onFileDrag = useCallback(
|
||||||
(files: FileList) => {
|
(files: FileList | File[]) => {
|
||||||
if (!chatInput.current) {
|
if (!chatInput.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ChatEditor from './chat-editor';
|
import ChatEditor from './chat-editor';
|
||||||
import { S3Upload } from '~/views/components/s3-upload' ;
|
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 { Sigil } from '~/logic/lib/sigil';
|
||||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
@ -150,16 +150,14 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
|||||||
this.uploadFiles(event.clipboardData.files);
|
this.uploadFiles(event.clipboardData.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFiles(files: FileList) {
|
uploadFiles(files: FileList | File[]) {
|
||||||
if (!this.readyToUpload()) {
|
if (!this.readyToUpload()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.s3Uploader.current || !this.s3Uploader.current.inputRef.current)
|
if (!this.s3Uploader.current) {
|
||||||
return;
|
return;
|
||||||
this.s3Uploader.current.inputRef.current.files = files;
|
}
|
||||||
const fire = document.createEvent('HTMLEvents');
|
this.s3Uploader.current.uploadFiles(files);
|
||||||
fire.initEvent('change', true, true);
|
|
||||||
this.s3Uploader.current?.inputRef.current?.dispatchEvent(fire);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -51,6 +51,7 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
|||||||
this.s3 = new S3Client();
|
this.s3 = new S3Client();
|
||||||
this.setCredentials(props.credentials, props.configuration);
|
this.setCredentials(props.credentials, props.configuration);
|
||||||
this.inputRef = React.createRef();
|
this.inputRef = React.createRef();
|
||||||
|
this.uploadFiles = this.uploadFiles.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
isReady(creds, config): boolean {
|
isReady(creds, config): boolean {
|
||||||
@ -84,13 +85,9 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(): void {
|
uploadFiles(files: FileList | File[]) {
|
||||||
const { props } = this;
|
const { props } = this;
|
||||||
if (!this.inputRef.current) { return; }
|
let file = ('item' in files) ? files.item(0) : files[0];
|
||||||
let files = this.inputRef.current.files;
|
|
||||||
if (!files || files.length <= 0) { return; }
|
|
||||||
|
|
||||||
let file = files.item(0);
|
|
||||||
if (!file) { return; }
|
if (!file) { return; }
|
||||||
const fileParts = file.name.split('.');
|
const fileParts = file.name.split('.');
|
||||||
const fileName = fileParts.slice(0, -1);
|
const fileName = fileParts.slice(0, -1);
|
||||||
@ -118,6 +115,13 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
|||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange(): void {
|
||||||
|
if (!this.inputRef.current) { return; }
|
||||||
|
let files = this.inputRef.current.files;
|
||||||
|
if (!files || files.length <= 0) { return; }
|
||||||
|
this.uploadFiles(files);
|
||||||
|
}
|
||||||
|
|
||||||
onClick() {
|
onClick() {
|
||||||
if (!this.inputRef.current) { return; }
|
if (!this.inputRef.current) { return; }
|
||||||
this.inputRef.current.click();
|
this.inputRef.current.click();
|
||||||
|
Loading…
Reference in New Issue
Block a user