PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / assets / js / dropzone / dropzone.js

dropzone.js in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.1, at assets/js/dropzone/dropzone.js

2,312 lines 65.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import Emitter from "./emitter.js";
2 import defaultOptions from "./options.js";
3
4 export default class Dropzone extends Emitter {
5 static initClass() {
6 // Exposing the emitter class, mainly for tests
7 this.prototype.Emitter = Emitter;
8
9 /*
10 This is a list of all available events you can register on a dropzone object.
11
12 You can register an event handler like this:
13
14 dropzone.on("dragEnter", function() { });
15
16 */
17 this.prototype.events = [
18 "drop",
19 "dragstart",
20 "dragend",
21 "dragenter",
22 "dragover",
23 "dragleave",
24 "addedfile",
25 "addedfiles",
26 "removedfile",
27 "thumbnail",
28 "error",
29 "errormultiple",
30 "processing",
31 "processingmultiple",
32 "uploadprogress",
33 "totaluploadprogress",
34 "sending",
35 "sendingmultiple",
36 "success",
37 "successmultiple",
38 "canceled",
39 "canceledmultiple",
40 "complete",
41 "completemultiple",
42 "reset",
43 "maxfilesexceeded",
44 "maxfilesreached",
45 "queuecomplete",
46 ];
47
48 this.prototype._thumbnailQueue = [];
49 this.prototype._processingThumbnail = false;
50 }
51
52 // global utility
53 static extend(target, ...objects) {
54 for (let object of objects) {
55 for (let key in object) {
56 let val = object[key];
57 target[key] = val;
58 }
59 }
60 return target;
61 }
62
63 constructor(el, options) {
64 super();
65 let fallback, left;
66 this.element = el;
67 // For backwards compatibility since the version was in the prototype previously
68 this.version = Dropzone.version;
69
70 this.clickableElements = [];
71 this.listeners = [];
72 this.files = []; // All files
73
74 if (typeof this.element === "string") {
75 this.element = document.querySelector(this.element);
76 }
77
78 // Not checking if instance of HTMLElement or Element since IE9 is extremely weird.
79 if (!this.element || this.element.nodeType == null) {
80 throw new Error("Invalid dropzone element.");
81 }
82
83 if (this.element.dropzone) {
84 throw new Error("Dropzone already attached.");
85 }
86
87 // Now add this dropzone to the instances.
88 Dropzone.instances.push(this);
89
90 // Put the dropzone inside the element itself.
91 this.element.dropzone = this;
92
93 let elementOptions =
94 (left = Dropzone.optionsForElement(this.element)) != null ? left : {};
95
96 this.options = Dropzone.extend(
97 {},
98 defaultOptions,
99 elementOptions,
100 options != null ? options : {}
101 );
102
103 this.options.previewTemplate = this.options.previewTemplate.replace(
104 /\n*/g,
105 ""
106 );
107
108 // If the browser failed, just call the fallback and leave
109 if (this.options.forceFallback || !Dropzone.isBrowserSupported()) {
110 return this.options.fallback.call(this);
111 }
112
113 // @options.url = @element.getAttribute "action" unless @options.url?
114 if (this.options.url == null) {
115 this.options.url = this.element.getAttribute("action");
116 }
117
118 if (!this.options.url) {
119 throw new Error("No URL provided.");
120 }
121
122 if (this.options.acceptedFiles && this.options.acceptedMimeTypes) {
123 throw new Error(
124 "You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."
125 );
126 }
127
128 if (this.options.uploadMultiple && this.options.chunking) {
129 throw new Error("You cannot set both: uploadMultiple and chunking.");
130 }
131
132 // Backwards compatibility
133 if (this.options.acceptedMimeTypes) {
134 this.options.acceptedFiles = this.options.acceptedMimeTypes;
135 delete this.options.acceptedMimeTypes;
136 }
137
138 // Backwards compatibility
139 if (this.options.renameFilename != null) {
140 this.options.renameFile = (file) =>
141 this.options.renameFilename.call(this, file.name, file);
142 }
143
144 if (typeof this.options.method === "string") {
145 this.options.method = this.options.method.toUpperCase();
146 }
147
148 if ((fallback = this.getExistingFallback()) && fallback.parentNode) {
149 // Remove the fallback
150 fallback.parentNode.removeChild(fallback);
151 }
152
153 // Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false
154 if (this.options.previewsContainer !== false) {
155 if (this.options.previewsContainer) {
156 this.previewsContainer = Dropzone.getElement(
157 this.options.previewsContainer,
158 "previewsContainer"
159 );
160 } else {
161 this.previewsContainer = this.element;
162 }
163 }
164
165 if (this.options.clickable) {
166 if (this.options.clickable === true) {
167 this.clickableElements = [this.element];
168 } else {
169 this.clickableElements = Dropzone.getElements(
170 this.options.clickable,
171 "clickable"
172 );
173 }
174 }
175
176 this.init();
177 }
178
179 // Returns all files that have been accepted
180 getAcceptedFiles() {
181 return this.files.filter((file) => file.accepted).map((file) => file);
182 }
183
184 // Returns all files that have been rejected
185 // Not sure when that's going to be useful, but added for completeness.
186 getRejectedFiles() {
187 return this.files.filter((file) => !file.accepted).map((file) => file);
188 }
189
190 getFilesWithStatus(status) {
191 return this.files
192 .filter((file) => file.status === status)
193 .map((file) => file);
194 }
195
196 // Returns all files that are in the queue
197 getQueuedFiles() {
198 return this.getFilesWithStatus(Dropzone.QUEUED);
199 }
200
201 getUploadingFiles() {
202 return this.getFilesWithStatus(Dropzone.UPLOADING);
203 }
204
205 getAddedFiles() {
206 return this.getFilesWithStatus(Dropzone.ADDED);
207 }
208
209 // Files that are either queued or uploading
210 getActiveFiles() {
211 return this.files
212 .filter(
213 (file) =>
214 file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED
215 )
216 .map((file) => file);
217 }
218
219 // The function that gets called when Dropzone is initialized. You
220 // can (and should) setup event listeners inside this function.
221 init() {
222 // In case it isn't set already
223 if (this.element.tagName === "form") {
224 this.element.setAttribute("enctype", "multipart/form-data");
225 }
226
227 if (
228 this.element.classList.contains("dropzone") &&
229 !this.element.querySelector(".dz-message")
230 ) {
231 this.element.appendChild(
232 Dropzone.createElement(
233 `<div class="dz-default dz-message"><button class="dz-button" type="button">${this.options.dictDefaultMessage}</button></div>`
234 )
235 );
236 }
237
238 if (this.clickableElements.length) {
239 let setupHiddenFileInput = () => {
240 if (this.hiddenFileInput) {
241 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
242 }
243 this.hiddenFileInput = document.createElement("input");
244 this.hiddenFileInput.setAttribute("type", "file");
245 if (this.options.maxFiles === null || this.options.maxFiles > 1) {
246 this.hiddenFileInput.setAttribute("multiple", "multiple");
247 }
248 this.hiddenFileInput.className = "dz-hidden-input";
249
250 if (this.options.acceptedFiles !== null) {
251 this.hiddenFileInput.setAttribute(
252 "accept",
253 this.options.acceptedFiles
254 );
255 }
256 if (this.options.capture !== null) {
257 this.hiddenFileInput.setAttribute("capture", this.options.capture);
258 }
259
260 // Making sure that no one can "tab" into this field.
261 this.hiddenFileInput.setAttribute("tabindex", "-1");
262
263 // Not setting `display="none"` because some browsers don't accept clicks
264 // on elements that aren't displayed.
265 this.hiddenFileInput.style.visibility = "hidden";
266 this.hiddenFileInput.style.position = "absolute";
267 this.hiddenFileInput.style.top = "0";
268 this.hiddenFileInput.style.left = "0";
269 this.hiddenFileInput.style.height = "0";
270 this.hiddenFileInput.style.width = "0";
271 Dropzone.getElement(
272 this.options.hiddenInputContainer,
273 "hiddenInputContainer"
274 ).appendChild(this.hiddenFileInput);
275 this.hiddenFileInput.addEventListener("change", () => {
276 let { files } = this.hiddenFileInput;
277 if (files.length) {
278 for (let file of files) {
279 this.addFile(file);
280 }
281 }
282 this.emit("addedfiles", files);
283 setupHiddenFileInput();
284 });
285 };
286 setupHiddenFileInput();
287 }
288
289 this.URL = window.URL !== null ? window.URL : window.webkitURL;
290
291 // Setup all event listeners on the Dropzone object itself.
292 // They're not in @setupEventListeners() because they shouldn't be removed
293 // again when the dropzone gets disabled.
294 for (let eventName of this.events) {
295 this.on(eventName, this.options[eventName]);
296 }
297
298 this.on("uploadprogress", () => this.updateTotalUploadProgress());
299
300 this.on("removedfile", () => this.updateTotalUploadProgress());
301
302 this.on("canceled", (file) => this.emit("complete", file));
303
304 // Emit a `queuecomplete` event if all files finished uploading.
305 this.on("complete", (file) => {
306 if (
307 this.getAddedFiles().length === 0 &&
308 this.getUploadingFiles().length === 0 &&
309 this.getQueuedFiles().length === 0
310 ) {
311 // This needs to be deferred so that `queuecomplete` really triggers after `complete`
312 return setTimeout(() => this.emit("queuecomplete"), 0);
313 }
314 });
315
316 const containsFiles = function (e) {
317 if (e.dataTransfer.types) {
318 // Because e.dataTransfer.types is an Object in
319 // IE, we need to iterate like this instead of
320 // using e.dataTransfer.types.some()
321 for (var i = 0; i < e.dataTransfer.types.length; i++) {
322 if (e.dataTransfer.types[i] === "Files") return true;
323 }
324 }
325 return false;
326 };
327
328 let noPropagation = function (e) {
329 // If there are no files, we don't want to stop
330 // propagation so we don't interfere with other
331 // drag and drop behaviour.
332 if (!containsFiles(e)) return;
333 e.stopPropagation();
334 if (e.preventDefault) {
335 return e.preventDefault();
336 } else {
337 return (e.returnValue = false);
338 }
339 };
340
341 // Create the listeners
342 this.listeners = [
343 {
344 element: this.element,
345 events: {
346 dragstart: (e) => {
347 return this.emit("dragstart", e);
348 },
349 dragenter: (e) => {
350 noPropagation(e);
351 return this.emit("dragenter", e);
352 },
353 dragover: (e) => {
354 // Makes it possible to drag files from chrome's download bar
355 // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar
356 // Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception)
357 let efct;
358 try {
359 efct = e.dataTransfer.effectAllowed;
360 } catch (error) {}
361 e.dataTransfer.dropEffect =
362 "move" === efct || "linkMove" === efct ? "move" : "copy";
363
364 noPropagation(e);
365 return this.emit("dragover", e);
366 },
367 dragleave: (e) => {
368 return this.emit("dragleave", e);
369 },
370 drop: (e) => {
371 noPropagation(e);
372 return this.drop(e);
373 },
374 dragend: (e) => {
375 return this.emit("dragend", e);
376 },
377 },
378
379 // This is disabled right now, because the browsers don't implement it properly.
380 // "paste": (e) =>
381 // noPropagation e
382 // @paste e
383 },
384 ];
385
386 this.clickableElements.forEach((clickableElement) => {
387 return this.listeners.push({
388 element: clickableElement,
389 events: {
390 click: (evt) => {
391 // Only the actual dropzone or the message element should trigger file selection
392 if (
393 clickableElement !== this.element ||
394 evt.target === this.element ||
395 Dropzone.elementInside(
396 evt.target,
397 this.element.querySelector(".dz-message")
398 )
399 ) {
400 this.hiddenFileInput.click(); // Forward the click
401 }
402 return true;
403 },
404 },
405 });
406 });
407
408 this.enable();
409
410 return this.options.init.call(this);
411 }
412
413 // Not fully tested yet
414 destroy() {
415 this.disable();
416 this.removeAllFiles(true);
417 if (
418 this.hiddenFileInput != null ? this.hiddenFileInput.parentNode : undefined
419 ) {
420 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
421 this.hiddenFileInput = null;
422 }
423 delete this.element.dropzone;
424 return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
425 }
426
427 updateTotalUploadProgress() {
428 let totalUploadProgress;
429 let totalBytesSent = 0;
430 let totalBytes = 0;
431
432 let activeFiles = this.getActiveFiles();
433
434 if (activeFiles.length) {
435 for (let file of this.getActiveFiles()) {
436 totalBytesSent += file.upload.bytesSent;
437 totalBytes += file.upload.total;
438 }
439 totalUploadProgress = (100 * totalBytesSent) / totalBytes;
440 } else {
441 totalUploadProgress = 100;
442 }
443
444 return this.emit(
445 "totaluploadprogress",
446 totalUploadProgress,
447 totalBytes,
448 totalBytesSent
449 );
450 }
451
452 // @options.paramName can be a function taking one parameter rather than a string.
453 // A parameter name for a file is obtained simply by calling this with an index number.
454 _getParamName(n) {
455 if (typeof this.options.paramName === "function") {
456 return this.options.paramName(n);
457 } else {
458 return `${this.options.paramName}${
459 this.options.uploadMultiple ? `[${n}]` : ""
460 }`;
461 }
462 }
463
464 // If @options.renameFile is a function,
465 // the function will be used to rename the file.name before appending it to the formData
466 _renameFile(file) {
467 if (typeof this.options.renameFile !== "function") {
468 return file.name;
469 }
470 return this.options.renameFile(file);
471 }
472
473 // Returns a form that can be used as fallback if the browser does not support DragnDrop
474 //
475 // If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided.
476 // This code has to pass in IE7 :(
477 getFallbackForm() {
478 let existingFallback, form;
479 if ((existingFallback = this.getExistingFallback())) {
480 return existingFallback;
481 }
482
483 let fieldsString = '<div class="dz-fallback">';
484 if (this.options.dictFallbackText) {
485 fieldsString += `<p>${this.options.dictFallbackText}</p>`;
486 }
487 fieldsString += `<input type="file" name="${this._getParamName(0)}" ${
488 this.options.uploadMultiple ? 'multiple="multiple"' : undefined
489 } /><input type="submit" value="Upload!"></div>`;
490
491 let fields = Dropzone.createElement(fieldsString);
492 if (this.element.tagName !== "FORM") {
493 form = Dropzone.createElement(
494 `<form action="${this.options.url}" enctype="multipart/form-data" method="${this.options.method}"></form>`
495 );
496 form.appendChild(fields);
497 } else {
498 // Make sure that the enctype and method attributes are set properly
499 this.element.setAttribute("enctype", "multipart/form-data");
500 this.element.setAttribute("method", this.options.method);
501 }
502 return form != null ? form : fields;
503 }
504
505 // Returns the fallback elements if they exist already
506 //
507 // This code has to pass in IE7 :(
508 getExistingFallback() {
509 let getFallback = function (elements) {
510 for (let el of elements) {
511 if (/(^| )fallback($| )/.test(el.className)) {
512 return el;
513 }
514 }
515 };
516
517 for (let tagName of ["div", "form"]) {
518 var fallback;
519 if (
520 (fallback = getFallback(this.element.getElementsByTagName(tagName)))
521 ) {
522 return fallback;
523 }
524 }
525 }
526
527 // Activates all listeners stored in @listeners
528 setupEventListeners() {
529 return this.listeners.map((elementListeners) =>
530 (() => {
531 let result = [];
532 for (let event in elementListeners.events) {
533 let listener = elementListeners.events[event];
534 result.push(
535 elementListeners.element.addEventListener(event, listener, false)
536 );
537 }
538 return result;
539 })()
540 );
541 }
542
543 // Deactivates all listeners stored in @listeners
544 removeEventListeners() {
545 return this.listeners.map((elementListeners) =>
546 (() => {
547 let result = [];
548 for (let event in elementListeners.events) {
549 let listener = elementListeners.events[event];
550 result.push(
551 elementListeners.element.removeEventListener(event, listener, false)
552 );
553 }
554 return result;
555 })()
556 );
557 }
558
559 // Removes all event listeners and cancels all files in the queue or being processed.
560 disable() {
561 this.clickableElements.forEach((element) =>
562 element.classList.remove("dz-clickable")
563 );
564 this.removeEventListeners();
565 this.disabled = true;
566
567 return this.files.map((file) => this.cancelUpload(file));
568 }
569
570 enable() {
571 delete this.disabled;
572 this.clickableElements.forEach((element) =>
573 element.classList.add("dz-clickable")
574 );
575 return this.setupEventListeners();
576 }
577
578 // Returns a nicely formatted filesize
579 filesize(size) {
580 let selectedSize = 0;
581 let selectedUnit = "b";
582
583 if (size > 0) {
584 let units = ["tb", "gb", "mb", "kb", "b"];
585
586 for (let i = 0; i < units.length; i++) {
587 let unit = units[i];
588 let cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10;
589
590 if (size >= cutoff) {
591 selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i);
592 selectedUnit = unit;
593 break;
594 }
595 }
596
597 selectedSize = Math.round(10 * selectedSize) / 10; // Cutting of digits
598 }
599
600 return `<strong>${selectedSize}</strong> ${this.options.dictFileSizeUnits[selectedUnit]}`;
601 }
602
603 // Adds or removes the `dz-max-files-reached` class from the form.
604 _updateMaxFilesReachedClass() {
605 if (
606 this.options.maxFiles != null &&
607 this.getAcceptedFiles().length >= this.options.maxFiles
608 ) {
609 if (this.getAcceptedFiles().length === this.options.maxFiles) {
610 this.emit("maxfilesreached", this.files);
611 }
612 return this.element.classList.add("dz-max-files-reached");
613 } else {
614 return this.element.classList.remove("dz-max-files-reached");
615 }
616 }
617
618 drop(e) {
619 if (!e.dataTransfer) {
620 return;
621 }
622 this.emit("drop", e);
623
624 // Convert the FileList to an Array
625 // This is necessary for IE11
626 let files = [];
627 for (let i = 0; i < e.dataTransfer.files.length; i++) {
628 files[i] = e.dataTransfer.files[i];
629 }
630
631 // Even if it's a folder, files.length will contain the folders.
632 if (files.length) {
633 let { items } = e.dataTransfer;
634 if (items && items.length && items[0].webkitGetAsEntry != null) {
635 // The browser supports dropping of folders, so handle items instead of files
636 this._addFilesFromItems(items);
637 } else {
638 this.handleFiles(files);
639 }
640 }
641
642 this.emit("addedfiles", files);
643 }
644
645 paste(e) {
646 if (
647 __guard__(e != null ? e.clipboardData : undefined, (x) => x.items) == null
648 ) {
649 return;
650 }
651
652 this.emit("paste", e);
653 let { items } = e.clipboardData;
654
655 if (items.length) {
656 return this._addFilesFromItems(items);
657 }
658 }
659
660 handleFiles(files) {
661 for (let file of files) {
662 this.addFile(file);
663 }
664 }
665
666 // When a folder is dropped (or files are pasted), items must be handled
667 // instead of files.
668 _addFilesFromItems(items) {
669 return (() => {
670 let result = [];
671 for (let item of items) {
672 var entry;
673 if (
674 item.webkitGetAsEntry != null &&
675 (entry = item.webkitGetAsEntry())
676 ) {
677 if (entry.isFile) {
678 result.push(this.addFile(item.getAsFile()));
679 } else if (entry.isDirectory) {
680 // Append all files from that directory to files
681 result.push(this._addFilesFromDirectory(entry, entry.name));
682 } else {
683 result.push(undefined);
684 }
685 } else if (item.getAsFile != null) {
686 if (item.kind == null || item.kind === "file") {
687 result.push(this.addFile(item.getAsFile()));
688 } else {
689 result.push(undefined);
690 }
691 } else {
692 result.push(undefined);
693 }
694 }
695 return result;
696 })();
697 }
698
699 // Goes through the directory, and adds each file it finds recursively
700 _addFilesFromDirectory(directory, path) {
701 let dirReader = directory.createReader();
702
703 let errorHandler = (error) =>
704 __guardMethod__(console, "log", (o) => o.log(error));
705
706 var readEntries = () => {
707 return dirReader.readEntries((entries) => {
708 if (entries.length > 0) {
709 for (let entry of entries) {
710 if (entry.isFile) {
711 entry.file((file) => {
712 if (
713 this.options.ignoreHiddenFiles &&
714 file.name.substring(0, 1) === "."
715 ) {
716 return;
717 }
718 file.fullPath = `${path}/${file.name}`;
719 return this.addFile(file);
720 });
721 } else if (entry.isDirectory) {
722 this._addFilesFromDirectory(entry, `${path}/${entry.name}`);
723 }
724 }
725
726 // Recursively call readEntries() again, since browser only handle
727 // the first 100 entries.
728 // See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries
729 readEntries();
730 }
731 return null;
732 }, errorHandler);
733 };
734
735 return readEntries();
736 }
737
738 // If `done()` is called without argument the file is accepted
739 // If you call it with an error message, the file is rejected
740 // (This allows for asynchronous validation)
741 //
742 // This function checks the filesize, and if the file.type passes the
743 // `acceptedFiles` check.
744 accept(file, done) {
745 if (
746 this.options.maxFilesize &&
747 file.size > this.options.maxFilesize * 1024 * 1024
748 ) {
749 done(
750 this.options.dictFileTooBig
751 .replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100)
752 .replace("{{maxFilesize}}", this.options.maxFilesize)
753 );
754 } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) {
755 done(this.options.dictInvalidFileType);
756 } else if (
757 this.options.maxFiles != null &&
758 this.getAcceptedFiles().length >= this.options.maxFiles
759 ) {
760 done(
761 this.options.dictMaxFilesExceeded.replace(
762 "{{maxFiles}}",
763 this.options.maxFiles
764 )
765 );
766 this.emit("maxfilesexceeded", file);
767 } else {
768 this.options.accept.call(this, file, done);
769 }
770 }
771
772 addFile(file) {
773 file.upload = {
774 uuid: Dropzone.uuidv4(),
775 progress: 0,
776 // Setting the total upload size to file.size for the beginning
777 // It's actual different than the size to be transmitted.
778 total: file.size,
779 bytesSent: 0,
780 filename: this._renameFile(file),
781 // Not setting chunking information here, because the acutal data — and
782 // thus the chunks — might change if `options.transformFile` is set
783 // and does something to the data.
784 };
785 this.files.push(file);
786
787 file.status = Dropzone.ADDED;
788
789 this.emit("addedfile", file);
790
791 this._enqueueThumbnail(file);
792
793 this.accept(file, (error) => {
794 if (error) {
795 file.accepted = false;
796 this._errorProcessing([file], error); // Will set the file.status
797 } else {
798 file.accepted = true;
799 if (this.options.autoQueue) {
800 this.enqueueFile(file);
801 } // Will set .accepted = true
802 }
803 this._updateMaxFilesReachedClass();
804 });
805 }
806
807 // Wrapper for enqueueFile
808 enqueueFiles(files) {
809 for (let file of files) {
810 this.enqueueFile(file);
811 }
812 return null;
813 }
814
815 enqueueFile(file) {
816 if (file.status === Dropzone.ADDED && file.accepted === true) {
817 file.status = Dropzone.QUEUED;
818 if (this.options.autoProcessQueue) {
819 return setTimeout(() => this.processQueue(), 0); // Deferring the call
820 }
821 } else {
822 throw new Error(
823 "This file can't be queued because it has already been processed or was rejected."
824 );
825 }
826 }
827
828 _enqueueThumbnail(file) {
829 if (
830 this.options.createImageThumbnails &&
831 file.type.match(/image.*/) &&
832 file.size <= this.options.maxThumbnailFilesize * 1024 * 1024
833 ) {
834 this._thumbnailQueue.push(file);
835 return setTimeout(() => this._processThumbnailQueue(), 0); // Deferring the call
836 }
837 }
838
839 _processThumbnailQueue() {
840 if (this._processingThumbnail || this._thumbnailQueue.length === 0) {
841 return;
842 }
843
844 this._processingThumbnail = true;
845 let file = this._thumbnailQueue.shift();
846 return this.createThumbnail(
847 file,
848 this.options.thumbnailWidth,
849 this.options.thumbnailHeight,
850 this.options.thumbnailMethod,
851 true,
852 (dataUrl) => {
853 this.emit("thumbnail", file, dataUrl);
854 this._processingThumbnail = false;
855 return this._processThumbnailQueue();
856 }
857 );
858 }
859
860 // Can be called by the user to remove a file
861 removeFile(file) {
862 if (file.status === Dropzone.UPLOADING) {
863 this.cancelUpload(file);
864 }
865 this.files = without(this.files, file);
866
867 this.emit("removedfile", file);
868 if (this.files.length === 0) {
869 return this.emit("reset");
870 }
871 }
872
873 // Removes all files that aren't currently processed from the list
874 removeAllFiles(cancelIfNecessary) {
875 // Create a copy of files since removeFile() changes the @files array.
876 if (cancelIfNecessary == null) {
877 cancelIfNecessary = false;
878 }
879 for (let file of this.files.slice()) {
880 if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) {
881 this.removeFile(file);
882 }
883 }
884 return null;
885 }
886
887 // Resizes an image before it gets sent to the server. This function is the default behavior of
888 // `options.transformFile` if `resizeWidth` or `resizeHeight` are set. The callback is invoked with
889 // the resized blob.
890 resizeImage(file, width, height, resizeMethod, callback) {
891 return this.createThumbnail(
892 file,
893 width,
894 height,
895 resizeMethod,
896 true,
897 (dataUrl, canvas) => {
898 if (canvas == null) {
899 // The image has not been resized
900 return callback(file);
901 } else {
902 let { resizeMimeType } = this.options;
903 if (resizeMimeType == null) {
904 resizeMimeType = file.type;
905 }
906 let resizedDataURL = canvas.toDataURL(
907 resizeMimeType,
908 this.options.resizeQuality
909 );
910 if (
911 resizeMimeType === "image/jpeg" ||
912 resizeMimeType === "image/jpg"
913 ) {
914 // Now add the original EXIF information
915 resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL);
916 }
917 return callback(Dropzone.dataURItoBlob(resizedDataURL));
918 }
919 }
920 );
921 }
922
923 createThumbnail(file, width, height, resizeMethod, fixOrientation, callback) {
924 let fileReader = new FileReader();
925
926 fileReader.onload = () => {
927 file.dataURL = fileReader.result;
928
929 // Don't bother creating a thumbnail for SVG images since they're vector
930 if (file.type === "image/svg+xml") {
931 if (callback != null) {
932 callback(fileReader.result);
933 }
934 return;
935 }
936
937 this.createThumbnailFromUrl(
938 file,
939 width,
940 height,
941 resizeMethod,
942 fixOrientation,
943 callback
944 );
945 };
946
947 fileReader.readAsDataURL(file);
948 }
949
950 // `mockFile` needs to have these attributes:
951 //
952 // { name: 'name', size: 12345, imageUrl: '' }
953 //
954 // `callback` will be invoked when the image has been downloaded and displayed.
955 // `crossOrigin` will be added to the `img` tag when accessing the file.
956 displayExistingFile(
957 mockFile,
958 imageUrl,
959 callback,
960 crossOrigin,
961 resizeThumbnail = true
962 ) {
963 this.emit("addedfile", mockFile);
964 this.emit("complete", mockFile);
965
966 if (!resizeThumbnail) {
967 this.emit("thumbnail", mockFile, imageUrl);
968 if (callback) callback();
969 } else {
970 let onDone = (thumbnail) => {
971 this.emit("thumbnail", mockFile, thumbnail);
972 if (callback) callback();
973 };
974 mockFile.dataURL = imageUrl;
975
976 this.createThumbnailFromUrl(
977 mockFile,
978 this.options.thumbnailWidth,
979 this.options.thumbnailHeight,
980 this.options.thumbnailMethod,
981 this.options.fixOrientation,
982 onDone,
983 crossOrigin
984 );
985 }
986 }
987
988 createThumbnailFromUrl(
989 file,
990 width,
991 height,
992 resizeMethod,
993 fixOrientation,
994 callback,
995 crossOrigin
996 ) {
997 // Not using `new Image` here because of a bug in latest Chrome versions.
998 // See https://github.com/enyo/dropzone/pull/226
999 let img = document.createElement("img");
1000
1001 if (crossOrigin) {
1002 img.crossOrigin = crossOrigin;
1003 }
1004
1005 // fixOrientation is not needed anymore with browsers handling imageOrientation
1006 fixOrientation =
1007 getComputedStyle(document.body)["imageOrientation"] == "from-image"
1008 ? false
1009 : fixOrientation;
1010
1011 img.onload = () => {
1012 let loadExif = (callback) => callback(1);
1013 if (typeof EXIF !== "undefined" && EXIF !== null && fixOrientation) {
1014 loadExif = (callback) =>
1015 EXIF.getData(img, function () {
1016 return callback(EXIF.getTag(this, "Orientation"));
1017 });
1018 }
1019
1020 return loadExif((orientation) => {
1021 file.width = img.width;
1022 file.height = img.height;
1023
1024 let resizeInfo = this.options.resize.call(
1025 this,
1026 file,
1027 width,
1028 height,
1029 resizeMethod
1030 );
1031
1032 let canvas = document.createElement("canvas");
1033 let ctx = canvas.getContext("2d");
1034
1035 canvas.width = resizeInfo.trgWidth;
1036 canvas.height = resizeInfo.trgHeight;
1037
1038 if (orientation > 4) {
1039 canvas.width = resizeInfo.trgHeight;
1040 canvas.height = resizeInfo.trgWidth;
1041 }
1042
1043 switch (orientation) {
1044 case 2:
1045 // horizontal flip
1046 ctx.translate(canvas.width, 0);
1047 ctx.scale(-1, 1);
1048 break;
1049 case 3:
1050 // 180° rotate left
1051 ctx.translate(canvas.width, canvas.height);
1052 ctx.rotate(Math.PI);
1053 break;
1054 case 4:
1055 // vertical flip
1056 ctx.translate(0, canvas.height);
1057 ctx.scale(1, -1);
1058 break;
1059 case 5:
1060 // vertical flip + 90 rotate right
1061 ctx.rotate(0.5 * Math.PI);
1062 ctx.scale(1, -1);
1063 break;
1064 case 6:
1065 // 90° rotate right
1066 ctx.rotate(0.5 * Math.PI);
1067 ctx.translate(0, -canvas.width);
1068 break;
1069 case 7:
1070 // horizontal flip + 90 rotate right
1071 ctx.rotate(0.5 * Math.PI);
1072 ctx.translate(canvas.height, -canvas.width);
1073 ctx.scale(-1, 1);
1074 break;
1075 case 8:
1076 // 90° rotate left
1077 ctx.rotate(-0.5 * Math.PI);
1078 ctx.translate(-canvas.height, 0);
1079 break;
1080 }
1081
1082 // This is a bugfix for iOS' scaling bug.
1083 drawImageIOSFix(
1084 ctx,
1085 img,
1086 resizeInfo.srcX != null ? resizeInfo.srcX : 0,
1087 resizeInfo.srcY != null ? resizeInfo.srcY : 0,
1088 resizeInfo.srcWidth,
1089 resizeInfo.srcHeight,
1090 resizeInfo.trgX != null ? resizeInfo.trgX : 0,
1091 resizeInfo.trgY != null ? resizeInfo.trgY : 0,
1092 resizeInfo.trgWidth,
1093 resizeInfo.trgHeight
1094 );
1095
1096 let thumbnail = canvas.toDataURL("image/png");
1097
1098 if (callback != null) {
1099 return callback(thumbnail, canvas);
1100 }
1101 });
1102 };
1103
1104 if (callback != null) {
1105 img.onerror = callback;
1106 }
1107
1108 return (img.src = file.dataURL);
1109 }
1110
1111 // Goes through the queue and processes files if there aren't too many already.
1112 processQueue() {
1113 let { parallelUploads } = this.options;
1114 let processingLength = this.getUploadingFiles().length;
1115 let i = processingLength;
1116
1117 // There are already at least as many files uploading than should be
1118 if (processingLength >= parallelUploads) {
1119 return;
1120 }
1121
1122 let queuedFiles = this.getQueuedFiles();
1123
1124 if (!(queuedFiles.length > 0)) {
1125 return;
1126 }
1127
1128 if (this.options.uploadMultiple) {
1129 // The files should be uploaded in one request
1130 return this.processFiles(
1131 queuedFiles.slice(0, parallelUploads - processingLength)
1132 );
1133 } else {
1134 while (i < parallelUploads) {
1135 if (!queuedFiles.length) {
1136 return;
1137 } // Nothing left to process
1138 this.processFile(queuedFiles.shift());
1139 i++;
1140 }
1141 }
1142 }
1143
1144 // Wrapper for `processFiles`
1145 processFile(file) {
1146 return this.processFiles([file]);
1147 }
1148
1149 // Loads the file, then calls finishedLoading()
1150 processFiles(files) {
1151 for (let file of files) {
1152 file.processing = true; // Backwards compatibility
1153 file.status = Dropzone.UPLOADING;
1154
1155 this.emit("processing", file);
1156 }
1157
1158 if (this.options.uploadMultiple) {
1159 this.emit("processingmultiple", files);
1160 }
1161
1162 return this.uploadFiles(files);
1163 }
1164
1165 _getFilesWithXhr(xhr) {
1166 let files;
1167 return (files = this.files
1168 .filter((file) => file.xhr === xhr)
1169 .map((file) => file));
1170 }
1171
1172 // Cancels the file upload and sets the status to CANCELED
1173 // **if** the file is actually being uploaded.
1174 // If it's still in the queue, the file is being removed from it and the status
1175 // set to CANCELED.
1176 cancelUpload(file) {
1177 if (file.status === Dropzone.UPLOADING) {
1178 let groupedFiles = this._getFilesWithXhr(file.xhr);
1179 for (let groupedFile of groupedFiles) {
1180 groupedFile.status = Dropzone.CANCELED;
1181 }
1182 if (typeof file.xhr !== "undefined") {
1183 file.xhr.abort();
1184 }
1185 for (let groupedFile of groupedFiles) {
1186 this.emit("canceled", groupedFile);
1187 }
1188 if (this.options.uploadMultiple) {
1189 this.emit("canceledmultiple", groupedFiles);
1190 }
1191 } else if (
1192 file.status === Dropzone.ADDED ||
1193 file.status === Dropzone.QUEUED
1194 ) {
1195 file.status = Dropzone.CANCELED;
1196 this.emit("canceled", file);
1197 if (this.options.uploadMultiple) {
1198 this.emit("canceledmultiple", [file]);
1199 }
1200 }
1201
1202 if (this.options.autoProcessQueue) {
1203 return this.processQueue();
1204 }
1205 }
1206
1207 resolveOption(option, ...args) {
1208 if (typeof option === "function") {
1209 return option.apply(this, args);
1210 }
1211 return option;
1212 }
1213
1214 uploadFile(file) {
1215 return this.uploadFiles([file]);
1216 }
1217
1218 uploadFiles(files) {
1219 this._transformFiles(files, (transformedFiles) => {
1220 if (this.options.chunking) {
1221 // Chunking is not allowed to be used with `uploadMultiple` so we know
1222 // that there is only __one__file.
1223 let transformedFile = transformedFiles[0];
1224 files[0].upload.chunked =
1225 this.options.chunking &&
1226 (this.options.forceChunking ||
1227 transformedFile.size > this.options.chunkSize);
1228 files[0].upload.totalChunkCount = Math.ceil(
1229 transformedFile.size / this.options.chunkSize
1230 );
1231 }
1232
1233 if (files[0].upload.chunked) {
1234 // This file should be sent in chunks!
1235
1236 // If the chunking option is set, we **know** that there can only be **one** file, since
1237 // uploadMultiple is not allowed with this option.
1238 let file = files[0];
1239 let transformedFile = transformedFiles[0];
1240 let startedChunkCount = 0;
1241
1242 file.upload.chunks = [];
1243
1244 let handleNextChunk = () => {
1245 let chunkIndex = 0;
1246
1247 // Find the next item in file.upload.chunks that is not defined yet.
1248 while (file.upload.chunks[chunkIndex] !== undefined) {
1249 chunkIndex++;
1250 }
1251
1252 // This means, that all chunks have already been started.
1253 if (chunkIndex >= file.upload.totalChunkCount) return;
1254
1255 startedChunkCount++;
1256
1257 let start = chunkIndex * this.options.chunkSize;
1258 let end = Math.min(
1259 start + this.options.chunkSize,
1260 transformedFile.size
1261 );
1262
1263 let dataBlock = {
1264 name: this._getParamName(0),
1265 data: transformedFile.webkitSlice
1266 ? transformedFile.webkitSlice(start, end)
1267 : transformedFile.slice(start, end),
1268 filename: file.upload.filename,
1269 chunkIndex: chunkIndex,
1270 };
1271
1272 file.upload.chunks[chunkIndex] = {
1273 file: file,
1274 index: chunkIndex,
1275 dataBlock: dataBlock, // In case we want to retry.
1276 status: Dropzone.UPLOADING,
1277 progress: 0,
1278 retries: 0, // The number of times this block has been retried.
1279 };
1280
1281 this._uploadData(files, [dataBlock]);
1282 };
1283
1284 file.upload.finishedChunkUpload = (chunk, response) => {
1285 let allFinished = true;
1286 chunk.status = Dropzone.SUCCESS;
1287
1288 // Clear the data from the chunk
1289 chunk.dataBlock = null;
1290 // Leaving this reference to xhr intact here will cause memory leaks in some browsers
1291 chunk.xhr = null;
1292
1293 for (let i = 0; i < file.upload.totalChunkCount; i++) {
1294 if (file.upload.chunks[i] === undefined) {
1295 return handleNextChunk();
1296 }
1297 if (file.upload.chunks[i].status !== Dropzone.SUCCESS) {
1298 allFinished = false;
1299 }
1300 }
1301
1302 if (allFinished) {
1303 this.options.chunksUploaded(file, () => {
1304 this._finished(files, response, null);
1305 });
1306 }
1307 };
1308
1309 if (this.options.parallelChunkUploads) {
1310 for (let i = 0; i < file.upload.totalChunkCount; i++) {
1311 handleNextChunk();
1312 }
1313 } else {
1314 handleNextChunk();
1315 }
1316 } else {
1317 let dataBlocks = [];
1318 for (let i = 0; i < files.length; i++) {
1319 dataBlocks[i] = {
1320 name: this._getParamName(i),
1321 data: transformedFiles[i],
1322 filename: files[i].upload.filename,
1323 };
1324 }
1325 this._uploadData(files, dataBlocks);
1326 }
1327 });
1328 }
1329
1330 /// Returns the right chunk for given file and xhr
1331 _getChunk(file, xhr) {
1332 for (let i = 0; i < file.upload.totalChunkCount; i++) {
1333 if (
1334 file.upload.chunks[i] !== undefined &&
1335 file.upload.chunks[i].xhr === xhr
1336 ) {
1337 return file.upload.chunks[i];
1338 }
1339 }
1340 }
1341
1342 // This function actually uploads the file(s) to the server.
1343 // If dataBlocks contains the actual data to upload (meaning, that this could either be transformed
1344 // files, or individual chunks for chunked upload).
1345 _uploadData(files, dataBlocks) {
1346 let xhr = new XMLHttpRequest();
1347
1348 // Put the xhr object in the file objects to be able to reference it later.
1349 for (let file of files) {
1350 file.xhr = xhr;
1351 }
1352 if (files[0].upload.chunked) {
1353 // Put the xhr object in the right chunk object, so it can be associated later, and found with _getChunk
1354 files[0].upload.chunks[dataBlocks[0].chunkIndex].xhr = xhr;
1355 }
1356
1357 let method = this.resolveOption(this.options.method, files);
1358 let url = this.resolveOption(this.options.url, files);
1359 xhr.open(method, url, true);
1360
1361 // Setting the timeout after open because of IE11 issue: https://gitlab.com/meno/dropzone/issues/8
1362 let timeout = this.resolveOption(this.options.timeout, files);
1363 if (timeout) xhr.timeout = this.resolveOption(this.options.timeout, files);
1364
1365 // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
1366 xhr.withCredentials = !!this.options.withCredentials;
1367
1368 xhr.onload = (e) => {
1369 this._finishedUploading(files, xhr, e);
1370 };
1371
1372 xhr.ontimeout = () => {
1373 this._handleUploadError(
1374 files,
1375 xhr,
1376 `Request timedout after ${this.options.timeout / 1000} seconds`
1377 );
1378 };
1379
1380 xhr.onerror = () => {
1381 this._handleUploadError(files, xhr);
1382 };
1383
1384 // Some browsers do not have the .upload property
1385 let progressObj = xhr.upload != null ? xhr.upload : xhr;
1386 progressObj.onprogress = (e) =>
1387 this._updateFilesUploadProgress(files, xhr, e);
1388
1389 let headers = {
1390 Accept: "application/json",
1391 "Cache-Control": "no-cache",
1392 "X-Requested-With": "XMLHttpRequest",
1393 };
1394
1395 if (this.options.headers) {
1396 Dropzone.extend(headers, this.options.headers);
1397 }
1398
1399 for (let headerName in headers) {
1400 let headerValue = headers[headerName];
1401 if (headerValue) {
1402 xhr.setRequestHeader(headerName, headerValue);
1403 }
1404 }
1405
1406 let formData = new FormData();
1407
1408 // Adding all @options parameters
1409 if (this.options.params) {
1410 let additionalParams = this.options.params;
1411 if (typeof additionalParams === "function") {
1412 additionalParams = additionalParams.call(
1413 this,
1414 files,
1415 xhr,
1416 files[0].upload.chunked ? this._getChunk(files[0], xhr) : null
1417 );
1418 }
1419
1420 for (let key in additionalParams) {
1421 let value = additionalParams[key];
1422 if (Array.isArray(value)) {
1423 // The additional parameter contains an array,
1424 // so lets iterate over it to attach each value
1425 // individually.
1426 for (let i = 0; i < value.length; i++) {
1427 formData.append(key, value[i]);
1428 }
1429 } else {
1430 formData.append(key, value);
1431 }
1432 }
1433 }
1434
1435 // Let the user add additional data if necessary
1436 for (let file of files) {
1437 this.emit("sending", file, xhr, formData);
1438 }
1439 if (this.options.uploadMultiple) {
1440 this.emit("sendingmultiple", files, xhr, formData);
1441 }
1442
1443 this._addFormElementData(formData);
1444
1445 // Finally add the files
1446 // Has to be last because some servers (eg: S3) expect the file to be the last parameter
1447 for (let i = 0; i < dataBlocks.length; i++) {
1448 let dataBlock = dataBlocks[i];
1449 formData.append(dataBlock.name, dataBlock.data, dataBlock.filename);
1450 }
1451
1452 this.submitRequest(xhr, formData, files);
1453 }
1454
1455 // Transforms all files with this.options.transformFile and invokes done with the transformed files when done.
1456 _transformFiles(files, done) {
1457 let transformedFiles = [];
1458 // Clumsy way of handling asynchronous calls, until I get to add a proper Future library.
1459 let doneCounter = 0;
1460 for (let i = 0; i < files.length; i++) {
1461 this.options.transformFile.call(this, files[i], (transformedFile) => {
1462 transformedFiles[i] = transformedFile;
1463 if (++doneCounter === files.length) {
1464 done(transformedFiles);
1465 }
1466 });
1467 }
1468 }
1469
1470 // Takes care of adding other input elements of the form to the AJAX request
1471 _addFormElementData(formData) {
1472 // Take care of other input elements
1473 if (this.element.tagName === "FORM") {
1474 for (let input of this.element.querySelectorAll(
1475 "input, textarea, select, button"
1476 )) {
1477 let inputName = input.getAttribute("name");
1478 let inputType = input.getAttribute("type");
1479 if (inputType) inputType = inputType.toLowerCase();
1480
1481 // If the input doesn't have a name, we can't use it.
1482 if (typeof inputName === "undefined" || inputName === null) continue;
1483
1484 if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
1485 // Possibly multiple values
1486 for (let option of input.options) {
1487 if (option.selected) {
1488 formData.append(inputName, option.value);
1489 }
1490 }
1491 } else if (
1492 !inputType ||
1493 (inputType !== "checkbox" && inputType !== "radio") ||
1494 input.checked
1495 ) {
1496 formData.append(inputName, input.value);
1497 }
1498 }
1499 }
1500 }
1501
1502 // Invoked when there is new progress information about given files.
1503 // If e is not provided, it is assumed that the upload is finished.
1504 _updateFilesUploadProgress(files, xhr, e) {
1505 if (!files[0].upload.chunked) {
1506 // Handle file uploads without chunking
1507 for (let file of files) {
1508 if (
1509 file.upload.total &&
1510 file.upload.bytesSent &&
1511 file.upload.bytesSent == file.upload.total
1512 ) {
1513 // If both, the `total` and `bytesSent` have already been set, and
1514 // they are equal (meaning progress is at 100%), we can skip this
1515 // file, since an upload progress shouldn't go down.
1516 continue;
1517 }
1518
1519 if (e) {
1520 file.upload.progress = (100 * e.loaded) / e.total;
1521 file.upload.total = e.total;
1522 file.upload.bytesSent = e.loaded;
1523 } else {
1524 // No event, so we're at 100%
1525 file.upload.progress = 100;
1526 file.upload.bytesSent = file.upload.total;
1527 }
1528
1529 this.emit(
1530 "uploadprogress",
1531 file,
1532 file.upload.progress,
1533 file.upload.bytesSent
1534 );
1535 }
1536 } else {
1537 // Handle chunked file uploads
1538
1539 // Chunked upload is not compatible with uploading multiple files in one
1540 // request, so we know there's only one file.
1541 let file = files[0];
1542
1543 // Since this is a chunked upload, we need to update the appropriate chunk
1544 // progress.
1545 let chunk = this._getChunk(file, xhr);
1546
1547 if (e) {
1548 chunk.progress = (100 * e.loaded) / e.total;
1549 chunk.total = e.total;
1550 chunk.bytesSent = e.loaded;
1551 } else {
1552 // No event, so we're at 100%
1553 chunk.progress = 100;
1554 chunk.bytesSent = chunk.total;
1555 }
1556
1557 // Now tally the *file* upload progress from its individual chunks
1558 file.upload.progress = 0;
1559 file.upload.total = 0;
1560 file.upload.bytesSent = 0;
1561 for (let i = 0; i < file.upload.totalChunkCount; i++) {
1562 if (
1563 file.upload.chunks[i] &&
1564 typeof file.upload.chunks[i].progress !== "undefined"
1565 ) {
1566 file.upload.progress += file.upload.chunks[i].progress;
1567 file.upload.total += file.upload.chunks[i].total;
1568 file.upload.bytesSent += file.upload.chunks[i].bytesSent;
1569 }
1570 }
1571 // Since the process is a percentage, we need to divide by the amount of
1572 // chunks we've used.
1573 file.upload.progress = file.upload.progress / file.upload.totalChunkCount;
1574
1575 this.emit(
1576 "uploadprogress",
1577 file,
1578 file.upload.progress,
1579 file.upload.bytesSent
1580 );
1581 }
1582 }
1583
1584 _finishedUploading(files, xhr, e) {
1585 let response;
1586
1587 if (files[0].status === Dropzone.CANCELED) {
1588 return;
1589 }
1590
1591 if (xhr.readyState !== 4) {
1592 return;
1593 }
1594
1595 if (xhr.responseType !== "arraybuffer" && xhr.responseType !== "blob") {
1596 response = xhr.responseText;
1597
1598 if (
1599 xhr.getResponseHeader("content-type") &&
1600 ~xhr.getResponseHeader("content-type").indexOf("application/json")
1601 ) {
1602 try {
1603 response = JSON.parse(response);
1604 } catch (error) {
1605 e = error;
1606 response = "Invalid JSON response from server.";
1607 }
1608 }
1609 }
1610
1611 this._updateFilesUploadProgress(files, xhr);
1612
1613 if (!(200 <= xhr.status && xhr.status < 300)) {
1614 this._handleUploadError(files, xhr, response);
1615 } else {
1616 if (files[0].upload.chunked) {
1617 files[0].upload.finishedChunkUpload(
1618 this._getChunk(files[0], xhr),
1619 response
1620 );
1621 } else {
1622 this._finished(files, response, e);
1623 }
1624 }
1625 }
1626
1627 _handleUploadError(files, xhr, response) {
1628 if (files[0].status === Dropzone.CANCELED) {
1629 return;
1630 }
1631
1632 if (files[0].upload.chunked && this.options.retryChunks) {
1633 let chunk = this._getChunk(files[0], xhr);
1634 if (chunk.retries++ < this.options.retryChunksLimit) {
1635 this._uploadData(files, [chunk.dataBlock]);
1636 return;
1637 } else {
1638 console.warn("Retried this chunk too often. Giving up.");
1639 }
1640 }
1641
1642 this._errorProcessing(
1643 files,
1644 response ||
1645 this.options.dictResponseError.replace("{{statusCode}}", xhr.status),
1646 xhr
1647 );
1648 }
1649
1650 submitRequest(xhr, formData, files) {
1651 if (xhr.readyState != 1) {
1652 console.warn(
1653 "Cannot send this request because the XMLHttpRequest.readyState is not OPENED."
1654 );
1655 return;
1656 }
1657 xhr.send(formData);
1658 }
1659
1660 // Called internally when processing is finished.
1661 // Individual callbacks have to be called in the appropriate sections.
1662 _finished(files, responseText, e) {
1663 for (let file of files) {
1664 file.status = Dropzone.SUCCESS;
1665 this.emit("success", file, responseText, e);
1666 this.emit("complete", file);
1667 }
1668 if (this.options.uploadMultiple) {
1669 this.emit("successmultiple", files, responseText, e);
1670 this.emit("completemultiple", files);
1671 }
1672
1673 if (this.options.autoProcessQueue) {
1674 return this.processQueue();
1675 }
1676 }
1677
1678 // Called internally when processing is finished.
1679 // Individual callbacks have to be called in the appropriate sections.
1680 _errorProcessing(files, message, xhr) {
1681 for (let file of files) {
1682 file.status = Dropzone.ERROR;
1683 this.emit("error", file, message, xhr);
1684 this.emit("complete", file);
1685 }
1686 if (this.options.uploadMultiple) {
1687 this.emit("errormultiple", files, message, xhr);
1688 this.emit("completemultiple", files);
1689 }
1690
1691 if (this.options.autoProcessQueue) {
1692 return this.processQueue();
1693 }
1694 }
1695
1696 static uuidv4() {
1697 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
1698 /[xy]/g,
1699 function (c) {
1700 let r = (Math.random() * 16) | 0,
1701 v = c === "x" ? r : (r & 0x3) | 0x8;
1702 return v.toString(16);
1703 }
1704 );
1705 }
1706 }
1707 Dropzone.initClass();
1708
1709 Dropzone.version = "dev";
1710
1711 // This is a map of options for your different dropzones. Add configurations
1712 // to this object for your different dropzone elemens.
1713 //
1714 // Example:
1715 //
1716 // Dropzone.options.myDropzoneElementId = { maxFilesize: 1 };
1717 //
1718 // To disable autoDiscover for a specific element, you can set `false` as an option:
1719 //
1720 // Dropzone.options.myDisabledElementId = false;
1721 //
1722 // And in html:
1723 //
1724 // <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form>
1725 Dropzone.options = {};
1726
1727 // Returns the options for an element or undefined if none available.
1728 Dropzone.optionsForElement = function (element) {
1729 // Get the `Dropzone.options.elementId` for this element if it exists
1730 if (element.getAttribute("id")) {
1731 return Dropzone.options[camelize(element.getAttribute("id"))];
1732 } else {
1733 return undefined;
1734 }
1735 };
1736
1737 // Holds a list of all dropzone instances
1738 Dropzone.instances = [];
1739
1740 // Returns the dropzone for given element if any
1741 Dropzone.forElement = function (element) {
1742 if (typeof element === "string") {
1743 element = document.querySelector(element);
1744 }
1745 if ((element != null ? element.dropzone : undefined) == null) {
1746 throw new Error(
1747 "No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."
1748 );
1749 }
1750 return element.dropzone;
1751 };
1752
1753 // Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements.
1754 Dropzone.autoDiscover = true;
1755
1756 // Looks for all .dropzone elements and creates a dropzone for them
1757 Dropzone.discover = function () {
1758 let dropzones;
1759 if (document.querySelectorAll) {
1760 dropzones = document.querySelectorAll(".dropzone");
1761 } else {
1762 dropzones = [];
1763 // IE :(
1764 let checkElements = (elements) =>
1765 (() => {
1766 let result = [];
1767 for (let el of elements) {
1768 if (/(^| )dropzone($| )/.test(el.className)) {
1769 result.push(dropzones.push(el));
1770 } else {
1771 result.push(undefined);
1772 }
1773 }
1774 return result;
1775 })();
1776 checkElements(document.getElementsByTagName("div"));
1777 checkElements(document.getElementsByTagName("form"));
1778 }
1779
1780 return (() => {
1781 let result = [];
1782 for (let dropzone of dropzones) {
1783 // Create a dropzone unless auto discover has been disabled for specific element
1784 if (Dropzone.optionsForElement(dropzone) !== false) {
1785 result.push(new Dropzone(dropzone));
1786 } else {
1787 result.push(undefined);
1788 }
1789 }
1790 return result;
1791 })();
1792 };
1793
1794 // Some browsers support drag and drog functionality, but not correctly.
1795 //
1796 // So I created a blocklist of userAgents. Yes, yes. Browser sniffing, I know.
1797 // But what to do when browsers *theoretically* support an API, but crash
1798 // when using it.
1799 //
1800 // This is a list of regular expressions tested against navigator.userAgent
1801 //
1802 // ** It should only be used on browser that *do* support the API, but
1803 // incorrectly **
1804 Dropzone.blockedBrowsers = [
1805 // The mac os and windows phone version of opera 12 seems to have a problem with the File drag'n'drop API.
1806 /opera.*(Macintosh|Windows Phone).*version\/12/i,
1807 ];
1808
1809 // Checks if the browser is supported
1810 Dropzone.isBrowserSupported = function () {
1811 let capableBrowser = true;
1812
1813 if (
1814 window.File &&
1815 window.FileReader &&
1816 window.FileList &&
1817 window.Blob &&
1818 window.FormData &&
1819 document.querySelector
1820 ) {
1821 if (!("classList" in document.createElement("a"))) {
1822 capableBrowser = false;
1823 } else {
1824 if (Dropzone.blacklistedBrowsers !== undefined) {
1825 // Since this has been renamed, this makes sure we don't break older
1826 // configuration.
1827 Dropzone.blockedBrowsers = Dropzone.blacklistedBrowsers;
1828 }
1829 // The browser supports the API, but may be blocked.
1830 for (let regex of Dropzone.blockedBrowsers) {
1831 if (regex.test(navigator.userAgent)) {
1832 capableBrowser = false;
1833 continue;
1834 }
1835 }
1836 }
1837 } else {
1838 capableBrowser = false;
1839 }
1840
1841 return capableBrowser;
1842 };
1843
1844 Dropzone.dataURItoBlob = function (dataURI) {
1845 // convert base64 to raw binary data held in a string
1846 // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
1847 let byteString = atob(dataURI.split(",")[1]);
1848
1849 // separate out the mime component
1850 let mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
1851
1852 // write the bytes of the string to an ArrayBuffer
1853 let ab = new ArrayBuffer(byteString.length);
1854 let ia = new Uint8Array(ab);
1855 for (
1856 let i = 0, end = byteString.length, asc = 0 <= end;
1857 asc ? i <= end : i >= end;
1858 asc ? i++ : i--
1859 ) {
1860 ia[i] = byteString.charCodeAt(i);
1861 }
1862
1863 // write the ArrayBuffer to a blob
1864 return new Blob([ab], { type: mimeString });
1865 };
1866
1867 // Returns an array without the rejected item
1868 const without = (list, rejectedItem) =>
1869 list.filter((item) => item !== rejectedItem).map((item) => item);
1870
1871 // abc-def_ghi -> abcDefGhi
1872 const camelize = (str) =>
1873 str.replace(/[\-_](\w)/g, (match) => match.charAt(1).toUpperCase());
1874
1875 // Creates an element from string
1876 Dropzone.createElement = function (string) {
1877 let div = document.createElement("div");
1878 div.innerHTML = string;
1879 return div.childNodes[0];
1880 };
1881
1882 // Tests if given element is inside (or simply is) the container
1883 Dropzone.elementInside = function (element, container) {
1884 if (element === container) {
1885 return true;
1886 } // Coffeescript doesn't support do/while loops
1887 while ((element = element.parentNode)) {
1888 if (element === container) {
1889 return true;
1890 }
1891 }
1892 return false;
1893 };
1894
1895 Dropzone.getElement = function (el, name) {
1896 let element;
1897 if (typeof el === "string") {
1898 element = document.querySelector(el);
1899 } else if (el.nodeType != null) {
1900 element = el;
1901 }
1902 if (element == null) {
1903 throw new Error(
1904 `Invalid \`${name}\` option provided. Please provide a CSS selector or a plain HTML element.`
1905 );
1906 }
1907 return element;
1908 };
1909
1910 Dropzone.getElements = function (els, name) {
1911 let el, elements;
1912 if (els instanceof Array) {
1913 elements = [];
1914 try {
1915 for (el of els) {
1916 elements.push(this.getElement(el, name));
1917 }
1918 } catch (e) {
1919 elements = null;
1920 }
1921 } else if (typeof els === "string") {
1922 elements = [];
1923 for (el of document.querySelectorAll(els)) {
1924 elements.push(el);
1925 }
1926 } else if (els.nodeType != null) {
1927 elements = [els];
1928 }
1929
1930 if (elements == null || !elements.length) {
1931 throw new Error(
1932 `Invalid \`${name}\` option provided. Please provide a CSS selector, a plain HTML element or a list of those.`
1933 );
1934 }
1935
1936 return elements;
1937 };
1938
1939 // Asks the user the question and calls accepted or rejected accordingly
1940 //
1941 // The default implementation just uses `window.confirm` and then calls the
1942 // appropriate callback.
1943 Dropzone.confirm = function (question, accepted, rejected) {
1944 if (window.confirm(question)) {
1945 return accepted();
1946 } else if (rejected != null) {
1947 return rejected();
1948 }
1949 };
1950
1951 // Validates the mime type like this:
1952 //
1953 // https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
1954 Dropzone.isValidFile = function (file, acceptedFiles) {
1955 if (!acceptedFiles) {
1956 return true;
1957 } // If there are no accepted mime types, it's OK
1958 acceptedFiles = acceptedFiles.split(",");
1959
1960 let mimeType = file.type;
1961 let baseMimeType = mimeType.replace(/\/.*$/, "");
1962
1963 for (let validType of acceptedFiles) {
1964 validType = validType.trim();
1965 if (validType.charAt(0) === ".") {
1966 if (
1967 file.name
1968 .toLowerCase()
1969 .indexOf(
1970 validType.toLowerCase(),
1971 file.name.length - validType.length
1972 ) !== -1
1973 ) {
1974 return true;
1975 }
1976 } else if (/\/\*$/.test(validType)) {
1977 // This is something like a image/* mime type
1978 if (baseMimeType === validType.replace(/\/.*$/, "")) {
1979 return true;
1980 }
1981 } else {
1982 if (mimeType === validType) {
1983 return true;
1984 }
1985 }
1986 }
1987
1988 return false;
1989 };
1990
1991 // Augment jQuery
1992 if (typeof jQuery !== "undefined" && jQuery !== null) {
1993 jQuery.fn.dropzone = function (options) {
1994 return this.each(function () {
1995 return new Dropzone(this, options);
1996 });
1997 };
1998 }
1999
2000 // Dropzone file status codes
2001 Dropzone.ADDED = "added";
2002
2003 Dropzone.QUEUED = "queued";
2004 // For backwards compatibility. Now, if a file is accepted, it's either queued
2005 // or uploading.
2006 Dropzone.ACCEPTED = Dropzone.QUEUED;
2007
2008 Dropzone.UPLOADING = "uploading";
2009 Dropzone.PROCESSING = Dropzone.UPLOADING; // alias
2010
2011 Dropzone.CANCELED = "canceled";
2012 Dropzone.ERROR = "error";
2013 Dropzone.SUCCESS = "success";
2014
2015 /*
2016
2017 Bugfix for iOS 6 and 7
2018 Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
2019 based on the work of https://github.com/stomita/ios-imagefile-megapixel
2020
2021 */
2022
2023 // Detecting vertical squash in loaded image.
2024 // Fixes a bug which squash image vertically while drawing into canvas for some images.
2025 // This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
2026 let detectVerticalSquash = function (img) {
2027 let iw = img.naturalWidth;
2028 let ih = img.naturalHeight;
2029 let canvas = document.createElement("canvas");
2030 canvas.width = 1;
2031 canvas.height = ih;
2032 let ctx = canvas.getContext("2d");
2033 ctx.drawImage(img, 0, 0);
2034 let { data } = ctx.getImageData(1, 0, 1, ih);
2035
2036 // search image edge pixel position in case it is squashed vertically.
2037 let sy = 0;
2038 let ey = ih;
2039 let py = ih;
2040 while (py > sy) {
2041 let alpha = data[(py - 1) * 4 + 3];
2042
2043 if (alpha === 0) {
2044 ey = py;
2045 } else {
2046 sy = py;
2047 }
2048
2049 py = (ey + sy) >> 1;
2050 }
2051 let ratio = py / ih;
2052
2053 if (ratio === 0) {
2054 return 1;
2055 } else {
2056 return ratio;
2057 }
2058 };
2059
2060 // A replacement for context.drawImage
2061 // (args are for source and destination).
2062 var drawImageIOSFix = function (ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
2063 let vertSquashRatio = detectVerticalSquash(img);
2064 return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
2065 };
2066
2067 // Based on MinifyJpeg
2068 // Source: http://www.perry.cz/files/ExifRestorer.js
2069 // http://elicon.blog57.fc2.com/blog-entry-206.html
2070 class ExifRestore {
2071 static initClass() {
2072 this.KEY_STR =
2073 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
2074 }
2075
2076 static encode64(input) {
2077 let output = "";
2078 let chr1 = undefined;
2079 let chr2 = undefined;
2080 let chr3 = "";
2081 let enc1 = undefined;
2082 let enc2 = undefined;
2083 let enc3 = undefined;
2084 let enc4 = "";
2085 let i = 0;
2086 while (true) {
2087 chr1 = input[i++];
2088 chr2 = input[i++];
2089 chr3 = input[i++];
2090 enc1 = chr1 >> 2;
2091 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2092 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
2093 enc4 = chr3 & 63;
2094 if (isNaN(chr2)) {
2095 enc3 = enc4 = 64;
2096 } else if (isNaN(chr3)) {
2097 enc4 = 64;
2098 }
2099 output =
2100 output +
2101 this.KEY_STR.charAt(enc1) +
2102 this.KEY_STR.charAt(enc2) +
2103 this.KEY_STR.charAt(enc3) +
2104 this.KEY_STR.charAt(enc4);
2105 chr1 = chr2 = chr3 = "";
2106 enc1 = enc2 = enc3 = enc4 = "";
2107 if (!(i < input.length)) {
2108 break;
2109 }
2110 }
2111 return output;
2112 }
2113
2114 static restore(origFileBase64, resizedFileBase64) {
2115 if (!origFileBase64.match("data:image/jpeg;base64,")) {
2116 return resizedFileBase64;
2117 }
2118 let rawImage = this.decode64(
2119 origFileBase64.replace("data:image/jpeg;base64,", "")
2120 );
2121 let segments = this.slice2Segments(rawImage);
2122 let image = this.exifManipulation(resizedFileBase64, segments);
2123 return `data:image/jpeg;base64,${this.encode64(image)}`;
2124 }
2125
2126 static exifManipulation(resizedFileBase64, segments) {
2127 let exifArray = this.getExifArray(segments);
2128 let newImageArray = this.insertExif(resizedFileBase64, exifArray);
2129 let aBuffer = new Uint8Array(newImageArray);
2130 return aBuffer;
2131 }
2132
2133 static getExifArray(segments) {
2134 let seg = undefined;
2135 let x = 0;
2136 while (x < segments.length) {
2137 seg = segments[x];
2138 if ((seg[0] === 255) & (seg[1] === 225)) {
2139 return seg;
2140 }
2141 x++;
2142 }
2143 return [];
2144 }
2145
2146 static insertExif(resizedFileBase64, exifArray) {
2147 let imageData = resizedFileBase64.replace("data:image/jpeg;base64,", "");
2148 let buf = this.decode64(imageData);
2149 let separatePoint = buf.indexOf(255, 3);
2150 let mae = buf.slice(0, separatePoint);
2151 let ato = buf.slice(separatePoint);
2152 let array = mae;
2153 array = array.concat(exifArray);
2154 array = array.concat(ato);
2155 return array;
2156 }
2157
2158 static slice2Segments(rawImageArray) {
2159 let head = 0;
2160 let segments = [];
2161 while (true) {
2162 var length;
2163 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 218)) {
2164 break;
2165 }
2166 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 216)) {
2167 head += 2;
2168 } else {
2169 length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3];
2170 let endPoint = head + length + 2;
2171 let seg = rawImageArray.slice(head, endPoint);
2172 segments.push(seg);
2173 head = endPoint;
2174 }
2175 if (head > rawImageArray.length) {
2176 break;
2177 }
2178 }
2179 return segments;
2180 }
2181
2182 static decode64(input) {
2183 let output = "";
2184 let chr1 = undefined;
2185 let chr2 = undefined;
2186 let chr3 = "";
2187 let enc1 = undefined;
2188 let enc2 = undefined;
2189 let enc3 = undefined;
2190 let enc4 = "";
2191 let i = 0;
2192 let buf = [];
2193 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
2194 let base64test = /[^A-Za-z0-9\+\/\=]/g;
2195 if (base64test.exec(input)) {
2196 console.warn(
2197 "There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."
2198 );
2199 }
2200 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
2201 while (true) {
2202 enc1 = this.KEY_STR.indexOf(input.charAt(i++));
2203 enc2 = this.KEY_STR.indexOf(input.charAt(i++));
2204 enc3 = this.KEY_STR.indexOf(input.charAt(i++));
2205 enc4 = this.KEY_STR.indexOf(input.charAt(i++));
2206 chr1 = (enc1 << 2) | (enc2 >> 4);
2207 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
2208 chr3 = ((enc3 & 3) << 6) | enc4;
2209 buf.push(chr1);
2210 if (enc3 !== 64) {
2211 buf.push(chr2);
2212 }
2213 if (enc4 !== 64) {
2214 buf.push(chr3);
2215 }
2216 chr1 = chr2 = chr3 = "";
2217 enc1 = enc2 = enc3 = enc4 = "";
2218 if (!(i < input.length)) {
2219 break;
2220 }
2221 }
2222 return buf;
2223 }
2224 }
2225 ExifRestore.initClass();
2226
2227 /*
2228 * contentloaded.js
2229 *
2230 * Author: Diego Perini (diego.perini at gmail.com)
2231 * Summary: cross-browser wrapper for DOMContentLoaded
2232 * Updated: 20101020
2233 * License: MIT
2234 * Version: 1.2
2235 *
2236 * URL:
2237 * http://javascript.nwbox.com/ContentLoaded/
2238 * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
2239 */
2240
2241 // @win window reference
2242 // @fn function reference
2243 let contentLoaded = function (win, fn) {
2244 let done = false;
2245 let top = true;
2246 let doc = win.document;
2247 let root = doc.documentElement;
2248 let add = doc.addEventListener ? "addEventListener" : "attachEvent";
2249 let rem = doc.addEventListener ? "removeEventListener" : "detachEvent";
2250 let pre = doc.addEventListener ? "" : "on";
2251 var init = function (e) {
2252 if (e.type === "readystatechange" && doc.readyState !== "complete") {
2253 return;
2254 }
2255 (e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
2256 if (!done && (done = true)) {
2257 return fn.call(win, e.type || e);
2258 }
2259 };
2260
2261 var poll = function () {
2262 try {
2263 root.doScroll("left");
2264 } catch (e) {
2265 setTimeout(poll, 50);
2266 return;
2267 }
2268 return init("poll");
2269 };
2270
2271 if (doc.readyState !== "complete") {
2272 if (doc.createEventObject && root.doScroll) {
2273 try {
2274 top = !win.frameElement;
2275 } catch (error) {}
2276 if (top) {
2277 poll();
2278 }
2279 }
2280 doc[add](pre + "DOMContentLoaded", init, false);
2281 doc[add](pre + "readystatechange", init, false);
2282 return win[add](pre + "load", init, false);
2283 }
2284 };
2285
2286 // As a single function to be able to write tests.
2287 Dropzone._autoDiscoverFunction = function () {
2288 if (Dropzone.autoDiscover) {
2289 return Dropzone.discover();
2290 }
2291 };
2292 contentLoaded(window, Dropzone._autoDiscoverFunction);
2293
2294 function __guard__(value, transform) {
2295 return typeof value !== "undefined" && value !== null
2296 ? transform(value)
2297 : undefined;
2298 }
2299 function __guardMethod__(obj, methodName, transform) {
2300 if (
2301 typeof obj !== "undefined" &&
2302 obj !== null &&
2303 typeof obj[methodName] === "function"
2304 ) {
2305 return transform(obj, methodName);
2306 } else {
2307 return undefined;
2308 }
2309 }
2310
2311 export { Dropzone };
2312