diff --git a/pkg/interface/src/logic/lib/useDrag.ts b/pkg/interface/src/logic/lib/useDrag.ts index 5b4878fb24..43072deca1 100644 --- a/pkg/interface/src/logic/lib/useDrag.ts +++ b/pkg/interface/src/logic/lib/useDrag.ts @@ -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, diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 1ebe6e48ba..dc6cb6338c 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -353,4 +353,4 @@ export function usePreventWindowUnload(shouldPreventDefault: boolean, message = export function pluralize(text: string, isPlural = false, vowel = false) { return isPlural ? `${text}s`: `${vowel ? 'an' : 'a'} ${text}`; -} +} \ No newline at end of file diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index 0d8c905f74..65171325b9 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -68,7 +68,7 @@ export function ChatResource(props: ChatResourceProps) { const chatInput = useRef(); const onFileDrag = useCallback( - (files: FileList) => { + (files: FileList | File[]) => { if (!chatInput.current) { return; } diff --git a/pkg/interface/src/views/apps/chat/components/ChatInput.tsx b/pkg/interface/src/views/apps/chat/components/ChatInput.tsx index 09ff23904d..fd6773a173 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatInput.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatInput.tsx @@ -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 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() { diff --git a/pkg/interface/src/views/components/s3-upload.tsx b/pkg/interface/src/views/components/s3-upload.tsx index d89ef154a9..89b7c06277 100644 --- a/pkg/interface/src/views/components/s3-upload.tsx +++ b/pkg/interface/src/views/components/s3-upload.tsx @@ -51,6 +51,7 @@ export class S3Upload extends Component { 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 { ); } - 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 { }, 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();