1
- import { AuAlert , AuHelpText , AuIcon } from ' @appuniversum/ember-appuniversum' ;
2
1
import { action } from ' @ember/object' ;
3
2
import { guidFor } from ' @ember/object/internals' ;
4
3
import { inject as service } from ' @ember/service' ;
5
4
import Component from ' @glimmer/component' ;
6
5
import { tracked } from ' @glimmer/tracking' ;
7
6
import { task } from ' ember-concurrency' ;
8
- import perform from ' ember-concurrency/helpers/perform' ;
9
7
import FileDropzone from ' ember-file-upload/components/file-dropzone' ;
10
8
import fileQueue from ' ember-file-upload/helpers/file-queue' ;
9
+ import type { FileQueueService , UploadFile } from ' ember-file-upload' ;
10
+ import AuAlert from ' ./au-alert' ;
11
+ import AuHelpText from ' ./au-help-text' ;
12
+ import AuIcon from ' ./au-icon' ;
13
+
14
+ export interface AuFileUploadSignature {
15
+ Args: {
16
+ accept? : string ;
17
+ endPoint? : string ;
18
+ helpTextDragDrop? : string ;
19
+ helpTextFileNotSupported? : string ;
20
+ maxFileSizeMB? : number ;
21
+ multiple? : boolean ;
22
+ onFinishUpload? : (uploadedFile : number , queueInfo : QueueInfo ) => void ;
23
+ onQueueUpdate? : (queueInfo : QueueInfo ) => void ;
24
+ title? : string ;
25
+ };
26
+ // ember-file-upload doesn't seem to expose the signature of the FileDropzone component
27
+ // We just hardcode what they have defined in the addon for now.
28
+ Element: HTMLElement ;
29
+ }
30
+
31
+ export type QueueInfo = {
32
+ isQueueEmpty: boolean ;
33
+ };
34
+
35
+ // A simpler version of the File and UploadFile types
36
+ type BasicFile = {
37
+ name: string ;
38
+ type: string ;
39
+ };
40
+
41
+ type UploadError = {
42
+ filename: string ;
43
+ error? : string ;
44
+ };
11
45
12
- export default class AuFileUpload extends Component {
13
- @service fileQueue;
14
- @tracked uploadErrorData = [];
46
+ export default class AuFileUpload extends Component < AuFileUploadSignature > {
47
+ @service declare fileQueue: FileQueueService ;
48
+ @tracked uploadErrorData: UploadError [] = [];
15
49
16
50
get uploadingMsg() {
17
51
return ` Bezig met het opladen van ${this .queue .files .length } bestand(en). (${this .queue .progress }%) ` ;
@@ -55,37 +89,43 @@ export default class AuFileUpload extends Component {
55
89
return this .uploadErrorData .length > 0 ;
56
90
}
57
91
58
- @task
59
- * upload (file ) {
92
+ @action
93
+ upload(file : UploadFile ): void | undefined {
94
+ this .uploadTask .perform (file );
95
+ }
96
+
97
+ uploadTask = task (async (file : UploadFile ) => {
60
98
this .resetErrors ();
61
- let uploadedFile = yield this .uploadFileTask .perform (file);
99
+ const uploadedFile = await this .uploadFileTask .perform (file );
62
100
63
101
this .notifyQueueUpdate ();
64
102
65
103
if (uploadedFile && this .args .onFinishUpload )
66
104
this .args .onFinishUpload (uploadedFile , this .calculateQueueInfo ());
67
- }
105
+ });
68
106
69
- @task ({ enqueue: true , maxConcurrency: 3 })
70
- * uploadFileTask (file ) {
71
- this .notifyQueueUpdate ();
72
- try {
73
- const response = yield file .upload (this .endPoint , {
74
- ' Content-Type' : ' multipart/form-data' ,
75
- });
76
- const body = yield response .json ();
77
- const fileId = body? .data ? .id ;
78
- return fileId;
79
- } catch (e) {
80
- this .addError (file);
81
- this .removeFileFromQueue (file);
82
- return null ;
83
- }
84
- }
107
+ uploadFileTask = task (
108
+ { enqueue: true , maxConcurrency: 3 },
109
+ async (file : UploadFile ): Promise <number | null > => {
110
+ this .notifyQueueUpdate ();
111
+ try {
112
+ const response = await file .upload (this .endPoint , {
113
+ contentType: ' multipart/form-data' ,
114
+ });
115
+ const body = await response .json ();
116
+ const fileId = body ?.data ?.id ;
117
+ return fileId ;
118
+ } catch (e ) {
119
+ this .addError (file );
120
+ this .removeFileFromQueue (file );
121
+ return null ;
122
+ }
123
+ },
124
+ );
85
125
86
126
@action
87
- filter (file , files , index ) {
88
- let isFirstFile = index === 0 ;
127
+ filter(file : File , files : File [] , index : number ) {
128
+ const isFirstFile = index === 0 ;
89
129
90
130
if (isFirstFile ) {
91
131
this .resetErrors ();
@@ -115,14 +155,14 @@ export default class AuFileUpload extends Component {
115
155
}
116
156
}
117
157
118
- calculateQueueInfo () {
158
+ calculateQueueInfo(): QueueInfo {
119
159
const filesQueueInfo = {
120
160
isQueueEmpty: this .uploadFileTask .isIdle ,
121
161
};
122
162
return filesQueueInfo ;
123
163
}
124
164
125
- addError (file , errorMessage ) {
165
+ addError(file : BasicFile , errorMessage ? : string ) {
126
166
this .uploadErrorData = [
127
167
... this .uploadErrorData ,
128
168
{
@@ -136,13 +176,13 @@ export default class AuFileUpload extends Component {
136
176
this .uploadErrorData = [];
137
177
}
138
178
139
- removeFileFromQueue (file ) {
179
+ removeFileFromQueue(file : UploadFile ) {
140
180
this .queue .remove (file );
141
181
}
142
182
143
183
<template >
144
184
{{#let
145
- ( fileQueue name =this . queueName onFileAdded =( perform this . upload ) )
185
+ ( fileQueue name =this . queueName onFileAdded =this . uploadTask.perform )
146
186
as | queue |
147
187
}}
148
188
<FileDropzone
@@ -212,39 +252,39 @@ export default class AuFileUpload extends Component {
212
252
}
213
253
214
254
// Private util that is exported for testing purposes
215
- export function isValidFileType (file , accept ) {
255
+ export function isValidFileType(file : Partial < BasicFile > , accept ? : string ) {
216
256
if (! accept ) {
217
257
return true ;
218
258
}
219
259
220
- let tokens = accept .split (' ,' ).map (function (token ) {
260
+ const tokens = accept .split (' ,' ).map (function (token ) {
221
261
return token .trim ().toLowerCase ();
222
262
});
223
263
224
- let validMimeTypes = tokens .filter (function (token ) {
264
+ const validMimeTypes = tokens .filter (function (token ) {
225
265
return ! token .startsWith (' .' );
226
266
});
227
- let type = file .type ? .toLowerCase ();
267
+ const type = file .type ?.toLowerCase ();
228
268
229
- let validExtensions = tokens .filter (function (token ) {
269
+ const validExtensions = tokens .filter (function (token ) {
230
270
return token .startsWith (' .' );
231
271
});
232
272
233
273
let extension = null ;
234
274
if (file .name && / (\. [^ . ] + )$ / .test (file .name )) {
235
- extension = file .name .toLowerCase ().match (/ (\. [^ . ] + )$ / )[1 ];
275
+ extension = file .name .toLowerCase ().match (/ (\. [^ . ] + )$ / )?. [1 ];
236
276
}
237
277
238
278
return (
239
- isValidMimeType (type, validMimeTypes) ||
240
- isValidExtension (extension, validExtensions)
279
+ ( !! type && isValidMimeType (type , validMimeTypes ) ) ||
280
+ ( !! extension && isValidExtension (extension , validExtensions ) )
241
281
);
242
282
}
243
283
244
- function isValidMimeType (type , validMimeTypes = []) {
284
+ function isValidMimeType(type : string , validMimeTypes : string [] = []) {
245
285
return validMimeTypes .some (function (validType ) {
246
286
if ([' audio/*' , ' video/*' , ' image/*' ].includes (validType )) {
247
- let genericType = validType .split (' /' )[0 ];
287
+ const genericType = validType .split (' /' )[0 ] as string ;
248
288
249
289
return type .startsWith (genericType );
250
290
} else {
@@ -253,10 +293,13 @@ function isValidMimeType(type, validMimeTypes = []) {
253
293
});
254
294
}
255
295
256
- function isValidExtension (extension , validExtensions = []) {
296
+ function isValidExtension(
297
+ extension : string ,
298
+ validExtensions : string [] = [],
299
+ ): boolean {
257
300
return validExtensions .includes (extension );
258
301
}
259
302
260
- function isValidFileSize (fileSize , maximumSize ) {
303
+ function isValidFileSize(fileSize : number , maximumSize : number ) : boolean {
261
304
return fileSize < maximumSize * Math .pow (1024 , 2 );
262
305
}
0 commit comments