@@ -7,7 +7,6 @@ import styled from 'styled-components/macro';
77import { ModalMask } from '@/components/elements/Modal' ;
88import Fade from '@/components/elements/Fade' ;
99import useEventListener from '@/plugins/useEventListener' ;
10- import SpinnerOverlay from '@/components/elements/SpinnerOverlay' ;
1110import useFlash from '@/plugins/useFlash' ;
1211import useFileManagerSwr from '@/plugins/useFileManagerSwr' ;
1312import { ServerContext } from '@/state/server' ;
@@ -19,18 +18,40 @@ const InnerContainer = styled.div`
1918 ${ tw `bg-black w-full border-4 border-primary-500 border-dashed rounded p-10 mx-10` }
2019` ;
2120
21+ function isFileOrDirectory ( event : DragEvent ) : boolean {
22+ if ( ! event . dataTransfer ?. types ) {
23+ return false ;
24+ }
25+
26+ for ( let i = 0 ; i < event . dataTransfer . types . length ; i ++ ) {
27+ // Check if the item being dragged is not a file.
28+ // On Firefox a file of type "application/x-moz-file" is also in the array.
29+ if ( event . dataTransfer . types [ i ] !== 'Files' && event . dataTransfer . types [ i ] !== 'application/x-moz-file' ) {
30+ return false ;
31+ }
32+ }
33+
34+ return true ;
35+ }
36+
2237export default ( { className } : WithClassname ) => {
2338 const fileUploadInput = useRef < HTMLInputElement > ( null ) ;
24- const uuid = ServerContext . useStoreState ( ( state ) => state . server . data ! . uuid ) ;
39+ const [ timeouts , setTimeouts ] = useState < NodeJS . Timeout [ ] > ( [ ] ) ;
2540 const [ visible , setVisible ] = useState ( false ) ;
26- const [ loading , setLoading ] = useState ( false ) ;
2741 const { mutate } = useFileManagerSwr ( ) ;
2842 const { clearFlashes, clearAndAddHttpError } = useFlash ( ) ;
43+
44+ const uuid = ServerContext . useStoreState ( ( state ) => state . server . data ! . uuid ) ;
2945 const directory = ServerContext . useStoreState ( ( state ) => state . files . directory ) ;
46+ const appendFileUpload = ServerContext . useStoreActions ( ( actions ) => actions . files . appendFileUpload ) ;
47+ const removeFileUpload = ServerContext . useStoreActions ( ( actions ) => actions . files . removeFileUpload ) ;
3048
3149 useEventListener (
3250 'dragenter' ,
3351 ( e ) => {
52+ if ( ! isFileOrDirectory ( e ) ) {
53+ return ;
54+ }
3455 e . stopPropagation ( ) ;
3556 setVisible ( true ) ;
3657 } ,
@@ -40,6 +61,9 @@ export default ({ className }: WithClassname) => {
4061 useEventListener (
4162 'dragexit' ,
4263 ( e ) => {
64+ if ( ! isFileOrDirectory ( e ) ) {
65+ return ;
66+ }
4367 e . stopPropagation ( ) ;
4468 setVisible ( false ) ;
4569 } ,
@@ -57,27 +81,47 @@ export default ({ className }: WithClassname) => {
5781 } ;
5882 } , [ visible ] ) ;
5983
60- const onFileSubmission = ( files : FileList ) => {
61- const form = new FormData ( ) ;
62- Array . from ( files ) . forEach ( ( file ) => form . append ( 'files' , file ) ) ;
84+ useEffect ( ( ) => {
85+ return ( ) => timeouts . forEach ( clearTimeout ) ;
86+ } , [ ] ) ;
6387
64- setLoading ( true ) ;
88+ const onFileSubmission = ( files : FileList ) => {
89+ const formData : FormData [ ] = [ ] ;
90+ Array . from ( files ) . forEach ( ( file ) => {
91+ const form = new FormData ( ) ;
92+ form . append ( 'files' , file ) ;
93+ formData . push ( form ) ;
94+ } ) ;
6595 clearFlashes ( 'files' ) ;
66- getFileUploadUrl ( uuid )
67- . then ( ( url ) =>
68- axios . post ( `${ url } &directory=${ directory } ` , form , {
69- headers : {
70- 'Content-Type' : 'multipart/form-data' ,
71- } ,
72- } )
96+ Promise . all (
97+ Array . from ( formData ) . map ( ( f ) =>
98+ getFileUploadUrl ( uuid ) . then ( ( url ) =>
99+ axios . post ( `${ url } &directory=${ directory } ` , f , {
100+ headers : { 'Content-Type' : 'multipart/form-data' } ,
101+ onUploadProgress : ( data : ProgressEvent ) => {
102+ // @ts -expect-error this is valid
103+ const name = f . getAll ( 'files' ) [ 0 ] . name ;
104+
105+ appendFileUpload ( {
106+ name : name ,
107+ loaded : data . loaded ,
108+ total : data . total ,
109+ } ) ;
110+
111+ if ( data . loaded === data . total ) {
112+ const timeout = setTimeout ( ( ) => removeFileUpload ( name ) , 2000 ) ;
113+ setTimeouts ( ( t ) => [ ...t , timeout ] ) ;
114+ }
115+ } ,
116+ } )
117+ )
73118 )
119+ )
74120 . then ( ( ) => mutate ( ) )
75121 . catch ( ( error ) => {
76122 console . error ( error ) ;
77123 clearAndAddHttpError ( { error, key : 'files' } ) ;
78- } )
79- . then ( ( ) => setVisible ( false ) )
80- . then ( ( ) => setLoading ( false ) ) ;
124+ } ) ;
81125 } ;
82126
83127 return (
@@ -97,14 +141,13 @@ export default ({ className }: WithClassname) => {
97141 onFileSubmission ( e . dataTransfer . files ) ;
98142 } }
99143 >
100- < div css = { tw `w-full flex items-center justify-center` } style = { { pointerEvents : ' none' } } >
144+ < div css = { tw `w-full flex items-center justify-center pointer-events- none` } >
101145 < InnerContainer >
102146 < p css = { tw `text-lg text-neutral-200 text-center` } > Drag and drop files to upload.</ p >
103147 </ InnerContainer >
104148 </ div >
105149 </ ModalMask >
106150 </ Fade >
107- < SpinnerOverlay visible = { loading } size = { 'large' } fixed />
108151 </ Portal >
109152 < input
110153 type = { 'file' }
@@ -119,12 +162,7 @@ export default ({ className }: WithClassname) => {
119162 }
120163 } }
121164 />
122- < Button
123- className = { className }
124- onClick = { ( ) => {
125- fileUploadInput . current ? fileUploadInput . current . click ( ) : setVisible ( true ) ;
126- } }
127- >
165+ < Button className = { className } onClick = { ( ) => fileUploadInput . current && fileUploadInput . current . click ( ) } >
128166 Upload
129167 </ Button >
130168 </ >
0 commit comments