@@ -5,6 +5,12 @@ import { UUIFileDropzoneEvent } from './UUIFileDropzoneEvent';
5
5
import { LabelMixin } from '@umbraco-ui/uui-base/lib/mixins' ;
6
6
import { demandCustomElement } from '@umbraco-ui/uui-base/lib/utils' ;
7
7
8
+ export interface UUIFileFolder {
9
+ folderName : string ;
10
+ folders : UUIFileFolder [ ] ;
11
+ files : File [ ] ;
12
+ }
13
+
8
14
/**
9
15
* @element uui-file-dropzone
10
16
* @fires {UUIFileDropzoneEvent } change - fires when the a file has been selected.
@@ -66,6 +72,13 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
66
72
return this . _accept ;
67
73
}
68
74
75
+ @property ( {
76
+ type : Boolean ,
77
+ reflect : true ,
78
+ attribute : 'disallow-folder-upload' ,
79
+ } )
80
+ public disallowFolderUpload : boolean = false ;
81
+
69
82
/**
70
83
* Allows for multiple files to be selected.
71
84
* @type {boolean }
@@ -97,59 +110,69 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
97
110
demandCustomElement ( this , 'uui-symbol-file-dropzone' ) ;
98
111
}
99
112
100
- private async _getAllFileEntries (
101
- dataTransferItemList : DataTransferItemList ,
102
- ) : Promise < File [ ] > {
103
- const fileEntries : File [ ] = [ ] ;
113
+ private async _getAllEntries ( dataTransferItemList : DataTransferItemList ) {
104
114
// Use BFS to traverse entire directory/file structure
105
115
const queue = [ ...dataTransferItemList ] ;
106
116
107
- while ( queue . length > 0 ) {
108
- const entry = queue . shift ( ) ! ;
117
+ const folders : UUIFileFolder [ ] = [ ] ;
118
+ const files : File [ ] = [ ] ;
109
119
110
- if ( entry . kind === 'file' ) {
120
+ for ( const entry of queue ) {
121
+ if ( entry . type ) {
122
+ // Entry is a file
111
123
const file = entry . getAsFile ( ) ;
112
124
if ( ! file ) continue ;
113
125
if ( this . _isAccepted ( file ) ) {
114
- fileEntries . push ( file ) ;
126
+ files . push ( file ) ;
127
+ }
128
+ } else if ( ! entry . type && ! this . disallowFolderUpload ) {
129
+ // Entry is a directive. The entry kind is "file" for both files and directories which seems like a bug. The file type is empty however. Can we trust this?
130
+ if ( 'webkitGetAsEntry' in entry === true ) {
131
+ const dir = entry . webkitGetAsEntry ( ) as FileSystemDirectoryEntry ;
132
+ folders . push ( await this . _mkdir ( dir ) ) ;
133
+ } else if ( 'getAsEntry' in entry === true ) {
134
+ // non-WebKit browsers may rename webkitGetAsEntry to getAsEntry. MDN recommends looking for both.
135
+ //@ts -ignore
136
+ const dir = entry . getAsEntry ( ) as FileSystemDirectoryEntry ;
137
+ folders . push ( await this . _mkdir ( dir ) ) ;
115
138
}
116
- } else if ( entry . kind === 'directory' ) {
117
- if ( 'webkitGetAsEntry' in entry === false ) continue ;
118
- const directory = entry . webkitGetAsEntry ( ) ! as FileSystemDirectoryEntry ;
119
- queue . push (
120
- ...( await this . _readAllDirectoryEntries ( directory . createReader ( ) ) ) ,
121
- ) ;
122
139
}
123
140
}
124
-
125
- return fileEntries ;
141
+ return { files, folders } ;
126
142
}
127
143
128
- // Get all the entries (files or sub-directories) in a directory
129
- // by calling readEntries until it returns empty array
130
- private async _readAllDirectoryEntries (
131
- directoryReader : FileSystemDirectoryReader ,
132
- ) {
133
- const entries : any = [ ] ;
134
- let readEntries : any = await this . _readEntriesPromise ( directoryReader ) ;
135
- while ( readEntries . length > 0 ) {
136
- entries . push ( ...readEntries ) ;
137
- readEntries = await this . _readEntriesPromise ( directoryReader ) ;
138
- }
139
- return entries ;
140
- }
144
+ // Make directory structure
145
+ private async _mkdir (
146
+ entry : FileSystemDirectoryEntry ,
147
+ ) : Promise < UUIFileFolder > {
148
+ const reader = entry . createReader ( ) ;
149
+ const folders : UUIFileFolder [ ] = [ ] ;
150
+ const files : File [ ] = [ ] ;
151
+
152
+ const readEntries = ( reader : FileSystemDirectoryReader ) => {
153
+ reader . readEntries ( async entries => {
154
+ if ( ! entries . length ) return ;
155
+
156
+ for ( const en of entries ) {
157
+ if ( en . isFile ) {
158
+ const file = await this . _getAsFile ( en as FileSystemFileEntry ) ;
159
+ if ( this . _isAccepted ( file ) ) {
160
+ files . push ( file ) ;
161
+ }
162
+ } else if ( en . isDirectory ) {
163
+ const directory = await this . _mkdir ( en as FileSystemDirectoryEntry ) ;
164
+ folders . push ( directory ) ;
165
+ }
166
+ }
141
167
142
- private async _readEntriesPromise (
143
- directoryReader : FileSystemDirectoryReader ,
144
- ) {
145
- return new Promise ( ( resolve , reject ) => {
146
- try {
147
- directoryReader . readEntries ( resolve , reject ) ;
148
- } catch ( err ) {
149
- console . log ( err ) ;
150
- reject ( err ) ;
151
- }
152
- } ) ;
168
+ readEntries ( reader ) ;
169
+ } ) ;
170
+ } ;
171
+
172
+ readEntries ( reader ) ;
173
+
174
+ const result : UUIFileFolder = { folderName : entry . name , folders, files } ;
175
+ return result ;
153
176
}
154
177
155
178
private _isAccepted ( file : File ) {
@@ -184,22 +207,28 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
184
207
return false ;
185
208
}
186
209
210
+ private async _getAsFile ( fileEntry : FileSystemFileEntry ) : Promise < File > {
211
+ return new Promise ( ( resolve , reject ) => fileEntry . file ( resolve , reject ) ) ;
212
+ }
213
+
187
214
private async _onDrop ( e : DragEvent ) {
188
215
e . preventDefault ( ) ;
189
216
this . _dropzone . classList . remove ( 'hover' ) ;
190
217
191
218
const items = e . dataTransfer ?. items ;
192
219
193
220
if ( items ) {
194
- let result = await this . _getAllFileEntries ( items ) ;
221
+ const fileSystemResult = await this . _getAllEntries ( items ) ;
195
222
196
- if ( this . multiple === false && result . length ) {
197
- result = [ result [ 0 ] ] ;
223
+ if ( this . multiple === false && fileSystemResult . files . length ) {
224
+ fileSystemResult . files = [ fileSystemResult . files [ 0 ] ] ;
198
225
}
199
226
227
+ this . _getAllEntries ( items ) ;
228
+
200
229
this . dispatchEvent (
201
230
new UUIFileDropzoneEvent ( UUIFileDropzoneEvent . CHANGE , {
202
- detail : { files : result } ,
231
+ detail : fileSystemResult ,
203
232
} ) ,
204
233
) ;
205
234
}
0 commit comments