1/* 2 * 3 * More info at [www.dropzonejs.com](http://www.dropzonejs.com) 4 * 5 * (c) 2012, Matias Meno 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a copy 8 * of this software and associated documentation files (the "Software"), to deal 9 * in the Software without restriction, including without limitation the rights 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 * copies of the Software, and to permit persons to whom the Software is 12 * furnished to do so, subject to the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included in 15 * all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 * THE SOFTWARE. 24 * 25 */ 26 27 28// The Emitter class provides the ability to call `.on()` on Dropzone to listen 29// to events. 30// It is strongly based on component's emitter class, and I removed the 31// functionality because of the dependency hell with different frameworks. 32class Emitter { 33 // Add an event listener for given event 34 on(event, fn) { 35 this._callbacks = this._callbacks || {}; 36 // Create namespace for this event 37 if (!this._callbacks[event]) { 38 this._callbacks[event] = []; 39 } 40 this._callbacks[event].push(fn); 41 return this; 42 } 43 44 45 emit(event, ...args) { 46 this._callbacks = this._callbacks || {}; 47 let callbacks = this._callbacks[event]; 48 49 if (callbacks) { 50 for (let callback of callbacks) { 51 callback.apply(this, args); 52 } 53 } 54 55 return this; 56 } 57 58 // Remove event listener for given event. If fn is not provided, all event 59 // listeners for that event will be removed. If neither is provided, all 60 // event listeners will be removed. 61 off(event, fn) { 62 if (!this._callbacks || (arguments.length === 0)) { 63 this._callbacks = {}; 64 return this; 65 } 66 67 // specific event 68 let callbacks = this._callbacks[event]; 69 if (!callbacks) { 70 return this; 71 } 72 73 // remove all handlers 74 if (arguments.length === 1) { 75 delete this._callbacks[event]; 76 return this; 77 } 78 79 // remove specific handler 80 for (let i = 0; i < callbacks.length; i++) { 81 let callback = callbacks[i]; 82 if (callback === fn) { 83 callbacks.splice(i, 1); 84 break; 85 } 86 } 87 88 return this; 89 } 90} 91 92class Dropzone extends Emitter { 93 static initClass() { 94 95 // Exposing the emitter class, mainly for tests 96 this.prototype.Emitter = Emitter; 97 98 /* 99 This is a list of all available events you can register on a dropzone object. 100 101 You can register an event handler like this: 102 103 dropzone.on("dragEnter", function() { }); 104 105 */ 106 this.prototype.events = [ 107 "drop", 108 "dragstart", 109 "dragend", 110 "dragenter", 111 "dragover", 112 "dragleave", 113 "addedfile", 114 "addedfiles", 115 "removedfile", 116 "thumbnail", 117 "error", 118 "errormultiple", 119 "processing", 120 "processingmultiple", 121 "uploadprogress", 122 "totaluploadprogress", 123 "sending", 124 "sendingmultiple", 125 "success", 126 "successmultiple", 127 "canceled", 128 "canceledmultiple", 129 "complete", 130 "completemultiple", 131 "reset", 132 "maxfilesexceeded", 133 "maxfilesreached", 134 "queuecomplete" 135 ]; 136 137 138 this.prototype.defaultOptions = { 139 /** 140 * Has to be specified on elements other than form (or when the form 141 * doesn't have an `action` attribute). You can also 142 * provide a function that will be called with `files` and 143 * must return the url (since `v3.12.0`) 144 */ 145 url: null, 146 147 /** 148 * Can be changed to `"put"` if necessary. You can also provide a function 149 * that will be called with `files` and must return the method (since `v3.12.0`).
150 */ 151 method: "post", 152 153 /** 154 * Will be set on the XHRequest. 155 */ 156 withCredentials: false, 157 158 /** 159 * The timeout for the XHR requests in milliseconds (since `v4.4.0`). 160 */ 161 timeout: 30000, 162 163 /** 164 * How many file uploads to process in parallel (See the 165 * Enqueuing file uploads documentation section for more info) 166 */ 167 parallelUploads: 2, 168 169 /** 170 * Whether to send multiple files in one request. If 171 * this it set to true, then the fallback file input element will 172 * have the `multiple` attribute as well. This option will 173 * also trigger additional events (like `processingmultiple`). See the events 174 * documentation section for more information. 175 */ 176 uploadMultiple: false, 177 178 /** 179 * Whether you want files to be uploaded in chunks to your server. This can't be 180 * used in combination with `uploadMultiple`. 181 * 182 * See [chunksUploaded](#config-chunksUploaded) for the callback to finalise an upload. 183 */ 184 chunking: false, 185 186 /** 187 * If `chunking` is enabled, this defines whether **every** file should be chunked, 188 * even if the file size is below chunkSize. This means, that the additional chunk 189 * form data will be submitted and the `chunksUploaded` callback will be invoked. 190 */ 191 forceChunking: false, 192 193 /** 194 * If `chunking` is `true`, then this defines the chunk size in bytes. 195 */ 196 chunkSize: 2000000, 197 198 /** 199 * If `true`, the individual chunks of a file are being uploaded simultaneously. 200 */ 201 parallelChunkUploads: false, 202 203 /** 204 * Whether a chunk should be retried if it fails. 205 */ 206 retryChunks: false, 207 208 /** 209 * If `retryChunks` is true, how many times should it be retried. 210 */ 211 retryChunksLimit: 3, 212 213 /** 214 * If not `null` defines how many files this Dropzone handles. If it exceeds, 215 * the event `maxfilesexceeded` will be called. The dropzone element gets the 216 * class `dz-max-files-reached` accordingly so you can provide visual feedback. 217 */ 218 maxFilesize: 256, 219 220 /** 221 * The name of the file param that gets transferred. 222 * **NOTE**: If you have the option `uploadMultiple` set to `true`, then 223 * Dropzone will append `[]` to the name. 224 */ 225 paramName: "upfile", 226 227 /** 228 * Whether thumbnails for images should be generated 229 */ 230 createImageThumbnails: true, 231 232 /** 233 * In MB. When the filename exceeds this limit, the thumbnail will not be generated. 234 */ 235 maxThumbnailFilesize: 10, 236 237 /** 238 * If `null`, the ratio of the image will be used to calculate it. 239 */ 240 thumbnailWidth: 120, 241 242 /** 243 * The same as `thumbnailWidth`. If both are null, images will not be resized. 244 */ 245 thumbnailHeight: 120, 246 247 /** 248 * How the images should be scaled down in case both, `thumbnailWidth` and `thumbnailHeight` are provided. 249 * Can be either `contain` or `crop`. 250 */ 251 thumbnailMethod: 'crop', 252 253 /** 254 * If set, images will be resized to these dimensions before being **uploaded**. 255 * If only one, `resizeWidth` **or** `resizeHeight` is provided, the original aspect 256 * ratio of the file will be preserved. 257 * 258 * The `options.transformFile` function uses these options, so if the `transformFile` function 259 * is overridden, these options don't do anything. 260 */ 261 resizeWidth: null, 262 263 /** 264 * See `resizeWidth`. 265 */ 266 resizeHeight: null, 267 268 /** 269 * The mime type of the resized image (before it gets uploaded to the server). 270 * If `null` the original mime type will be used. To force jpeg, for example, use `image/jpeg`. 271 * See `resizeWidth` for more information. 272 */ 273 resizeMimeType: null, 274 275 /** 276 * The quality of the resized images. See `resizeWidth`. 277 */ 278 resizeQuality: 0.8, 279 280 /** 281 * How the images should be scaled down in case both, `resizeWidth` and `resizeHeight` are provided. 282 * Can be either `contain` or `crop`. 283 */ 284 resizeMethod: 'contain', 285 286 /** 287 * The base that is used to calculate the filesize. You can change this to 288 * 1024 if you would rather display kibibytes, mebibytes, etc... 289 * 1024 is technically incorrect, because `1024 bytes` are `1 kibibyte` not `1 kilobyte`. 290 * You can change this to `1024` if you don't care about validity. 291 */ 292 filesizeBase: 1000, 293 294 /** 295 * Can be used to limit the maximum number of files that will be handled by this Dropzone 296 */ 297 maxFiles: null, 298 299 /** 300 * An optional object to send additional headers to the server. Eg: 301 * `{ "My-Awesome-Header": "header value" }` 302 */ 303 headers: null, 304 305 /** 306 * If `true`, the dropzone element itself will be clickable, if `false` 307 * nothing will be clickable. 308 * 309 * You can also pass an HTML element, a CSS selector (for multiple elements) 310 * or an array of those. In that case, all of those elements will trigger an 311 * upload when clicked. 312 */ 313 clickable: true, 314 315 /** 316 * Whether hidden files in directories should be ignored. 317 */ 318 ignoreHiddenFiles: true, 319 320 321 /** 322 * The default implementation of `accept` checks the file's mime type or 323 * extension against this list. This is a comma separated list of mime
324 * types or file extensions. 325 * 326 * Eg.: `image/*,application/pdf,.psd` 327 * 328 * If the Dropzone is `clickable` this option will also be used as 329 * [`accept`](https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept) 330 * parameter on the hidden file input as well. 331 */ 332 acceptedFiles: null, 333 334 /** 335 * **Deprecated!** 336 * Use acceptedFiles instead. 337 */ 338 acceptedMimeTypes: null, 339 340 /** 341 * If false, files will be added to the queue but the queue will not be 342 * processed automatically. 343 * This can be useful if you need some additional user input before sending 344 * files (or if you want want all files sent at once). 345 * If you're ready to send the file simply call `myDropzone.processQueue()`. 346 * 347 * See the [enqueuing file uploads](#enqueuing-file-uploads) documentation 348 * section for more information. 349 */ 350 autoProcessQueue: true, 351 352 /** 353 * If false, files added to the dropzone will not be queued by default. 354 * You'll have to call `enqueueFile(file)` manually. 355 */ 356 autoQueue: true, 357 358 /** 359 * If `true`, this will add a link to every file preview to remove or cancel (if 360 * already uploading) the file. The `dictCancelUpload`, `dictCancelUploadConfirmation` 361 * and `dictRemoveFile` options are used for the wording. 362 */ 363 addRemoveLinks: false, 364 365 /** 366 * Defines where to display the file previews â if `null` the 367 * Dropzone element itself is used. Can be a plain `HTMLElement` or a CSS 368 * selector. The element should have the `dropzone-previews` class so 369 * the previews are displayed properly. 370 */ 371 previewsContainer: null, 372 373 /** 374 * This is the element the hidden input field (which is used when clicking on the 375 * dropzone to trigger file selection) will be appended to. This might 376 * be important in case you use frameworks to switch the content of your page. 377 * 378 * Can be a selector string, or an element directly. 379 */ 380 hiddenInputContainer: "body", 381 382 /** 383 * If null, no capture type will be specified 384 * If camera, mobile devices will skip the file selection and choose camera 385 * If microphone, mobile devices will skip the file selection and choose the microphone 386 * If camcorder, mobile devices will skip the file selection and choose the camera in video mode 387 * On apple devices multiple must be set to false. AcceptedFiles may need to 388 * be set to an appropriate mime type (e.g. "image/*", "audio/*", or "video/*"). 389 */ 390 capture: null, 391 392 /** 393 * **Deprecated**. Use `renameFile` instead. 394 */ 395 renameFilename: null, 396 397 /** 398 * A function that is invoked before the file is uploaded to the server and renames the file. 399 * This function gets the `File` as argument and can use the `file.name`. The actual name of the 400 * file that gets used during the upload can be accessed through `file.upload.filename`. 401 */ 402 renameFile: null, 403 404 /** 405 * If `true` the fallback will be forced. This is very useful to test your server 406 * implementations first and make sure that everything works as 407 * expected without dropzone if you experience problems, and to test 408 * how your fallbacks will look. 409 */ 410 forceFallback: false, 411 412 /** 413 * The text used before any files are dropped. 414 */ 415 dictDefaultMessage: "Drop files here to upload", 416 417 /** 418 * The text that replaces the default message text it the browser is not supported. 419 */ 420 dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", 421 422 /** 423 * The text that will be added before the fallback form. 424 * If you provide a fallback element yourself, or if this option is `null` this will 425 * be ignored. 426 */ 427 dictFallbackText: "Please use the fallback form below to upload your files like in the olde
427n days.", 428 429 /** 430 * If the filesize is too big. 431 * `{{filesize}}` and `{{maxFilesize}}` will be replaced with the respective configuration values. 432 */ 433 dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", 434 435 /** 436 * If the file doesn't match the file type. 437 */ 438 dictInvalidFileType: "You can't upload files of this type.", 439 440 /** 441 * If the server response was invalid. 442 * `{{statusCode}}` will be replaced with the servers status code. 443 */ 444 dictResponseError: "Server responded with {{statusCode}} code.", 445 446 /** 447 * If `addRemoveLinks` is true, the text to be used for the cancel upload link. 448 */ 449 dictCancelUpload: "Cancel upload", 450 451 /** 452 * The text that is displayed if an upload was manually canceled 453 */ 454 dictUploadCanceled: "Upload canceled.", 455 456 /** 457 * If `addRemoveLinks` is true, the text to be used for confirmation when cancelling upload. 458 */ 459 dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", 460 461 /** 462 * If `addRemoveLinks` is true, the text to be used to remove a file. 463 */ 464 dictRemoveFile: "Remove file", 465 466 /** 467 * If this is not null, then the user will be prompted before removing a file. 468 */ 469 dictRemoveFileConfirmation: null, 470 471 /** 472 * Displayed if `maxFiles` is st and exceeded. 473 * The string `{{maxFiles}}` will be replaced by the configuration value. 474 */ 475 dictMaxFilesExceeded: "You can not upload any more files.", 476 477 /** 478 * Allows you to translate the different units. Starting with `tb` for terabytes and going down to 479 * `b` for bytes. 480 */ 481 dictFileSizeUnits: { tb: "TB", gb: "GB", mb: "MB", kb: "KB", b: "b" }, 482 /** 483 * Called when dropzone initialized 484 * You can add event listeners here 485 */ 486 init() {}, 487 488 /** 489 * Can be an **object** of additional parameters to transfer to the server, **or** a `Function` 490 * that gets invoked with the `files`, `xhr` and, if it's a chunked upload, `chunk` arguments. In case 491 * of a function, this needs to return a map. 492 * 493 * The default implementation does nothing for normal uploads, but adds relevant information for 494 * chunked uploads. 495 * 496 * This is the same as adding hidden input fields in the form element. 497 */ 498 params(files, xhr, chunk) { 499 if (chunk) { 500 return { 501 dzuuid: chunk.file.upload.uuid, 502 dzchunkindex: chunk.index, 503 dztotalfilesize: chunk.file.size, 504 dzchunksize: this.options.chunkSize, 505 dztotalchunkcount: chunk.file.upload.totalChunkCount, 506 dzchunkbyteoffset: chunk.index * this.options.chunkSize 507 }; 508 } 509 }, 510 511 /** 512 * A function that gets a [file](https://developer.mozilla.org/en-US/docs/DOM/File) 513 * and a `done` function as parameters. 514 * 515 * If the done function is invoked without arguments, the file is "accepted" and will 516 * be processed. If you pass an error message, the file is rejected, and the error 517 * message will be displayed. 518 * This function will not be called if the file is too big or doesn't match the mime types. 519 */ 520 accept(file, done) { 521 return done(); 522 }, 523 524 /** 525 * The callback that will be invoked when all chunks have been uploaded for a file. 526 * It gets the file for which the chunks have been uploaded as the first parameter, 527 * and the `done` function as second. `done()` needs to be invoked when everything 528 * needed to finish the upload process is done. 529 */ 530 chunksUploaded: function(file, done) { done(); }, 531 532 /** 533 * Gets called when the browser is not supported. 534 * The default implementation shows the fallback input field and adds 535 * a text. 536 */ 537 fallback() { 538 // This code should pass in IE7... :( 539 let messageElement; 540 this.element.className = `${this.element.className} dz-browser-not-supported`; 541 542 for (let child of this.element.getElementsByTagName("div")) { 543 if (/(^| )dz-message($| )/.test(child.className)) { 544 messageElement = child; 545 child.className = "dz-message"; // Removes the 'dz-default' class 546 break; 547 } 548 } 549 if (!messageElement) { 550 messageElement = Dropzone.createElement("<div class=\"dz-message\"><span></span></div>"); 551 this.element.appendChild(messageElement); 552 } 553 554 let span = messageElement.getElementsByTagName("span")[0]; 555 if (span) { 556 if (span.textContent != null) { 557 span.textContent = this.options.dictFallbackMessage; 558 }
558 else if (span.innerText != null) { 559 span.innerText = this.options.dictFallbackMessage; 560 } 561 } 562 563 return this.element.appendChild(this.getFallbackForm()); 564 }, 565 566 567 /** 568 * Gets called to calculate the thumbnail dimensions. 569 * 570 * It gets `file`, `width` and `height` (both may be `null`) as parameters and must return an object containing: 571 * 572 * - `srcWidth` & `srcHeight` (required) 573 * - `trgWidth` & `trgHeight` (required) 574 * - `srcX` & `srcY` (optional, default `0`) 575 * - `trgX` & `trgY` (optional, default `0`) 576 * 577 * Those values are going to be used by `ctx.drawImage()`. 578 */ 579 resize(file, width, height, resizeMethod) { 580 let info = { 581 srcX: 0, 582 srcY: 0, 583 srcWidth: file.width, 584 srcHeight: file.height 585 }; 586 587 let srcRatio = file.width / file.height; 588 589 // Automatically calculate dimensions if not specified 590 if ((width == null) && (height == null)) { 591 width = info.srcWidth; 592 height = info.srcHeight; 593 } else if ((width == null)) { 594 width = height * srcRatio; 595 } else if ((height == null)) { 596 height = width / srcRatio; 597 } 598 599 // Make sure images aren't upscaled 600 width = Math.min(width, info.srcWidth); 601 height = Math.min(height, info.srcHeight); 602 603 let trgRatio = width / height; 604 605 if ((info.srcWidth > width) || (info.srcHeight > height)) { 606 // Image is bigger and needs rescaling 607 if (resizeMethod === 'crop') { 608 if (srcRatio > trgRatio) { 609 info.srcHeight = file.height; 610 info.srcWidth = info.srcHeight * trgRatio; 611 } else { 612 info.srcWidth = file.width; 613 info.srcHeight = info.srcWidth / trgRatio; 614 } 615 } else if (resizeMethod === 'contain') { 616 // Method 'contain' 617 if (srcRatio > trgRatio) { 618 height = width / srcRatio; 619 } else { 620 width = height * srcRatio; 621 } 622 } else { 623 throw new Error(`Unknown resizeMethod '${resizeMethod}'`); 624 } 625 } 626 627 info.srcX = (file.width - info.srcWidth) / 2; 628 info.srcY = (file.height - info.srcHeight) / 2; 629 630 info.trgWidth = width; 631 info.trgHeight = height; 632 633 return info; 634 }, 635 636 /** 637 * Can be used to transform the file (for example, resize an image if necessary). 638 * 639 * The default implementation uses `resizeWidth` and `resizeHeight` (if provided) and resizes 640 * images according to those dimensions. 641 * 642 * Gets the `file` as the first parameter, and a `done()` function as the second, that needs 643 * to be invoked with the file when the transformation is done. 644 */ 645 transformFile(file, done) { 646 if ((this.options.resizeWidth || this.options.resizeHeight) && file.type.match(/image.*/)) { 647 return this.resizeImage(file, this.options.resizeWidth, this.options.resizeHeight, this.options.resizeMethod, done); 648 } else { 649 return done(file); 650 } 651 }, 652 653 654 /** 655 * A string that contains the template used for each dropped 656 * file. Change it to fulfill your needs but make sure to properly 657 * provide all elements. 658 * 659 * If you want to use an actual HTML element instead of providing a String 660 * as a config option, you could create a div with the id `tpl`, 661 * put the template inside it and provide the element like this: 662 * 663 * document 664 * .querySelector('#tpl') 665 * .innerHTML 666 * 667 */ 668 previewTemplate: `\ 669<div class="dz-preview dz-file-preview">
670 <div class="dz-image"><img data-dz-thumbnail /></div> 671 <div class="dz-details"> 672 <div class="dz-size"><span data-dz-size></span></div> 673 <div class="dz-filename"><span data-dz-name></span></div> 674 </div> 675 <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div> 676 <div class="dz-error-message"><span data-dz-errormessage></span></div> 677 <div class="dz-success-mark"> 678 <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 679 <title>Check</title> 680 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 681 <path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF"></path> 682 </g> 683 </svg> 684 </div> 685 <div class="dz-error-mark"> 686 <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 687 <title>Error</title> 688 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 689 <g stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475"> 690 <path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z"></path> 691 </g> 692 </g> 693 </svg> 694 </div> 695</div>\ 696`, 697 698 // END OPTIONS 699 // (Required by the dropzone documentation parser) 700 701 702 /* 703 Those functions register themselves to the events on init and handle all 704 the user interface specific stuff. Overwriting them won't break the upload 705 but can break the way it's displayed. 706 You can overwrite them if you don't like the default behavior. If you just 707 want to add an additional event handler, register it on the dropzone object 708 and don't overwrite those options. 709 */ 710 711 712 713 714 // Those are self explanatory and simply concern the DragnDrop. 715 drop(e) { 716 return this.element.classList.remove("dz-drag-hover"); 717 }, 718 dragstart(e) {}, 719 dragend(e) { 720 return this.element.classList.remove("dz-drag-hover"); 721 }, 722 dragenter(e) { 723 return this.element.classList.add("dz-drag-hover"); 724 }, 725 dragover(e) { 726 return this.element.classList.add("dz-drag-hover"); 727 }, 728 dragleave(e) { 729 return this.element.classList.remove("dz-drag-hover"); 730 }, 731 732 paste(e) {}, 733 734 // Called whenever there are no files left in the dropzone anymore, and the 735 // dropzone should be displayed as if in the initial state. 736 reset() { 737 return this.element.classList.remove("dz-started"); 738 }, 739 740 // Called when a file is added to the queue 741 // Receives `file` 742 addedfile(file) { 743 if (this.element === this.previewsContainer) { 744 this.element.classList.add("dz-started"); 745 } 746 747 if (this.previewsContainer) { 748 file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); 749 file.previewTemplate = file.previewElement; // Backwards compatibility 750 751 this.previewsContainer.appendChild(file.previewElement); 752 for (var node of file.previewElement.querySelectorAll("[data-dz-name]")) { 753 node.textContent = file.name; 754 } 755 for (node of file.previewElement.querySelectorAll("[data-dz-size]")) { 756 node.innerHTML = this.filesize(file.size); 757 } 758 759 if (this.options.addRemoveLinks) { 760 file._removeLink = Dropzone.createElement(`<a class="dz-remove" href="javascript:undefined;" data-dz-remove>${this.options.dictRemoveFile}</a>`); 761 file.previewElement.appendChild(file._removeLink); 762 } 763 764 let removeFileEvent = e => { 765 e.preventDefault(); 766 e.stopPropagation(); 767 if (file.status === Dropzone.UPLOADING) { 768 return Dropzone.confirm(this.options.dictCancelUploadConfirmation, () => this.removeFile(file)); 769 } else { 770 if (this.options.dictRemoveFileConfirmation) { 771 return Dropzone.confirm(this.options.dictRemoveFileConfirmation, () => this.removeFile(file)); 772 } else { 773 return this.removeFile(file); 774 } 775 } 776 }; 777 778 for (let removeLink of file.previewElement.querySelectorAll("[data-dz-remove]")) { 779 removeLink.addEventListener("click", removeFileEvent); 780 } 781 } 782 }, 783 784 785 // Called whenever a file is removed. 786 removedfile(file) { 787 if (file.previewElement != null && file.previewElement.parentNode != null) { 788 file.previewElement.parentNode.removeChild(file.previewElement); 789 } 790 return this._updateMaxFilesReachedClass(); 791 }, 792 793 // Called when a thumbnail has been generated 794 // Receives `file` and `dataUrl` 795 thumbnail(file, dataUrl) { 796 if (file.previewElement) { 797 file.previewElement.classList.remove("dz-file-preview"); 798 for (let thumbnailElement of file.previewElement.querySelectorAll("[data-dz-thumbnail]")) { 799 thumbnailElement.alt = file.name; 800 thumbnailElement.src = dataUrl; 801 } 802 803 return setTimeout((() => file.previewElement.classList.add("dz-image-preview")), 1); 804 } 805 }, 806 807 // Called whenever an error occurs 808 // Receives `file` and `message` 809 error(file, message) { 810 if (file.previewElement) { 811 file.previewElement.classList.add("dz-error"); 812 if ((typeof message !== "String") && message.error) { 813 message = message.error; 814 } 815 for (let node of file.previewElement.querySelectorAll("[data-dz-errormessage]")) { 816 node.textContent = message; 817 } 818 } 819 }, 820 821 errormultiple() {}, 822 823 // Called when a file gets processed. Since there is a cue, not all added 824 // files are processed immediately. 825 // Receives `file` 826 processing(file) { 827 if (file.previewElement) { 828 file.previewElement.classList.add("dz-processing"); 829 if (file._removeLink) { 830 return file._removeLink.innerHTML = this.options.dictCancelUpload; 831 } 832 } 833 }, 834 835 processingmultiple() {}, 836 837 // Called whenever the upload progress gets updated. 838 // Receives `file`, `progress` (percentage 0-100) and `bytesSent`. 839 // To get the total number of bytes of the file, use `file.size` 840 uploadprogress(file, progress, bytesSent) { 841 if (file.previewElement) { 842 for (let node of file.previewElement.querySelectorAll("[data-dz-uploadprogress]")) { 843 node.nodeName === 'PROGRESS' ? 844 (node.value = progress) : 845 (node.style.width = `${progress}%`) 846 } 847 } 848 }, 849 850 // Called whenever the total upload progress gets updated. 851 // Called with totalUploadProgress (0-100), totalBytes and totalBytesSent 852 totaluploadprogress() {}, 853 854 // Called just before the file is sent. Gets the `xhr` object as second 855 // parameter, so you can modify it (for example to add a CSRF token) and a 856 // `formData` object to add additional information. 857 sending() {}, 858 859 sendingmultiple() {}, 860 861 // When the complete upload is finished and successful 862 // Receives `file` 863 success(file) { 864 if (file.previewElement) { 865 return file.previewElement.classList.add("dz-success"); 866 } 867 }, 868 869 successmultiple() {}, 870 871 // When the upload is canceled. 872 canceled(file) {
873 return this.emit("error", file, this.options.dictUploadCanceled); 874 }, 875 876 canceledmultiple() {}, 877 878 // When the upload is finished, either with success or an error. 879 // Receives `file` 880 complete(file) { 881 if (file._removeLink) { 882 file._removeLink.innerHTML = this.options.dictRemoveFile; 883 } 884 if (file.previewElement) { 885 return file.previewElement.classList.add("dz-complete"); 886 } 887 }, 888 889 completemultiple() {}, 890 891 maxfilesexceeded() {}, 892 893 maxfilesreached() {}, 894 895 queuecomplete() {}, 896 897 addedfiles() {} 898 }; 899 900 901 this.prototype._thumbnailQueue = []; 902 this.prototype._processingThumbnail = false; 903 } 904 905 // global utility 906 static extend(target, ...objects) { 907 for (let object of objects) { 908 for (let key in object) { 909 let val = object[key]; 910 target[key] = val; 911 } 912 } 913 return target; 914 } 915 916 constructor(el, options) { 917 super(); 918 let fallback, left; 919 this.element = el; 920 // For backwards compatibility since the version was in the prototype previously 921 this.version = Dropzone.version; 922 923 this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); 924 925 this.clickableElements = []; 926 this.listeners = []; 927 this.files = []; // All files 928 929 if (typeof this.element === "string") { 930 this.element = document.querySelector(this.element); 931 } 932 933 // Not checking if instance of HTMLElement or Element since IE9 is extremely weird. 934 if (!this.element || (this.element.nodeType == null)) { 935 throw new Error("Invalid dropzone element."); 936 } 937 938 if (this.element.dropzone) { 939 throw new Error("Dropzone already attached."); 940 } 941 942 // Now add this dropzone to the instances. 943 Dropzone.instances.push(this); 944 945 // Put the dropzone inside the element itself. 946 this.element.dropzone = this; 947 948 let elementOptions = (left = Dropzone.optionsForElement(this.element)) != null ? left : {}; 949 950 this.options = Dropzone.extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); 951 952 // If the browser failed, just call the fallback and leave 953 if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { 954 return this.options.fallback.call(this); 955 } 956 957 // @options.url = @element.getAttribute "action" unless @options.url? 958 if (this.options.url == null) { 959 this.options.url = this.element.getAttribute("action"); 960 } 961 962 if (!this.options.url) { 963 throw new Error("No URL provided."); 964 } 965 966 if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { 967 throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); 968 } 969 970 if (this.options.uploadMultiple && this.options.chunking) { 971 throw new Error('You cannot set both: uploadMultiple and chunking.'); 972 } 973 974 // Backwards compatibility 975 if (this.options.acceptedMimeTypes) { 976 this.options.acceptedFiles = this.options.acceptedMimeTypes; 977 delete this.options.acceptedMimeTypes; 978 } 979 980 // Backwards compatibility 981 if (this.options.renameFilename != null) { 982 this.options.renameFile = file => this.options.renameFilename.call(this, file.name, file); 983 } 984 985 this.options.method = this.options.method.toUpperCase(); 986 987 if ((fallback = this.getExistingFallback()) && fallback.parentNode) { 988 // Remove the fallback 989 fallback.parentNode.removeChild(fallback); 990 } 991 992 // Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false 993 if (this.options.previewsContainer !== false) { 994 if (this.options.previewsContainer) { 995 this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); 996 } else { 997 this.previewsContainer = this.element; 998 } 999 } 1000 1001 if (this.options.clickable) { 1002 if (this.options.clickable === true) { 1003 this.clickableElements = [this.element]; 1004 } else { 1005 this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); 1006 } 1007 } 1008 1009 1010 this.init(); 1011 } 1012 1013 1014 // Returns all files that have been accepted 1015 getAcceptedFiles() { 1016 return this.files.filter((file) => file.accepted).map((file) => file); 1017 } 1018 1019 // Returns all files that have been rejected
1020 // Not sure when that's going to be useful, but added for completeness. 1021 getRejectedFiles() { 1022 return this.files.filter((file) => !file.accepted).map((file) => file); 1023 } 1024 1025 getFilesWithStatus(status) { 1026 return this.files.filter((file) => file.status === status).map((file) => file); 1027 } 1028 1029 // Returns all files that are in the queue 1030 getQueuedFiles() { 1031 return this.getFilesWithStatus(Dropzone.QUEUED); 1032 } 1033 1034 getUploadingFiles() { 1035 return this.getFilesWithStatus(Dropzone.UPLOADING); 1036 } 1037 1038 getAddedFiles() { 1039 return this.getFilesWithStatus(Dropzone.ADDED); 1040 } 1041 1042 // Files that are either queued or uploading 1043 getActiveFiles() { 1044 return this.files.filter((file) => (file.status === Dropzone.UPLOADING) || (file.status === Dropzone.QUEUED)).map((file) => file); 1045 } 1046 1047 // The function that gets called when Dropzone is initialized. You 1048 // can (and should) setup event listeners inside this function. 1049 init() { 1050 // In case it isn't set already 1051 if (this.element.tagName === "form") { 1052 this.element.setAttribute("enctype", "multipart/form-data"); 1053 } 1054 1055 if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { 1056 this.element.appendChild(Dropzone.createElement(`<div class="dz-default dz-message"><button class="dz-button" type="button">${this.options.dictDefaultMessage}</button></div>`)); 1057 } 1058 1059 if (this.clickableElements.length) { 1060 let setupHiddenFileInput = () => { 1061 if (this.hiddenFileInput) { 1062 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); 1063 } 1064 this.hiddenFileInput = document.createElement("input"); 1065 this.hiddenFileInput.setAttribute("type", "file"); 1066 if ((this.options.maxFiles === null) || (this.options.maxFiles > 1)) { 1067 this.hiddenFileInput.setAttribute("multiple", "multiple"); 1068 } 1069 this.hiddenFileInput.className = "dz-hidden-input"; 1070 1071 if (this.options.acceptedFiles !== null) { 1072 this.hiddenFileInput.setAttribute("accept", this.options.acceptedFiles); 1073 } 1074 if (this.options.capture !== null) { 1075 this.hiddenFileInput.setAttribute("capture", this.options.capture); 1076 } 1077 1078 // Not setting `display="none"` because some browsers don't accept clicks 1079 // on elements that aren't displayed. 1080 this.hiddenFileInput.style.visibility = "hidden"; 1081 this.hiddenFileInput.style.position = "absolute"; 1082 this.hiddenFileInput.style.top = "0"; 1083 this.hiddenFileInput.style.left = "0"; 1084 this.hiddenFileInput.style.height = "0"; 1085 this.hiddenFileInput.style.width = "0"; 1086 Dropzone.getElement(this.options.hiddenInputContainer, 'hiddenInputContainer').appendChild(this.hiddenFileInput); 1087 return this.hiddenFileInput.addEventListener("change", () => { 1088 let { files } = this.hiddenFileInput; 1089 if (files.length) { 1090 for (let file of files) { 1091 this.addFile(file); 1092 } 1093 } 1094 this.emit("addedfiles", files); 1095 return setupHiddenFileInput(); 1096 }); 1097 }; 1098 setupHiddenFileInput(); 1099 } 1100 1101 this.URL = window.URL !== null ? window.URL : window.webkitURL; 1102 1103 1104 // Setup all event listeners on the Dropzone object itself. 1105 // They're not in @setupEventListeners() because they shouldn't be removed 1106 // again when the dropzone gets disabled. 1107 for (let eventName of this.events) { 1108 this.on(eventName, this.options[eventName]); 1109 } 1110 1111 this.on("uploadprogress", () => this.updateTotalUploadProgress()); 1112 1113 this.on("removedfile", () => this.updateTotalUploadProgress()); 1114 1115 this.on("canceled", file =>
1115 this.emit("complete", file)); 1116 1117 // Emit a `queuecomplete` event if all files finished uploading. 1118 this.on("complete", file => { 1119 if ((this.getAddedFiles().length === 0) && (this.getUploadingFiles().length === 0) && (this.getQueuedFiles().length === 0)) { 1120 // This needs to be deferred so that `queuecomplete` really triggers after `complete` 1121 return setTimeout((() => this.emit("queuecomplete")), 0); 1122 } 1123 }); 1124 1125 const containsFiles = function(e) { 1126 return (e.dataTransfer.types && e.dataTransfer.types.some(type => type == "Files")); 1127 }; 1128 1129 let noPropagation = function(e) { 1130 // If there are no files, we don't want to stop 1131 // propagation so we don't interfere with other 1132 // drag and drop behaviour. 1133 if (!containsFiles(e)) return; 1134 e.stopPropagation(); 1135 if (e.preventDefault) { 1136 return e.preventDefault(); 1137 } else { 1138 return e.returnValue = false; 1139 } 1140 }; 1141 1142 // Create the listeners 1143 this.listeners = [{ 1144 element: this.element, 1145 events: { 1146 "dragstart": e => { 1147 return this.emit("dragstart", e); 1148 }, 1149 "dragenter": e => { 1150 noPropagation(e); 1151 return this.emit("dragenter", e); 1152 }, 1153 "dragover": e => { 1154 // Makes it possible to drag files from chrome's download bar 1155 // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar 1156 // Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception) 1157 let efct; 1158 try { 1159 efct = e.dataTransfer.effectAllowed; 1160 } catch (error) {} 1161 e.dataTransfer.dropEffect = ('move' === efct) || ('linkMove' === efct) ? 'move' : 'copy'; 1162 1163 noPropagation(e); 1164 return this.emit("dragover", e); 1165 }, 1166 "dragleave": e => { 1167 return this.emit("dragleave", e); 1168 }, 1169 "drop": e => { 1170 noPropagation(e); 1171 return this.drop(e); 1172 }, 1173 "dragend": e => { 1174 return this.emit("dragend", e); 1175 } 1176 } 1177 1178 // This is disabled right now, because the browsers don't implement it properly. 1179 // "paste": (e) => 1180 // noPropagation e 1181 // @paste e 1182 }]; 1183 1184 this.clickableElements.forEach(clickableElement => { 1185 return this.listeners.push({ 1186 element: clickableElement, 1187 events: { 1188 "click": evt => { 1189 // Only the actual dropzone or the message element should trigger file selection 1190 if ((clickableElement !== this.element) || ((evt.target === this.element) || Dropzone.elementInside(evt.target, this.element.querySelector(".dz-message")))) { 1191 this.hiddenFileInput.click(); // Forward the click 1192 } 1193 return true; 1194 } 1195 } 1196 }); 1197 }); 1198 1199 this.enable(); 1200 1201 return this.options.init.call(this); 1202 } 1203 1204 // Not fully tested yet 1205 destroy() { 1206 this.disable(); 1207 this.removeAllFiles(true); 1208 if (this.hiddenFileInput != null ? this.hiddenFileInput.parentNode : undefined) { 1209 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); 1210 this.hiddenFileInput = null; 1211 } 1212 delete this.element.dropzone; 1213 return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); 1214 } 1215 1216 1217 updateTotalUploadProgress() { 1218 let totalUploadProgress; 1219 let totalBytesSent = 0; 1220 let totalBytes = 0; 1221 1222 let activeFiles = this.getActiveFiles(); 1223 1224 if (activeFiles.length) { 1225 for (let file of this.getActiveFiles()) {
1226 totalBytesSent += file.upload.bytesSent; 1227 totalBytes += file.upload.total; 1228 } 1229 totalUploadProgress = (100 * totalBytesSent) / totalBytes; 1230 } else { 1231 totalUploadProgress = 100; 1232 } 1233 1234 return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); 1235 } 1236 1237 // @options.paramName can be a function taking one parameter rather than a string. 1238 // A parameter name for a file is obtained simply by calling this with an index number. 1239 _getParamName(n) { 1240 if (typeof this.options.paramName === "function") { 1241 return this.options.paramName(n); 1242 } else { 1243 return `${this.options.paramName}${this.options.uploadMultiple ? `[${n}]` : ""}`; 1244 } 1245 } 1246 1247 // If @options.renameFile is a function, 1248 // the function will be used to rename the file.name before appending it to the formData 1249 _renameFile(file) { 1250 if (typeof this.options.renameFile !== "function") { 1251 return file.name; 1252 } 1253 return this.options.renameFile(file); 1254 } 1255 1256 // Returns a form that can be used as fallback if the browser does not support DragnDrop 1257 // 1258 // If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided. 1259 // This code has to pass in IE7 :( 1260 getFallbackForm() { 1261 let existingFallback, form; 1262 if (existingFallback = this.getExistingFallback()) { 1263 return existingFallback; 1264 } 1265 1266 let fieldsString = "<div class=\"dz-fallback\">"; 1267 if (this.options.dictFallbackText) { 1268 fieldsString += `<p>${this.options.dictFallbackText}</p>`; 1269 } 1270 fieldsString += `<input type="file" name="${this._getParamName(0)}" ${this.options.uploadMultiple ? 'multiple="multiple"' : undefined} /><input type="submit" value="Upload!"></div>`; 1271 1272 let fields = Dropzone.createElement(fieldsString); 1273 if (this.element.tagName !== "FORM") { 1274 form = Dropzone.createElement(`<form action="${this.options.url}" enctype="multipart/form-data" method="${this.options.method}"></form>`); 1275 form.appendChild(fields); 1276 } else { 1277 // Make sure that the enctype and method attributes are set properly 1278 this.element.setAttribute("enctype", "multipart/form-data"); 1279 this.element.setAttribute("method", this.options.method); 1280 } 1281 return form != null ? form : fields; 1282 } 1283 1284 1285 // Returns the fallback elements if they exist already 1286 // 1287 // This code has to pass in IE7 :( 1288 getExistingFallback() { 1289 let getFallback = function (elements) { 1290 for (let el of elements) { 1291 if (/(^| )fallback($| )/.test(el.className)) { 1292 return el; 1293 } 1294 } 1295 }; 1296 1297 for (let tagName of ["div", "form"]) { 1298 var fallback; 1299 if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { 1300 return fallback; 1301 } 1302 } 1303 } 1304 1305 1306 // Activates all listeners stored in @listeners 1307 setupEventListeners() { 1308 return this.listeners.map((elementListeners) => 1309 (() => { 1310 let result = []; 1311 for (let event in elementListeners.events) { 1312 let listener = elementListeners.events[event]; 1313 result.push(elementListeners.element.addEventListener(event, listener, false)); 1314 } 1315 return result; 1316 })()); 1317 } 1318 1319 1320 // Deactivates all listeners stored in @listeners 1321 removeEventListeners() { 1322 return this.listeners.map((elementListeners) => 1323 (() => { 1324 let result = []; 1325 for (let event in elementListeners.events) { 1326 let listener = elementListeners.events[event]; 1327 result.push(elementListeners.element.removeEventListener(event, listener, false)); 1328 } 1329 return result; 1330 })()); 1331 } 1332 1333 // Removes all event listeners and cancels all files in the queue or being processed. 1334 disable() { 1335 this.clickableElements.forEach(element => element.classList.remove("dz-clickable")); 1336 this.removeEventListeners(); 1337 this.disabled = true; 1338 1339 return this.files.map((file) => this.cancelUpload(file)); 1340 } 1341 1342 enable() { 1343 delete this.disabled;
1344 this.clickableElements.forEach(element => element.classList.add("dz-clickable")); 1345 return this.setupEventListeners(); 1346 } 1347 1348 // Returns a nicely formatted filesize 1349 filesize(size) { 1350 let selectedSize = 0; 1351 let selectedUnit = "b"; 1352 1353 if (size > 0) { 1354 let units = ['tb', 'gb', 'mb', 'kb', 'b']; 1355 1356 for (let i = 0; i < units.length; i++) { 1357 let unit = units[i]; 1358 let cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; 1359 1360 if (size >= cutoff) { 1361 selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); 1362 selectedUnit = unit; 1363 break; 1364 } 1365 } 1366 1367 selectedSize = Math.round(10 * selectedSize) / 10; // Cutting of digits 1368 } 1369 1370 return `<strong>${selectedSize}</strong> ${this.options.dictFileSizeUnits[selectedUnit]}`; 1371 } 1372 1373 1374 // Adds or removes the `dz-max-files-reached` class from the form. 1375 _updateMaxFilesReachedClass() { 1376 if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) { 1377 if (this.getAcceptedFiles().length === this.options.maxFiles) { 1378 this.emit('maxfilesreached', this.files); 1379 } 1380 return this.element.classList.add("dz-max-files-reached"); 1381 } else { 1382 return this.element.classList.remove("dz-max-files-reached"); 1383 } 1384 } 1385 1386 1387 drop(e) { 1388 if (!e.dataTransfer) { 1389 return; 1390 } 1391 this.emit("drop", e); 1392 1393 // Convert the FileList to an Array 1394 // This is necessary for IE11 1395 let files = []; 1396 for (let i = 0; i < e.dataTransfer.files.length; i++) { 1397 files[i] = e.dataTransfer.files[i]; 1398 } 1399 1400 // Even if it's a folder, files.length will contain the folders. 1401 if (files.length) { 1402 let { items } = e.dataTransfer; 1403 if (items && items.length && (items[0].webkitGetAsEntry != null)) { 1404 // The browser supports dropping of folders, so handle items instead of files 1405 this._addFilesFromItems(items); 1406 } else { 1407 this.handleFiles(files); 1408 } 1409 } 1410 1411 this.emit("addedfiles", files); 1412 } 1413 1414 paste(e) { 1415 if (__guard__(e != null ? e.clipboardData : undefined, x => x.items) == null) { 1416 return; 1417 } 1418 1419 this.emit("paste", e); 1420 let { items } = e.clipboardData; 1421 1422 if (items.length) { 1423 return this._addFilesFromItems(items); 1424 } 1425 } 1426 1427 1428 handleFiles(files) { 1429 for (let file of files) { 1430 this.addFile(file); 1431 } 1432 } 1433 1434 // When a folder is dropped (or files are pasted), items must be handled 1435 // instead of files. 1436 _addFilesFromItems(items) { 1437 return (() => { 1438 let result = []; 1439 for (let item of items) { 1440 var entry; 1441 if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { 1442 if (entry.isFile) { 1443 result.push(this.addFile(item.getAsFile())); 1444 } else if (entry.isDirectory) { 1445 // Append all files from that directory to files 1446 result.push(this._addFilesFromDirectory(entry, entry.name)); 1447 } else { 1448 result.push(undefined); 1449 } 1450 } else if (item.getAsFile != null) { 1451 if ((item.kind == null) || (item.kind === "file")) { 1452 result.push(this.addFile(item.getAsFile())); 1453 } else { 1454 result.push(undefined); 1455 } 1456 } else { 1457 result.push(undefined); 1458 } 1459 } 1460 return result; 1461 })(); 1462 } 1463 1464 1465 // Goes through the directory, and adds each file it finds recursively 1466 _addFilesFromDirectory(directory, path) { 1467 let dirReader = directory.createReader(); 1468 1469 let errorHandler = error => __guardMethod__(console, 'log', o => o.log(error)); 1470 1471 var readEntries = () => { 1472 return dirReader.readEntries(entries => { 1473 if (entries.length > 0) { 1474 for (let entry of entries) { 1475 if (entry.isFile) { 1476 entry.file(file => { 1477 if (this.options.ignoreHiddenFiles && (file.name.substring(0, 1) === '.')) { 1478 return; 1479 } 1480 file.fullPath = `${path}/${file.name}`; 1481 return this.addFile(file); 1482 }); 1483 } else if (entry.isDirectory) { 1484 this._addFilesFromDirectory(entry, `${path}/${entry.name}`); 1485 } 1486 } 1487 1488 // Recursively call readEntries() again, since browser only handle 1489 // the first 100 entries. 1490 // See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries 1491 readEntries(); 1492 } 1493 return null; 1494 } 1495 , errorHandler); 1496 }; 1497 1498 return readEntries(); 1499 } 1500 1501 1502 // If `done()` is called without argument the file is accepted 1503 // If you call it with an error message, the file is rejected 1504 // (This allows for asynchronous validation) 1505 // 1506 // This function checks the filesize, and if the file.type passes the 1507 // `acceptedFiles` check. 1508 accept(file, done) { 1509 if (this.options.maxFilesize && file.size > (this.options.maxFilesize * 1024 * 1024)) { 1510 done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); 1511 } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { 1512 done(this.options.dictInvalidFileType); 1513 } else if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) { 1514 done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
1515 this.emit("maxfilesexceeded", file); 1516 } else { 1517 this.options.accept.call(this, file, done); 1518 } 1519 } 1520 1521 addFile(file) { 1522 file.upload = { 1523 uuid: Dropzone.uuidv4(), 1524 progress: 0, 1525 // Setting the total upload size to file.size for the beginning 1526 // It's actual different than the size to be transmitted. 1527 total: file.size, 1528 bytesSent: 0, 1529 filename: this._renameFile(file) 1530 // Not setting chunking information here, because the acutal data â and 1531 // thus the chunks â might change if `options.transformFile` is set 1532 // and does something to the data. 1533 }; 1534 this.files.push(file); 1535 1536 file.status = Dropzone.ADDED; 1537 1538 this.emit("addedfile", file); 1539 1540 this._enqueueThumbnail(file); 1541 1542 this.accept(file, error => { 1543 if (error) { 1544 file.accepted = false; 1545 this._errorProcessing([file], error); // Will set the file.status 1546 } else { 1547 file.accepted = true; 1548 if (this.options.autoQueue) { 1549 this.enqueueFile(file); 1550 } // Will set .accepted = true 1551 } 1552 this._updateMaxFilesReachedClass(); 1553 }); 1554 } 1555 1556 1557 // Wrapper for enqueueFile 1558 enqueueFiles(files) { 1559 for (let file of files) { 1560 this.enqueueFile(file); 1561 } 1562 return null; 1563 } 1564 1565 enqueueFile(file) { 1566 if ((file.status === Dropzone.ADDED) && (file.accepted === true)) { 1567 file.status = Dropzone.QUEUED; 1568 if (this.options.autoProcessQueue) { 1569 return setTimeout((() => this.processQueue()), 0); // Deferring the call 1570 } 1571 } else { 1572 throw new Error("This file can't be queued because it has already been processed or was rejected."); 1573 } 1574 } 1575 1576 _enqueueThumbnail(file) { 1577 if (this.options.createImageThumbnails && file.type.match(/image.*/) && (file.size <= (this.options.maxThumbnailFilesize * 1024 * 1024))) { 1578 this._thumbnailQueue.push(file); 1579 return setTimeout((() => this._processThumbnailQueue()), 0); // Deferring the call 1580 } 1581 } 1582 1583 _processThumbnailQueue() { 1584 if (this._processingThumbnail || (this._thumbnailQueue.length === 0)) { 1585 return; 1586 } 1587 1588 this._processingThumbnail = true; 1589 let file = this._thumbnailQueue.shift(); 1590 return this.createThumbnail(file, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.thumbnailMethod, true, dataUrl => { 1591 this.emit("thumbnail", file, dataUrl); 1592 this._processingThumbnail = false; 1593 return this._processThumbnailQueue(); 1594 }); 1595 } 1596 1597 1598 // Can be called by the user to remove a file 1599 removeFile(file) { 1600 if (file.status === Dropzone.UPLOADING) { 1601 this.cancelUpload(file); 1602 } 1603 this.files = without(this.files, file); 1604 1605 this.emit("removedfile", file); 1606 if (this.files.length === 0) { 1607 return this.emit("reset"); 1608 } 1609 } 1610 1611 // Removes all files that aren't currently processed from the list 1612 removeAllFiles(cancelIfNecessary) { 1613 // Create a copy of files since removeFile() changes the @files array. 1614 if (cancelIfNecessary == null) { 1615 cancelIfNecessary = false; 1616 } 1617 for (let file of this.files.slice()) { 1618 if ((file.status !== Dropzone.UPLOADING) || cancelIfNecessary) { 1619 this.removeFile(file); 1620 } 1621 } 1622 return null; 1623 } 1624 1625 // Resizes an image before it gets sent to the server. This function is the default behavior of 1626 // `options.transformFile` if `resizeWidth` or `resizeHeight` are set. The callback is invoked with 1627 // the resized blob. 1628 resizeImage(file, width, height, resizeMethod, callback) { 1629 return this.createThumbnail(file, width, height, resizeMethod, true, (dataUrl, canvas) => { 1630 if (canvas == null) { 1631 // The image has not been resized 1632 return callback(file); 1633 } else { 1634 let { resizeMimeType } = this.options; 1635 if (resizeMimeType == null) { 1636 resizeMimeType = file.type; 1637 } 1638 let resizedDataURL = canvas.toDataURL(resizeMimeType, this.options.resizeQuality); 1639 if ((resizeMimeType === 'image/jpeg') || (resizeMimeType === 'image/jpg')) { 1640 // Now add the original EXIF information 1641 resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL); 1642 } 1643 return callback(Dropzone.dataURItoBlob(resizedDataURL)); 1644 } 1645 }); 1646 } 1647 1648 createThumbnail(file, width, height, resizeMethod, fixOrientation, callback) { 1649 let fileReader = new FileReader; 1650 1651 fileReader.onload = () => { 1652 1653 file.dataURL = fileReader.result; 1654 1655 // Don't bother creating a thumbnail for SVG images since they're vector 1656 if (file.type === "image/svg+xml") { 1657 if (callback != null) { 1658 callback(fileReader.result); 1659 } 1660 return; 1661 } 1662 1663 this.createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback); 1664 }; 1665
1666 fileReader.readAsDataURL(file); 1667 } 1668 1669 // `mockFile` needs to have these attributes: 1670 // 1671 // { name: 'name', size: 12345, imageUrl: '' } 1672 // 1673 // `callback` will be invoked when the image has been downloaded and displayed. 1674 // `crossOrigin` will be added to the `img` tag when accessing the file. 1675 displayExistingFile(mockFile, imageUrl, callback, crossOrigin, resizeThumbnail = true) { 1676 this.emit("addedfile", mockFile); 1677 this.emit("complete", mockFile); 1678 1679 if (!resizeThumbnail) { 1680 this.emit("thumbnail", mockFile, imageUrl); 1681 if (callback) callback(); 1682 } else { 1683 let onDone = (thumbnail) => { 1684 this.emit('thumbnail', mockFile, thumbnail); 1685 if (callback) callback(); 1686 } 1687 mockFile.dataURL = imageUrl; 1688 1689 this.createThumbnailFromUrl(mockFile, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.resizeMethod, this.options.fixOrientation, onDone, crossOrigin); 1690 } 1691 } 1692 1693 1694 createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback, crossOrigin) { 1695 // Not using `new Image` here because of a bug in latest Chrome versions. 1696 // See https://github.com/enyo/dropzone/pull/226 1697 let img = document.createElement("img"); 1698 1699 if (crossOrigin) { 1700 img.crossOrigin = crossOrigin; 1701 } 1702 1703 img.onload = () => { 1704 let loadExif = callback => callback(1); 1705 if ((typeof EXIF !== 'undefined' && EXIF !== null) && fixOrientation) { 1706 loadExif = callback => 1707 EXIF.getData(img, function () { 1708 return callback(EXIF.getTag(this, 'Orientation')); 1709 }) 1710 ; 1711 } 1712 1713 return loadExif(orientation => { 1714 file.width = img.width; 1715 file.height = img.height; 1716 1717 let resizeInfo = this.options.resize.call(this, file, width, height, resizeMethod); 1718 1719 let canvas = document.createElement("canvas"); 1720 let ctx = canvas.getContext("2d"); 1721 1722 canvas.width = resizeInfo.trgWidth; 1723 canvas.height = resizeInfo.trgHeight; 1724 1725 if (orientation > 4) { 1726 canvas.width = resizeInfo.trgHeight; 1727 canvas.height = resizeInfo.trgWidth; 1728 } 1729 1730 switch (orientation) { 1731 case 2: 1732 // horizontal flip 1733 ctx.translate(canvas.width, 0); 1734 ctx.scale(-1, 1); 1735 break; 1736 case 3: 1737 // 180° rotate left 1738 ctx.translate(canvas.width, canvas.height); 1739 ctx.rotate(Math.PI); 1740 break; 1741 case 4: 1742 // vertical flip 1743 ctx.translate(0, canvas.height); 1744 ctx.scale(1, -1); 1745 break; 1746 case 5: 1747 // vertical flip + 90 rotate right 1748 ctx.rotate(0.5 * Math.PI); 1749 ctx.scale(1, -1); 1750 break; 1751 case 6: 1752 // 90° rotate right 1753 ctx.rotate(0.5 * Math.PI); 1754 ctx.translate(0, -canvas.width); 1755 break; 1756 case 7: 1757 // horizontal flip + 90 rotate right 1758 ctx.rotate(0.5 * Math.PI); 1759 ctx.translate(canvas.height, -canvas.width); 1760 ctx.scale(-1, 1); 1761 break; 1762 case 8: 1763 // 90° rotate left 1764 ctx.rotate(-0.5 * Math.PI); 1765 ctx.translate(-canvas.height, 0); 1766 break; 1767 } 1768 1769 // This is a bugfix for iOS' scaling bug. 1770 drawImageIOSFix(ctx, img, resizeInfo.srcX != null ? resizeInfo.srcX : 0, resizeInfo.srcY != null ? resizeInfo.srcY : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, resizeInfo.trgX != null ? resizeInfo.trgX : 0, resizeInfo.trgY != null ? resizeInfo.trgY : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); 1771 1772 let thumbnail = canvas.toDataURL("image/png"); 1773 1774 if (callback != null) { 1775 return callback(thumbnail, canvas); 1776 } 1777 }); 1778 }; 1779 1780 if (callback != null) { 1781 img.onerror = callback; 1782 } 1783 1784 return img.src = file.dataURL; 1785 } 1786 1787 1788 // Goes through the queue and processes files if there aren't too many already. 1789 processQueue() { 1790 let { parallelUploads } = this.options; 1791 let processingLength = this.getUploadingFiles().length; 1792 let i = processingLength; 1793 1794 // There are already at least as many files uploading than should be 1795 if (processingLength >= parallelUploads) { 1796 return; 1797 } 1798 1799 let queuedFiles = this.getQueuedFiles(); 1800 1801 if (!(queuedFiles.length > 0)) { 1802 return; 1803 } 1804 1805 if (this.options.uploadMultiple) { 1806 // The files should be uploaded in one request 1807 return this.processFiles(queuedFiles.slice(0, (parallelUploads - processingLength))); 1808 } else { 1809 while (i < parallelUploads) { 1810 if (!queuedFiles.length) { 1811 return; 1812 } // Nothing left to process 1813 this.processFile(queuedFiles.shift()); 1814 i++; 1815 } 1816 } 1817 } 1818 1819 1820 // Wrapper for `processFiles` 1821 processFile(file) { 1822 return this.processFiles([file]); 1823 } 1824 1825 1826 // Loads the file, then calls finishedLoading() 1827 processFiles(files) { 1828 for (let file of files) { 1829 file.processing = true; // Backwards compatibility 1830 file.status = Dropzone.UPLOADING; 1831
1832 this.emit("processing", file); 1833 } 1834 1835 if (this.options.uploadMultiple) { 1836 this.emit("processingmultiple", files); 1837 } 1838 1839 return this.uploadFiles(files); 1840 } 1841 1842 1843 _getFilesWithXhr(xhr) { 1844 let files; 1845 return files = (this.files.filter((file) => file.xhr === xhr).map((file) => file)); 1846 } 1847 1848 1849 // Cancels the file upload and sets the status to CANCELED 1850 // **if** the file is actually being uploaded. 1851 // If it's still in the queue, the file is being removed from it and the status 1852 // set to CANCELED. 1853 cancelUpload(file) { 1854 if (file.status === Dropzone.UPLOADING) { 1855 let groupedFiles = this._getFilesWithXhr(file.xhr); 1856 for (let groupedFile of groupedFiles) { 1857 groupedFile.status = Dropzone.CANCELED; 1858 } 1859 if (typeof file.xhr !== 'undefined') { 1860 file.xhr.abort(); 1861 } 1862 for (let groupedFile of groupedFiles) { 1863 this.emit("canceled", groupedFile); 1864 } 1865 if (this.options.uploadMultiple) { 1866 this.emit("canceledmultiple", groupedFiles); 1867 } 1868 1869 } else if (file.status === Dropzone.ADDED || file.status === Dropzone.QUEUED) { 1870 file.status = Dropzone.CANCELED; 1871 this.emit("canceled", file); 1872 if (this.options.uploadMultiple) { 1873 this.emit("canceledmultiple", [file]); 1874 } 1875 } 1876 1877 if (this.options.autoProcessQueue) { 1878 return this.processQueue(); 1879 } 1880 } 1881 1882 resolveOption(option, ...args) { 1883 if (typeof option === 'function') { 1884 return option.apply(this, args); 1885 } 1886 return option; 1887 } 1888 1889 uploadFile(file) { return this.uploadFiles([file]); } 1890 1891 uploadFiles(files) { 1892 this._transformFiles(files, (transformedFiles) => { 1893 if (this.options.chunking) { 1894 // Chunking is not allowed to be used with `uploadMultiple` so we know 1895 // that there is only __one__file. 1896 let transformedFile = transformedFiles[0]; 1897 files[0].upload.chunked = this.options.chunking && (this.options.forceChunking || transformedFile.size > this.options.chunkSize); 1898 files[0].upload.totalChunkCount = Math.ceil(transformedFile.size / this.options.chunkSize); 1899 } 1900 1901 if (files[0].upload.chunked) { 1902 // This file should be sent in chunks! 1903 1904 // If the chunking option is set, we **know** that there can only be **one** file, since 1905 // uploadMultiple is not allowed with this option. 1906 let file = files[0]; 1907 let transformedFile = transformedFiles[0]; 1908 let startedChunkCount = 0; 1909 1910 file.upload.chunks = []; 1911 1912 let handleNextChunk = () => { 1913 let chunkIndex = 0; 1914 1915 // Find the next item in file.upload.chunks that is not defined yet. 1916 while (file.upload.chunks[chunkIndex] !== undefined) { 1917 chunkIndex++; 1918 } 1919 1920 // This means, that all chunks have already been started. 1921 if (chunkIndex >= file.upload.totalChunkCount) return; 1922 1923 startedChunkCount++; 1924 1925 let start = chunkIndex * this.options.chunkSize; 1926 let end = Math.min(start + this.options.chunkSize, file.size); 1927 1928 let dataBlock = { 1929 name: this._getParamName(0), 1930 data: transformedFile.webkitSlice ? transformedFile.webkitSlice(start, end) : transformedFile.slice(start, end), 1931 filename: file.upload.filename, 1932 chunkIndex: chunkIndex 1933 }; 1934 1935 file.upload.chunks[chunkIndex] = { 1936 file: file, 1937 index: chunkIndex, 1938 dataBlock: dataBlock, // In case we want to retry. 1939 status: Dropzone.UPLOADING, 1940 progress: 0, 1941 retries: 0 // The number of times this block has been retried. 1942 }; 1943 1944 1945 this._uploadData(files, [dataBlock]); 1946 }; 1947 1948 file.upload.finishedChunkUpload = (chunk) => { 1949 let allFinished = true; 1950 chunk.status = Dropzone.SUCCESS; 1951 1952 // Clear the data from the chunk 1953 chunk.dataBlock = null; 1954 // Leaving this reference to xhr intact here will cause memory leaks in some browsers 1955 chunk.xhr = null; 1956 1957 for (let i = 0; i < file.upload.totalChunkCount; i++) { 1958 if (file.upload.chunks[i] === undefined) { 1959 return handleNextChunk(); 1960 } 1961 if (file.upload.chunks[i].status !== Dropzone.SUCCESS) { 1962 allFinished = false;
1963 } 1964 } 1965 1966 if (allFinished) { 1967 this.options.chunksUploaded(file, () => { 1968 this._finished(files, '', null); 1969 }); 1970 } 1971 }; 1972 1973 if (this.options.parallelChunkUploads) { 1974 for (let i = 0; i < file.upload.totalChunkCount; i++) { 1975 handleNextChunk(); 1976 } 1977 } 1978 else { 1979 handleNextChunk(); 1980 } 1981 } else { 1982 let dataBlocks = []; 1983 for (let i = 0; i < files.length; i++) { 1984 dataBlocks[i] = { 1985 name: this._getParamName(i), 1986 data: transformedFiles[i], 1987 filename: files[i].upload.filename 1988 }; 1989 } 1990 this._uploadData(files, dataBlocks); 1991 } 1992 }); 1993 } 1994 1995 /// Returns the right chunk for given file and xhr 1996 _getChunk(file, xhr) { 1997 for (let i = 0; i < file.upload.totalChunkCount; i++) { 1998 if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].xhr === xhr) { 1999 return file.upload.chunks[i]; 2000 } 2001 } 2002 } 2003 2004 // This function actually uploads the file(s) to the server. 2005 // If dataBlocks contains the actual data to upload (meaning, that this could either be transformed 2006 // files, or individual chunks for chunked upload). 2007 _uploadData(files, dataBlocks) { 2008 let xhr = new XMLHttpRequest(); 2009 2010 // Put the xhr object in the file objects to be able to reference it later. 2011 for (let file of files) { 2012 file.xhr = xhr; 2013 } 2014 if (files[0].upload.chunked) { 2015 // Put the xhr object in the right chunk object, so it can be associated later, and found with _getChunk 2016 files[0].upload.chunks[dataBlocks[0].chunkIndex].xhr = xhr; 2017 } 2018 2019 let method = this.resolveOption(this.options.method, files); 2020 let url = this.resolveOption(this.options.url, files); 2021 xhr.open(method, url, true); 2022 2023 // Setting the timeout after open because of IE11 issue: https://gitlab.com/meno/dropzone/issues/8 2024 xhr.timeout = this.resolveOption(this.options.timeout, files); 2025 2026 // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179 2027 xhr.withCredentials = !!this.options.withCredentials; 2028 2029 2030 xhr.onload = e => { 2031 this._finishedUploading(files, xhr, e); 2032 }; 2033 2034 xhr.ontimeout = () => { 2035 this._handleUploadError(files, xhr, `Request timedout after ${this.options.timeout} seconds`); 2036 }; 2037 2038 xhr.onerror = () => { 2039 this._handleUploadError(files, xhr); 2040 }; 2041 2042 // Some browsers do not have the .upload property 2043 let progressObj = xhr.upload != null ? xhr.upload : xhr; 2044 progressObj.onprogress = (e) => this._updateFilesUploadProgress(files, xhr, e); 2045 2046 let headers = { 2047 "Accept": "application/json", 2048 "Cache-Control": "no-cache", 2049 "X-Requested-With": "XMLHttpRequest", 2050 }; 2051 2052 if (this.options.headers) { 2053 Dropzone.extend(headers, this.options.headers); 2054 } 2055 2056 for (let headerName in headers) { 2057 let headerValue = headers[headerName]; 2058 if (headerValue) { 2059 xhr.setRequestHeader(headerName, headerValue); 2060 } 2061 } 2062 2063 let formData = new FormData(); 2064 2065 // Adding all @options parameters 2066 if (this.options.params) { 2067 let additionalParams = this.options.params; 2068 if (typeof additionalParams === 'function') { 2069 additionalParams = additionalParams.call(this, files, xhr, files[0].upload.chunked ? this._getChunk(files[0], xhr) : null); 2070 } 2071 2072 for (let key in additionalParams) { 2073 let value = additionalParams[key]; 2074 formData.append(key, value); 2075 } 2076 } 2077 2078 // Let the user add additional data if necessary 2079 for (let file of files) { 2080 this.emit("sending", file, xhr, formData); 2081 } 2082 if (this.options.uploadMultiple) { 2083 this.emit("sendingmultiple", files, xhr, formData); 2084 } 2085 2086 2087 this._addFormElementData(formData); 2088 2089 2090 // Finally add the files 2091 // Has to be last because some servers (eg: S3) expect the file to be the last parameter 2092 for (let i = 0; i < dataBlocks.length; i++) { 2093 let dataBlock = dataBlocks[i]; 2094 formData.append(dataBlock.name, dataBlock.data, dataBlock.filename); 2095 } 2096 2097 this.submitRequest(xhr, formData, files); 2098 } 2099 2100 2101 // Transforms all files with this.options.transformFile and invokes done with the transformed files when done. 2102 _transformFiles(files, done) { 2103 let transformedFiles = []; 2104 // Clumsy way of handling asynchronous calls, until I get to add a proper Future library. 2105 let doneCounter = 0; 2106 for (let i = 0; i < files.length; i++) { 2107 this.options.transformFile.call(this, files[i], (transformedFile) => { 2108 transformedFiles[i] = transformedFile; 2109 if (++doneCounter === files.length) { 2110 done(transformedFiles); 2111 } 2112 }); 2113 } 2114 } 2115 2116 // Takes care of adding other input elements of the form to the AJAX request 2117 _addFormElementData(formData) { 2118 // Take care of other input elements 2119 if (this.element.tagName === "FORM") { 2120 for (let input of this.element.querySelectorAll("input, textarea, select, button")) { 2121 let inputName = input.getAttribute("name"); 2122 let inputType = input.getAttribute("type"); 2123 if (inputType) inputType = inputType.toLowerCase(); 2124 2125 // If the input doesn't have a name, we can't use it. 2126 if (typeof inputName === 'undefined' || inputName === null) continue; 2127 2128 if ((input.tagName === "SELECT") && input.hasAttribute("multiple")) { 2129 // Possibly multiple values 2130 for (let option of input.options) { 2131 if (option.selected) { 2132 formData.append(inputName, option.value); 2133 } 2134 } 2135 } else if (!inputType || (inputType !== "checkbox" && inputType !== "radio") || input.checked) { 2136 formData.append(inputName, input.value); 2137 } 2138 } 2139 } 2140 } 2141 2142 // Invoked when there is new progress information about given files. 2143 // If e is not provided, it is assumed that the upload is finished. 2144 _updateFilesUploadProgress(files, xhr, e) { 2145 let progress; 2146 if (typeof e !== 'undefined') { 2147 progress = (100 * e.loaded) / e.total; 2148 2149 if (files[0].upload.chunked) { 2150 let file = files[0]; 2151 // Since this is a chunked upload, we need to update the appropriate chunk progress. 2152 let chunk = this._getChunk(file, xhr); 2153 chunk.progress = progress; 2154 chunk.total = e.total; 2155 chunk.bytesSent = e.loaded; 2156 let fileProgress = 0, fileTotal, fileBytesSent; 2157 file.upload.progress = 0; 2158 file.upload.total = 0;
2159 file.upload.bytesSent = 0; 2160 for (let i = 0; i < file.upload.totalChunkCount; i++) { 2161 if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].progress !== undefined) { 2162 file.upload.progress += file.upload.chunks[i].progress; 2163 file.upload.total += file.upload.chunks[i].total; 2164 file.upload.bytesSent += file.upload.chunks[i].bytesSent; 2165 } 2166 } 2167 file.upload.progress = file.upload.progress / file.upload.totalChunkCount; 2168 } else { 2169 for (let file of files) { 2170 file.upload.progress = progress; 2171 file.upload.total = e.total; 2172 file.upload.bytesSent = e.loaded; 2173 } 2174 } 2175 for (let file of files) { 2176 this.emit("uploadprogress", file, file.upload.progress, file.upload.bytesSent); 2177 } 2178 } else { 2179 // Called when the file finished uploading 2180 2181 let allFilesFinished = true; 2182 2183 progress = 100; 2184 2185 for (let file of files) { 2186 if ((file.upload.progress !== 100) || (file.upload.bytesSent !== file.upload.total)) { 2187 allFilesFinished = false; 2188 } 2189 file.upload.progress = progress; 2190 file.upload.bytesSent = file.upload.total; 2191 } 2192 2193 // Nothing to do, all files already at 100% 2194 if (allFilesFinished) { 2195 return; 2196 } 2197 2198 for (let file of files) { 2199 this.emit("uploadprogress", file, progress, file.upload.bytesSent); 2200 } 2201 } 2202 2203 } 2204 2205 2206 _finishedUploading(files, xhr, e) { 2207 let response; 2208 2209 if (files[0].status === Dropzone.CANCELED) { 2210 return; 2211 } 2212 2213 if (xhr.readyState !== 4) { 2214 return; 2215 } 2216 2217 if ((xhr.responseType !== 'arraybuffer') && (xhr.responseType !== 'blob')) { 2218 response = xhr.responseText; 2219 2220 if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { 2221 try { 2222 response = JSON.parse(response); 2223 } catch (error) { 2224 e = error; 2225 response = "Invalid JSON response from server."; 2226 } 2227 } 2228 } 2229 2230 this._updateFilesUploadProgress(files); 2231 2232 if (!(200 <= xhr.status && xhr.status < 300)) { 2233 this._handleUploadError(files, xhr, response); 2234 } else { 2235 if (files[0].upload.chunked) { 2236 files[0].upload.finishedChunkUpload(this._getChunk(files[0], xhr)); 2237 } else { 2238 this._finished(files, response, e); 2239 } 2240 } 2241 } 2242 2243 _handleUploadError(files, xhr, response) { 2244 if (files[0].status === Dropzone.CANCELED) { 2245 return; 2246 } 2247 2248 if (files[0].upload.chunked && this.options.retryChunks) { 2249 let chunk = this._getChunk(files[0], xhr); 2250 if (chunk.retries++ < this.options.retryChunksLimit) { 2251 this._uploadData(files, [chunk.dataBlock]); 2252 return; 2253 } else { 2254 console.warn('Retried this chunk too often. Giving up.') 2255 } 2256 } 2257 2258 this._errorProcessing(files, response || this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr); 2259 } 2260 2261 submitRequest(xhr, formData, files) { 2262 xhr.send(formData); 2263 } 2264 2265 // Called internally when processing is finished. 2266 // Individual callbacks have to be called in the appropriate sections. 2267 _finished(files, responseText, e) { 2268 for (let file of files) { 2269 file.status = Dropzone.SUCCESS; 2270 this.emit("success", file, responseText, e); 2271 this.emit("complete", file); 2272 } 2273 if (this.options.uploadMultiple) { 2274 this.emit("successmultiple", files, responseText, e); 2275 this.emit("completemultiple", files); 2276 } 2277 2278 if (this.options.autoProcessQueue) { 2279 return this.processQueue(); 2280 } 2281 } 2282 2283 // Called internally when processing is finished. 2284 // Individual callbacks have to be called in the appropriate sections. 2285 _errorProcessing(files, message, xhr) { 2286 for (let file of files) { 2287 file.status = Dropzone.ERROR; 2288 this.emit("error", file, message, xhr); 2289 this.emit("complete", file); 2290 } 2291 if (this.options.uploadMultiple) { 2292 this.emit("errormultiple", files, message, xhr); 2293 this.emit("completemultiple", files); 2294 } 2295 2296 if (this.options.autoProcessQueue) { 2297 return this.processQueue(); 2298 } 2299 } 2300 2301 static uuidv4() { 2302 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 2303 let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 2304 return v.toString(16); 2305 }); 2306 }; 2307} 2308Dropzone.initClass(); 2309 2310 2311Dropzone.version = "5.7.0"; 2312 2313 2314// This is a map of options for your different dropzones. Add configurations 2315// to this object for your different dropzone elemens. 2316// 2317// Example: 2318// 2319// Dropzone.options.myDropzoneElementId = { maxFilesize: 1 }; 2320// 2321// To disable autoDiscover for a specific element, you can set `false` as an option: 2322// 2323// Dropzone.options.myDisabledElementId = false;
2324// 2325// And in html: 2326// 2327// <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form> 2328Dropzone.options = {}; 2329 2330 2331// Returns the options for an element or undefined if none available. 2332Dropzone.optionsForElement = function (element) { 2333 // Get the `Dropzone.options.elementId` for this element if it exists 2334 if (element.getAttribute("id")) { 2335 return Dropzone.options[camelize(element.getAttribute("id"))]; 2336 } else { 2337 return undefined; 2338 } 2339}; 2340 2341 2342// Holds a list of all dropzone instances 2343Dropzone.instances = []; 2344 2345// Returns the dropzone for given element if any 2346Dropzone.forElement = function (element) { 2347 if (typeof element === "string") { 2348 element = document.querySelector(element); 2349 } 2350 if ((element != null ? element.dropzone : undefined) == null) { 2351 throw new Error("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."); 2352 } 2353 return element.dropzone; 2354}; 2355 2356 2357// Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements. 2358Dropzone.autoDiscover = false; 2359 2360// Looks for all .dropzone elements and creates a dropzone for them 2361Dropzone.discover = function () { 2362 let dropzones; 2363 if (document.querySelectorAll) { 2364 dropzones = document.querySelectorAll(".dropzone"); 2365 } else { 2366 dropzones = []; 2367 // IE :( 2368 let checkElements = elements => 2369 (() => { 2370 let result = []; 2371 for (let el of elements) { 2372 if (/(^| )dropzone($| )/.test(el.className)) { 2373 result.push(dropzones.push(el)); 2374 } else { 2375 result.push(undefined); 2376 } 2377 } 2378 return result; 2379 })() 2380 ; 2381 checkElements(document.getElementsByTagName("div")); 2382 checkElements(document.getElementsByTagName("form")); 2383 } 2384 2385 return (() => { 2386 let result = []; 2387 for (let dropzone of dropzones) { 2388 // Create a dropzone unless auto discover has been disabled for specific element 2389 if (Dropzone.optionsForElement(dropzone) !== false) { 2390 result.push(new Dropzone(dropzone)); 2391 } else { 2392 result.push(undefined); 2393 } 2394 } 2395 return result; 2396 })(); 2397}; 2398 2399 2400// Since the whole Drag'n'Drop API is pretty new, some browsers implement it, 2401// but not correctly. 2402// So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know. 2403// But what to do when browsers *theoretically* support an API, but crash 2404// when using it. 2405// 2406// This is a list of regular expressions tested against navigator.userAgent 2407// 2408// ** It should only be used on browser that *do* support the API, but 2409// incorrectly ** 2410// 2411Dropzone.blacklistedBrowsers = [ 2412 // The mac os and windows phone version of opera 12 seems to have a problem with the File drag'n'drop API. 2413 /opera.*(Macintosh|Windows Phone).*version\/12/i 2414]; 2415 2416 2417// Checks if the browser is supported 2418Dropzone.isBrowserSupported = function () { 2419 let capableBrowser = true; 2420 2421 if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { 2422 if (!("classList" in document.createElement("a"))) { 2423 capableBrowser = false; 2424 } else { 2425 // The browser supports the API, but may be blacklisted. 2426 for (let regex of Dropzone.blacklistedBrowsers) { 2427 if (regex.test(navigator.userAgent)) { 2428 capableBrowser = false; 2429 continue; 2430 } 2431 } 2432 } 2433 } else { 2434 capableBrowser = false; 2435 } 2436 2437 return capableBrowser; 2438}; 2439 2440Dropzone.dataURItoBlob = function (dataURI) { 2441 // convert base64 to raw binary data held in a string 2442 // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 2443 let byteString = atob(dataURI.split(',')[1]); 2444 2445 // separate out the mime component 2446 let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 2447 2448 // write the bytes of the string to an ArrayBuffer 2449 let ab = new ArrayBuffer(byteString.length); 2450 let ia = new Uint8Array(ab); 2451 for (let i = 0, end = byteString.length, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { 2452 ia[i] = byteString.charCodeAt(i); 2453 } 2454 2455 // write the ArrayBuffer to a blob 2456 return new Blob([ab], { type: mimeString }); 2457}; 2458 2459// Returns an array without the rejected item 2460const without = (list, rejectedItem) => list.filter((item) => item !== rejectedItem).map((item) => item); 2461 2462// abc-def_ghi -> abcDefGhi 2463const camelize = str => str.replace(/[\-_](\w)/g, match => match.charAt(1).toUpperCase()); 2464 2465// Creates an element from string 2466Dropzone.createElement = function (string) { 2467 let div = document.createElement("div"); 2468 div.innerHTML = string; 2469 return div.childNodes[0]; 2470}; 2471 2472// Tests if given element is inside (or simply is) the container 2473Dropzone.elementInside = function (element, container) { 2474 if (element === container) { 2475 return true; 2476 } // Coffeescript doesn't support do/while loops 2477 while ((element = element.parentNode)) { 2478 if (element === container) { 2479 return true; 2480 } 2481 } 2482 return false;
2483}; 2484 2485 2486Dropzone.getElement = function (el, name) { 2487 let element; 2488 if (typeof el === "string") { 2489 element = document.querySelector(el); 2490 } else if (el.nodeType != null) { 2491 element = el; 2492 } 2493 if (element == null) { 2494 throw new Error(`Invalid \`${name}\` option provided. Please provide a CSS selector or a plain HTML element.`); 2495 } 2496 return element; 2497}; 2498 2499 2500Dropzone.getElements = function (els, name) { 2501 let el, elements; 2502 if (els instanceof Array) { 2503 elements = []; 2504 try { 2505 for (el of els) { 2506 elements.push(this.getElement(el, name)); 2507 } 2508 } catch (e) { 2509 elements = null; 2510 } 2511 } else if (typeof els === "string") { 2512 elements = []; 2513 for (el of document.querySelectorAll(els)) { 2514 elements.push(el); 2515 } 2516 } else if (els.nodeType != null) { 2517 elements = [els]; 2518 } 2519 2520 if ((elements == null) || !elements.length) { 2521 throw new Error(`Invalid \`${name}\` option provided. Please provide a CSS selector, a plain HTML element or a list of those.`); 2522 } 2523 2524 return elements; 2525}; 2526 2527// Asks the user the question and calls accepted or rejected accordingly 2528// 2529// The default implementation just uses `window.confirm` and then calls the 2530// appropriate callback. 2531Dropzone.confirm = function (question, accepted, rejected) { 2532 if (window.confirm(question)) { 2533 return accepted(); 2534 } else if (rejected != null) { 2535 return rejected(); 2536 } 2537}; 2538 2539// Validates the mime type like this: 2540// 2541// https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept 2542Dropzone.isValidFile = function (file, acceptedFiles) { 2543 if (!acceptedFiles) { 2544 return true; 2545 } // If there are no accepted mime types, it's OK 2546 acceptedFiles = acceptedFiles.split(","); 2547 2548 let mimeType = file.type; 2549 let baseMimeType = mimeType.replace(/\/.*$/, ""); 2550 2551 for (let validType of acceptedFiles) { 2552 validType = validType.trim(); 2553 if (validType.charAt(0) === ".") { 2554 if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { 2555 return true; 2556 } 2557 } else if (/\/\*$/.test(validType)) { 2558 // This is something like a image/* mime type 2559 if (baseMimeType === validType.replace(/\/.*$/, "")) { 2560 return true; 2561 } 2562 } else { 2563 if (mimeType === validType) { 2564 return true; 2565 } 2566 } 2567 } 2568 2569 return false; 2570}; 2571 2572// Augment jQuery 2573if (typeof jQuery !== 'undefined' && jQuery !== null) { 2574 jQuery.fn.dropzone = function (options) { 2575 return this.each(function () { 2576 return new Dropzone(this, options); 2577 }); 2578 }; 2579} 2580 2581 2582if (typeof module !== 'undefined' && module !== null) { 2583 module.exports = Dropzone; 2584} else { 2585 window.Dropzone = Dropzone; 2586} 2587 2588 2589// Dropzone file status codes 2590Dropzone.ADDED = "added"; 2591 2592Dropzone.QUEUED = "queued"; 2593// For backwards compatibility. Now, if a file is accepted, it's either queued 2594// or uploading. 2595Dropzone.ACCEPTED = Dropzone.QUEUED; 2596 2597Dropzone.UPLOADING = "uploading"; 2598Dropzone.PROCESSING = Dropzone.UPLOADING; // alias 2599 2600Dropzone.CANCELED = "canceled"; 2601Dropzone.ERROR = "error"; 2602Dropzone.SUCCESS = "success"; 2603 2604 2605/* 2606 2607 Bugfix for iOS 6 and 7 2608 Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios 2609 based on the work of https://github.com/stomita/ios-imagefile-megapixel 2610 2611 */ 2612 2613// Detecting vertical squash in loaded image. 2614// Fixes a bug which squash image vertically while drawing into canvas for some images. 2615// This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel 2616let detectVerticalSquash = function (img) { 2617 let iw = img.naturalWidth; 2618 let ih = img.naturalHeight; 2619 let canvas = document.createElement("canvas"); 2620 canvas.width = 1; 2621 canvas.height = ih; 2622 let ctx = canvas.getContext("2d"); 2623 ctx.drawImage(img, 0, 0); 2624 let { data } = ctx.getImageData(1, 0, 1, ih); 2625 2626 2627 // search image edge pixel position in case it is squashed vertically. 2628 let sy = 0; 2629 let ey = ih; 2630 let py = ih; 2631 while (py > sy) { 2632 let alpha = data[((py - 1) * 4) + 3]; 2633 2634 if (alpha === 0) { 2635 ey = py; 2636 } else { 2637 sy = py; 2638 } 2639 2640 py = (ey + sy) >> 1; 2641 } 2642 let ratio = (py / ih); 2643 2644 if (ratio === 0) { 2645 return 1; 2646 } else { 2647 return ratio; 2648 } 2649}; 2650 2651// A replacement for context.drawImage 2652// (args are for source and destination). 2653var drawImageIOSFix = function (ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { 2654 let vertSquashRatio = detectVerticalSquash(img); 2655 return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); 2656}; 2657 2658 2659// Based on MinifyJpeg 2660// Source: http://www.perry.cz/files/ExifRestorer.js 2661// http://elicon.blog57.fc2.com/blog-entry-206.html 2662class ExifRestore { 2663 static initClass() { 2664 this.KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
2664+/='; 2665 } 2666 2667 static encode64(input) { 2668 let output = ''; 2669 let chr1 = undefined; 2670 let chr2 = undefined; 2671 let chr3 = ''; 2672 let enc1 = undefined; 2673 let enc2 = undefined; 2674 let enc3 = undefined; 2675 let enc4 = ''; 2676 let i = 0; 2677 while (true) { 2678 chr1 = input[i++]; 2679 chr2 = input[i++]; 2680 chr3 = input[i++]; 2681 enc1 = chr1 >> 2; 2682 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 2683 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 2684 enc4 = chr3 & 63; 2685 if (isNaN(chr2)) { 2686 enc3 = (enc4 = 64); 2687 } else if (isNaN(chr3)) { 2688 enc4 = 64; 2689 } 2690 output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4); 2691 chr1 = (chr2 = (chr3 = '')); 2692 enc1 = (enc2 = (enc3 = (enc4 = ''))); 2693 if (!(i < input.length)) { 2694 break; 2695 } 2696 } 2697 return output; 2698 } 2699 2700 static restore(origFileBase64, resizedFileBase64) { 2701 if (!origFileBase64.match('data:image/jpeg;base64,')) { 2702 return resizedFileBase64; 2703 } 2704 let rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', '')); 2705 let segments = this.slice2Segments(rawImage); 2706 let image = this.exifManipulation(resizedFileBase64, segments); 2707 return `data:image/jpeg;base64,${this.encode64(image)}`; 2708 } 2709 2710 static exifManipulation(resizedFileBase64, segments) { 2711 let exifArray = this.getExifArray(segments); 2712 let newImageArray = this.insertExif(resizedFileBase64, exifArray); 2713 let aBuffer = new Uint8Array(newImageArray); 2714 return aBuffer; 2715 } 2716 2717 static getExifArray(segments) { 2718 let seg = undefined; 2719 let x = 0; 2720 while (x < segments.length) { 2721 seg = segments[x]; 2722 if ((seg[0] === 255) & (seg[1] === 225)) { 2723 return seg; 2724 } 2725 x++; 2726 } 2727 return []; 2728 } 2729 2730 static insertExif(resizedFileBase64, exifArray) { 2731 let imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''); 2732 let buf = this.decode64(imageData); 2733 let separatePoint = buf.indexOf(255, 3); 2734 let mae = buf.slice(0, separatePoint); 2735 let ato = buf.slice(separatePoint); 2736 let array = mae; 2737 array = array.concat(exifArray); 2738 array = array.concat(ato); 2739 return array; 2740 } 2741 2742 static slice2Segments(rawImageArray) { 2743 let head = 0; 2744 let segments = []; 2745 while (true) { 2746 var length; 2747 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 218)) { 2748 break; 2749 } 2750 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 216)) { 2751 head += 2; 2752 } else { 2753 length = (rawImageArray[head + 2] * 256) + rawImageArray[head + 3]; 2754 let endPoint = head + length + 2; 2755 let seg = rawImageArray.slice(head, endPoint); 2756 segments.push(seg); 2757 head = endPoint; 2758 } 2759 if (head > rawImageArray.length) { 2760 break; 2761 } 2762 } 2763 return segments; 2764 } 2765 2766 static decode64(input) { 2767 let output = ''; 2768 let chr1 = undefined; 2769 let chr2 = undefined; 2770 let chr3 = ''; 2771 let enc1 = undefined; 2772 let enc2 = undefined; 2773 let enc3 = undefined; 2774 let enc4 = ''; 2775 let i = 0; 2776 let buf = []; 2777 // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 2778 let base64test = /[^A-Za-z0-9\+\/\=]/g; 2779 if (base64test.exec(input)) { 2780 console.warn('There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, \'+\', \'/\',and \'=\'\nExpect errors in decoding.'); 2781 } 2782 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); 2783 while (true) { 2784 enc1 = this.KEY_STR.indexOf(input.charAt(i++)); 2785 enc2 = this.KEY_STR.indexOf(input.charAt(i++)); 2786 enc3 = this.KEY_STR.indexOf(input.charAt(i++)); 2787 enc4 = this.KEY_STR.indexOf(input.charAt(i++)); 2788 chr1 = (enc1 << 2) | (enc2 >> 4); 2789 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 2790 chr3 = ((enc3 & 3) << 6) | enc4; 2791 buf.push(chr1); 2792 if (enc3 !== 64) { 2793 buf.push(chr2); 2794 } 2795 if (enc4 !== 64) { 2796 buf.push(chr3); 2797 } 2798 chr1 = (chr2 = (chr3 = '')); 2799 enc1 = (enc2 = (enc3 = (enc4 = ''))); 2800 if (!(i < input.length)) { 2801 break; 2802 } 2803 } 2804 return buf; 2805 } 2806} 2807ExifRestore.initClass(); 2808 2809 2810/* 2811 * contentloaded.js 2812 * 2813 * Author: Diego Perini (diego.perini at gmail.com) 2814 * Summary: cross-browser wrapper for DOMContentLoaded 2815 * Updated: 20101020 2816 * License: MIT 2817 * Version: 1.2 2818 * 2819 * URL: 2820 * http://javascript.nwbox.com/ContentLoaded/ 2821 * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 2822 */ 2823 2824// @win window reference 2825// @fn function reference 2826let contentLoaded = function (win, fn) { 2827 let done = false;
2828 let top = true; 2829 let doc = win.document; 2830 let root = doc.documentElement; 2831 let add = (doc.addEventListener ? "addEventListener" : "attachEvent"); 2832 let rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); 2833 let pre = (doc.addEventListener ? "" : "on"); 2834 var init = function (e) { 2835 if ((e.type === "readystatechange") && (doc.readyState !== "complete")) { 2836 return; 2837 } 2838 ((e.type === "load" ? win : doc))[rem](pre + e.type, init, false); 2839 if (!done && (done = true)) { 2840 return fn.call(win, e.type || e); 2841 } 2842 }; 2843 2844 var poll = function () { 2845 try { 2846 root.doScroll("left"); 2847 } catch (e) { 2848 setTimeout(poll, 50); 2849 return; 2850 } 2851 return init("poll"); 2852 }; 2853 2854 if (doc.readyState !== "complete") { 2855 if (doc.createEventObject && root.doScroll) { 2856 try { 2857 top = !win.frameElement; 2858 } catch (error) { 2859 } 2860 if (top) { 2861 poll(); 2862 } 2863 } 2864 doc[add](pre + "DOMContentLoaded", init, false); 2865 doc[add](pre + "readystatechange", init, false); 2866 return win[add](pre + "load", init, false); 2867 } 2868}; 2869 2870 2871// As a single function to be able to write tests. 2872Dropzone._autoDiscoverFunction = function () { 2873 if (Dropzone.autoDiscover) { 2874 return Dropzone.discover(); 2875 } 2876}; 2877contentLoaded(window, Dropzone._autoDiscoverFunction); 2878 2879function __guard__(value, transform) { 2880 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; 2881} 2882function __guardMethod__(obj, methodName, transform) { 2883 if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { 2884 return transform(obj, methodName); 2885 } else { 2886 return undefined; 2887 } 2888}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.