quasar-ui-danx 0.3.28 → 0.3.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,302 +4,309 @@ import { resolveFileLocation } from "./files";
4
4
  import { FlashMessages } from "./FlashMessages";
5
5
 
6
6
  export type FileUploadOptions = {
7
- directory?: string,
8
- presignedUploadUrl?: Function | null;
9
- uploadCompletedUrl?: Function | null;
7
+ directory?: string,
8
+ presignedUploadUrl?: Function | null;
9
+ uploadCompletedUrl?: Function | null;
10
10
  };
11
11
 
12
12
  export type UploadedFile = {
13
- id: string,
14
- name: string,
15
- size: number,
16
- type: string,
17
- progress: number,
18
- location: string,
19
- blobUrl: string,
20
- url: string,
13
+ id: string,
14
+ name: string,
15
+ size: number,
16
+ type: string,
17
+ progress: number,
18
+ location: string,
19
+ blobUrl: string,
20
+ url: string,
21
21
  }
22
22
 
23
23
  export class FileUpload {
24
- files: UploadedFile[] = [];
25
- fileUploads: UploadedFile[] = [];
26
- onErrorCb: Function | null = null;
27
- onProgressCb: Function | null = null;
28
- onCompleteCb: Function | null = null;
29
- onAllCompleteCb: Function | null = null;
30
- options: FileUploadOptions | null = {};
31
-
32
- constructor(files: UploadedFile[] | UploadedFile, options: FileUploadOptions | null = {}) {
33
- this.files = !Array.isArray(files) && !(files instanceof FileList) ? [files] : files;
34
- this.fileUploads = [];
35
- this.onErrorCb = null;
36
- this.onProgressCb = null;
37
- this.onCompleteCb = null;
38
- this.onAllCompleteCb = null;
39
-
40
- this.options = {
41
- ...danxOptions.value.fileUpload,
42
- ...options
43
- };
44
-
45
- if (!this.options.presignedUploadUrl) {
46
- throw new Error("Please configure the danxOptions: import { configure } from 'quasar-ui-danx';");
47
- }
48
- this.prepare();
49
- }
50
-
51
- /**
52
- * Prepares all files for upload and provides an id and blobUrl for each file
53
- */
54
- prepare() {
55
- // Prepare required attributes
56
- for (const file of this.files) {
57
- if (!(file instanceof File)) {
58
- throw Error(
59
- "FileUpload constructor requires a File object or a list of File objects"
60
- );
61
- }
62
-
63
- file.id = uid();
64
- file.blobUrl = URL.createObjectURL(file);
65
-
66
- // Prepare FormData
67
- const formData = new FormData();
68
- formData.append("file", file);
69
-
70
- this.fileUploads.push({
71
- file,
72
- xhr: null, // NOTE: The XHR will be setup asynchronously right before sending file uploads
73
- formData,
74
- isComplete: false
75
- });
76
- }
77
- }
78
-
79
- /**
80
- * Callback for when all files have been uploaded
81
- */
82
- onAllComplete(cb: Function) {
83
- this.onAllCompleteCb = cb;
84
- return this;
85
- }
86
-
87
- /**
88
- * Callback fired once for each file upon successful completion of upload
89
- * @param cb
90
- * @returns {FileUpload}
91
- */
92
- onComplete(cb: Function) {
93
- this.onCompleteCb = cb;
94
- return this;
95
- }
96
-
97
- /**
98
- * Callback fired each time there is an upload progress update for a file
99
- * @param cb
100
- * @returns {FileUpload}
101
- */
102
- onProgress(cb: Function) {
103
- this.onProgressCb = cb;
104
- return this;
105
- }
106
-
107
- /**
108
- * Callback fired when an error occurs during upload
109
- * @param cb
110
- * @returns {FileUpload}
111
- */
112
- onError(cb: Function) {
113
- this.onErrorCb = cb;
114
- return this;
115
- }
116
-
117
- /**
118
- * Handles the error events / fires the callback if it is set
119
- * @param e
120
- * @param file
121
- * @param error
122
- */
123
- errorHandler(e: InputEvent, file: UploadedFile, error = null) {
124
- if (this.onErrorCb) {
125
- this.onErrorCb({ e, file, error });
126
- }
127
- }
128
-
129
- /**
130
- * Resolve the locations of all the files
131
- * @returns {Promise<FileUpload>}
132
- */
133
- async resolveLocation(waitMessage = null) {
134
- for (const fileUpload of this.fileUploads) {
135
- fileUpload.file.location = await resolveFileLocation(
136
- fileUpload.file,
137
- waitMessage
138
- );
139
- fileUpload.formData.append(
140
- "meta",
141
- JSON.stringify(fileUpload.file.location)
142
- );
143
- }
144
- return this;
145
- }
146
-
147
- /**
148
- * Fires the progress callback
149
- * @param fileUpload
150
- * @param progress
151
- */
152
- fireProgressCallback(fileUpload, progress) {
153
- fileUpload.file.progress = progress;
154
- this.onProgressCb && this.onProgressCb({ file: this.wrapFile(fileUpload.file), progress });
155
- }
156
-
157
- /**
158
- * Fires the complete callback
159
- * @param fileUpload
160
- * @param uploadedFile
161
- */
162
- fireCompleteCallback(fileUpload, uploadedFile) {
163
- fileUpload.isComplete = true;
164
- fileUpload.file.progress = 1;
165
- this.onCompleteCb && this.onCompleteCb({ file: this.wrapFile(fileUpload.file), uploadedFile });
166
- }
167
-
168
- /**
169
- * Check if all files have been uploaded and call the callback if they have
170
- */
171
- checkAllComplete() {
172
- if (this.onAllCompleteCb) {
173
- if (this.fileUploads.every((fileUpload) => fileUpload.isComplete)) {
174
- this.onAllCompleteCb({ files: this.fileUploads });
175
- }
176
- }
177
- }
178
-
179
- /**
180
- * Returns a native JS object that is easier to work with than the File objects (no weird behavior of missing
181
- * properties, easily printable, etc.)
182
- * @param file
183
- * @returns {{size, name, progress, location, blobUrl: *, id, type}}
184
- */
185
- wrapFile(file) {
186
- return {
187
- id: file.id,
188
- name: file.name,
189
- size: file.size,
190
- type: file.type,
191
- progress: file.progress,
192
- location: file.location,
193
- blobUrl: file.blobUrl
194
- };
195
- }
196
-
197
- /**
198
- * Registers all the callbacks requested for the XHR / post-processing of file uploads
199
- */
200
- setXhrCallbacks() {
201
- // Set the error callbacks
202
- for (const fileUpload of this.fileUploads) {
203
- fileUpload.xhr.addEventListener(
204
- "error",
205
- (e) => this.errorHandler(e, fileUpload.file),
206
- false
207
- );
208
- }
209
-
210
- // Set the progress callbacks
211
- if (this.onProgressCb) {
212
- for (const fileUpload of this.fileUploads) {
213
- fileUpload.xhr.upload.addEventListener(
214
- "progress",
215
- (e) => {
216
- // Max of 95%, so we can indicate we are completing the signed URL process
217
- const progress = Math.min(.95, e.loaded / e.total);
218
- this.fireProgressCallback(fileUpload, progress);
219
- },
220
- false
221
- );
222
- }
223
- }
224
-
225
- // Set the load callbacks which registers the Complete / All Complete callbacks and handles non-xhr related
226
- // errors
227
- for (const fileUpload of this.fileUploads) {
228
- fileUpload.xhr.addEventListener(
229
- "load",
230
- async (e) => {
231
- try {
232
- // First complete the presigned upload to get the updated file resource data
233
- const uploadedFile = await this.completePresignedUpload(fileUpload);
234
-
235
- // Fire the file complete callbacks
236
- this.fireCompleteCallback(fileUpload, uploadedFile);
237
- this.checkAllComplete();
238
- } catch (error) {
239
- this.errorHandler(e, fileUpload.file, error);
240
- }
241
- },
242
- false
243
- );
244
- }
245
- }
246
-
247
- /**
248
- * Mark the presigned upload as completed and return the file resource from the platform server
249
- * @param fileUpload
250
- * @returns {Promise<void>}
251
- */
252
- async completePresignedUpload(fileUpload) {
253
- // Show 95% as the last 5% will be to complete the presigned upload
254
- this.fireProgressCallback(fileUpload, .95);
255
-
256
- // Let the platform know the presigned upload is complete
257
- return await fetch(this.options.uploadCompletedUrl(fileUpload.file.resource_id), { method: "POST" }).then(r => r.json());
258
- }
259
-
260
- /**
261
- * Start uploading all files
262
- */
263
- async upload() {
264
- for (const fileUpload of this.fileUploads) {
265
- const mimeType = fileUpload.file.mimeType || fileUpload.file.type;
266
- const presignedUrl = this.options.presignedUploadUrl(this.options.directory, fileUpload.file.name, mimeType);
267
-
268
- // Fetch presigned upload URL
269
- const fileResource = await fetch(presignedUrl).then(r => r.json());
270
-
271
- if (!fileResource.url) {
272
- FlashMessages.error("Could not fetch presigned upload URL for file " + fileUpload.file.name);
273
- continue;
274
- }
275
-
276
- const isS3Upload = !fileResource.url.match("upload-presigned-url-contents");
277
-
278
- // We need the file resource ID to complete the presigned upload
279
- fileUpload.file.resource_id = fileResource.id;
280
-
281
- // Prepare XHR request
282
- const xhr = new XMLHttpRequest();
283
-
284
- // The XHR request is different based on weather we're sending to S3 or the platform server
285
- if (isS3Upload) {
286
- xhr.open("PUT", fileResource.url);
287
- xhr.setRequestHeader("Content-Type", mimeType);
288
- fileUpload.body = fileUpload.file;
289
- } else {
290
- xhr.open("POST", fileResource.url);
291
- fileUpload.body = fileUpload.formData;
292
- }
293
-
294
- fileUpload.xhr = xhr;
295
- }
296
-
297
- // Set all the callbacks on the XHR requests
298
- this.setXhrCallbacks();
299
-
300
- // Send all the XHR file uploads
301
- for (const fileUpload of this.fileUploads) {
302
- fileUpload.xhr.send(fileUpload.body);
303
- }
304
- }
24
+ files: UploadedFile[] = [];
25
+ fileUploads: UploadedFile[] = [];
26
+ onErrorCb: Function | null = null;
27
+ onProgressCb: Function | null = null;
28
+ onCompleteCb: Function | null = null;
29
+ onAllCompleteCb: Function | null = null;
30
+ options: FileUploadOptions | null = {};
31
+
32
+ constructor(files: UploadedFile[] | UploadedFile, options: FileUploadOptions | null = {}) {
33
+ this.files = !Array.isArray(files) && !(files instanceof FileList) ? [files] : files;
34
+ this.fileUploads = [];
35
+ this.onErrorCb = null;
36
+ this.onProgressCb = null;
37
+ this.onCompleteCb = null;
38
+ this.onAllCompleteCb = null;
39
+
40
+ this.options = {
41
+ ...danxOptions.value.fileUpload,
42
+ ...options
43
+ };
44
+
45
+ if (!this.options.presignedUploadUrl) {
46
+ throw new Error("Please configure the danxOptions: import { configure } from 'quasar-ui-danx';");
47
+ }
48
+ this.prepare();
49
+ }
50
+
51
+ /**
52
+ * Prepares all files for upload and provides an id and blobUrl for each file
53
+ */
54
+ prepare() {
55
+ // Prepare required attributes
56
+ for (const file of this.files) {
57
+ if (!(file instanceof File)) {
58
+ throw Error(
59
+ "FileUpload constructor requires a File object or a list of File objects"
60
+ );
61
+ }
62
+
63
+ file.id = uid();
64
+ file.blobUrl = URL.createObjectURL(file);
65
+
66
+ // Prepare FormData
67
+ const formData = new FormData();
68
+ formData.append("file", file);
69
+
70
+ this.fileUploads.push({
71
+ file,
72
+ xhr: null, // NOTE: The XHR will be setup asynchronously right before sending file uploads
73
+ formData,
74
+ isComplete: false
75
+ });
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Callback for when all files have been uploaded
81
+ */
82
+ onAllComplete(cb: Function) {
83
+ this.onAllCompleteCb = cb;
84
+ return this;
85
+ }
86
+
87
+ /**
88
+ * Callback fired once for each file upon successful completion of upload
89
+ * @param cb
90
+ * @returns {FileUpload}
91
+ */
92
+ onComplete(cb: Function) {
93
+ this.onCompleteCb = cb;
94
+ return this;
95
+ }
96
+
97
+ /**
98
+ * Callback fired each time there is an upload progress update for a file
99
+ * @param cb
100
+ * @returns {FileUpload}
101
+ */
102
+ onProgress(cb: Function) {
103
+ this.onProgressCb = cb;
104
+ return this;
105
+ }
106
+
107
+ /**
108
+ * Callback fired when an error occurs during upload
109
+ * @param cb
110
+ * @returns {FileUpload}
111
+ */
112
+ onError(cb: Function) {
113
+ this.onErrorCb = cb;
114
+ return this;
115
+ }
116
+
117
+ /**
118
+ * Handles the error events / fires the callback if it is set
119
+ * @param e
120
+ * @param file
121
+ * @param error
122
+ */
123
+ errorHandler(e: InputEvent, file: UploadedFile, error = null) {
124
+ if (this.onErrorCb) {
125
+ this.onErrorCb({ e, file, error });
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Resolve the locations of all the files
131
+ * @returns {Promise<FileUpload>}
132
+ */
133
+ async resolveLocation(waitMessage = null) {
134
+ for (const fileUpload of this.fileUploads) {
135
+ fileUpload.file.location = await resolveFileLocation(
136
+ fileUpload.file,
137
+ waitMessage
138
+ );
139
+
140
+ fileUpload.formData.append(
141
+ "meta",
142
+ JSON.stringify(fileUpload.file.location)
143
+ );
144
+ }
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * Fires the progress callback
150
+ * @param fileUpload
151
+ * @param progress
152
+ */
153
+ fireProgressCallback(fileUpload, progress) {
154
+ fileUpload.file.progress = progress;
155
+ this.onProgressCb && this.onProgressCb({ file: this.wrapFile(fileUpload.file), progress });
156
+ }
157
+
158
+ /**
159
+ * Fires the complete callback
160
+ * @param fileUpload
161
+ * @param uploadedFile
162
+ */
163
+ fireCompleteCallback(fileUpload, uploadedFile) {
164
+ fileUpload.isComplete = true;
165
+ fileUpload.file.progress = 1;
166
+ this.onCompleteCb && this.onCompleteCb({ file: this.wrapFile(fileUpload.file), uploadedFile });
167
+ }
168
+
169
+ /**
170
+ * Check if all files have been uploaded and call the callback if they have
171
+ */
172
+ checkAllComplete() {
173
+ if (this.onAllCompleteCb) {
174
+ if (this.fileUploads.every((fileUpload) => fileUpload.isComplete)) {
175
+ this.onAllCompleteCb({ files: this.fileUploads });
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Returns a native JS object that is easier to work with than the File objects (no weird behavior of missing
182
+ * properties, easily printable, etc.)
183
+ * @param file
184
+ * @returns {{size, name, progress, location, blobUrl: *, id, type}}
185
+ */
186
+ wrapFile(file) {
187
+ return {
188
+ id: file.id,
189
+ name: file.name,
190
+ size: file.size,
191
+ type: file.type,
192
+ progress: file.progress,
193
+ location: file.location,
194
+ blobUrl: file.blobUrl
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Registers all the callbacks requested for the XHR / post-processing of file uploads
200
+ */
201
+ setXhrCallbacks() {
202
+ // Set the error callbacks
203
+ for (const fileUpload of this.fileUploads) {
204
+ fileUpload.xhr.addEventListener(
205
+ "error",
206
+ (e) => this.errorHandler(e, fileUpload.file),
207
+ false
208
+ );
209
+ }
210
+
211
+ // Set the progress callbacks
212
+ if (this.onProgressCb) {
213
+ for (const fileUpload of this.fileUploads) {
214
+ fileUpload.xhr.upload.addEventListener(
215
+ "progress",
216
+ (e) => {
217
+ // Max of 95%, so we can indicate we are completing the signed URL process
218
+ const progress = Math.min(.95, e.loaded / e.total);
219
+ this.fireProgressCallback(fileUpload, progress);
220
+ },
221
+ false
222
+ );
223
+ }
224
+ }
225
+
226
+ // Set the load callbacks which registers the Complete / All Complete callbacks and handles non-xhr related
227
+ // errors
228
+ for (const fileUpload of this.fileUploads) {
229
+ fileUpload.xhr.addEventListener(
230
+ "load",
231
+ async (e) => {
232
+ try {
233
+ // First complete the presigned upload to get the updated file resource data
234
+ const uploadedFile = await this.completePresignedUpload(fileUpload);
235
+
236
+ // Fire the file complete callbacks
237
+ this.fireCompleteCallback(fileUpload, uploadedFile);
238
+ this.checkAllComplete();
239
+ } catch (error) {
240
+ this.errorHandler(e, fileUpload.file, error);
241
+ }
242
+ },
243
+ false
244
+ );
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Mark the presigned upload as completed and return the file resource from the platform server
250
+ * @param fileUpload
251
+ * @returns {Promise<void>}
252
+ */
253
+ async completePresignedUpload(fileUpload) {
254
+ // Show 95% as the last 5% will be to complete the presigned upload
255
+ this.fireProgressCallback(fileUpload, .95);
256
+
257
+ // Let the platform know the presigned upload is complete
258
+ return await fetch(this.options.uploadCompletedUrl(fileUpload.file.resource_id), {
259
+ method: "POST",
260
+ headers: {
261
+ "Content-Type": "application/json"
262
+ },
263
+ body: JSON.stringify({ meta: fileUpload.file.location })
264
+ }).then(r => r.json());
265
+ }
266
+
267
+ /**
268
+ * Start uploading all files
269
+ */
270
+ async upload() {
271
+ for (const fileUpload of this.fileUploads) {
272
+ const mimeType = fileUpload.file.mimeType || fileUpload.file.type;
273
+ const presignedUrl = this.options.presignedUploadUrl(this.options.directory, fileUpload.file.name, mimeType);
274
+
275
+ // Fetch presigned upload URL
276
+ const fileResource = await fetch(presignedUrl).then(r => r.json());
277
+
278
+ if (!fileResource.url) {
279
+ FlashMessages.error("Could not fetch presigned upload URL for file " + fileUpload.file.name);
280
+ continue;
281
+ }
282
+
283
+ const isS3Upload = !fileResource.url.match("upload-presigned-url-contents");
284
+
285
+ // We need the file resource ID to complete the presigned upload
286
+ fileUpload.file.resource_id = fileResource.id;
287
+
288
+ // Prepare XHR request
289
+ const xhr = new XMLHttpRequest();
290
+
291
+ // The XHR request is different based on weather we're sending to S3 or the platform server
292
+ if (isS3Upload) {
293
+ xhr.open("PUT", fileResource.url);
294
+ xhr.setRequestHeader("Content-Type", mimeType);
295
+ fileUpload.body = fileUpload.file;
296
+ } else {
297
+ xhr.open("POST", fileResource.url);
298
+ fileUpload.body = fileUpload.formData;
299
+ }
300
+
301
+ fileUpload.xhr = xhr;
302
+ }
303
+
304
+ // Set all the callbacks on the XHR requests
305
+ this.setXhrCallbacks();
306
+
307
+ // Send all the XHR file uploads
308
+ for (const fileUpload of this.fileUploads) {
309
+ fileUpload.xhr.send(fileUpload.body);
310
+ }
311
+ }
305
312
  }