1/* 2 * 3 * More info at [www.dropzonejs.com](http://www.dropzonejs.com) 4 * 5 * Copyright (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: "file", 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 olden 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" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> 679 <title>Check</title> 680 <defs></defs> 681 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> 682 <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" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path> 683 </g> 684 </svg> 685 </div> 686 <div class="dz-error-mark"> 687 <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" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> 688 <title>Error</title> 689 <defs></defs> 690 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> 691 <g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475"> 692 <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" id="Oval-2" sketch:type="MSShapeGroup"></path> 693 </g> 694 </g> 695 </svg> 696 </div> 697</div>\ 698`, 699 700 // END OPTIONS 701 // (Required by the dropzone documentation parser) 702 703 704 /* 705 Those functions register themselves to the events on init and handle all 706 the user interface specific stuff. Overwriting them won't break the upload 707 but can break the way it's displayed. 708 You can overwrite them if you don't like the default behavior. If you just 709 want to add an additional event handler, register it on the dropzone object 710 and don't overwrite those options. 711 */ 712 713 714 715 716 // Those are self explanatory and simply concern the DragnDrop. 717 drop(e) { 718 return this.element.classList.remove("dz-drag-hover"); 719 }, 720 dragstart(e) { 721 }, 722 dragend(e) { 723 return this.element.classList.remove("dz-drag-hover"); 724 }, 725 dragenter(e) { 726 return this.element.classList.add("dz-drag-hover"); 727 }, 728 dragover(e) { 729 return this.element.classList.add("dz-drag-hover"); 730 }, 731 dragleave(e) { 732 return this.element.classList.remove("dz-drag-hover"); 733 }, 734 735 paste(e) { 736 }, 737 738 // Called whenever there are no files left in the dropzone anymore, and the 739 // dropzone should be displayed as if in the initial state. 740 reset() { 741 return this.element.classList.remove("dz-started"); 742 }, 743 744 // Called when a file is added to the queue 745 // Receives `file` 746 addedfile(file) { 747 if (this.element === this.previewsContainer) { 748 this.element.classList.add("dz-started"); 749 } 750 751 if (this.previewsContainer) { 752 file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); 753 file.previewTemplate = file.previewElement; // Backwards compatibility 754 755 this.previewsContainer.appendChild(file.previewElement); 756 for (var node of file.previewElement.querySelectorAll("[data-dz-name]")) { 757 node.textContent = file.name; 758 } 759 for (node of file.previewElement.querySelectorAll("[data-dz-size]")) { 760 node.innerHTML = this.filesize(file.size); 761 } 762 763 if (this.options.addRemoveLinks) { 764 file._removeLink = Dropzone.createElement(`<a class="dz-remove" href="javascript:undefined;" data-dz-remove>${this.options.dictRemoveFile}</a>`); 765 file.previewElement.appendChild(file._removeLink); 766 } 767 768 let removeFileEvent = e => { 769 e.preventDefault(); 770 e.stopPropagation(); 771 if (file.status === Dropzone.UPLOADING) { 772 return Dropzone.confirm(this.options.dictCancelUploadConfirmation, () => this.removeFile(file)); 773 } else { 774 if (this.options.dictRemoveFileConfirmation) { 775 return Dropzone.confirm(this.options.dictRemoveFileConfirmation, () => this.removeFile(file)); 776 } else { 777 return this.removeFile(file); 778 } 779 } 780 }; 781 782 for (let removeLink of file.previewElement.querySelectorAll("[data-dz-remove]")) { 783 removeLink.addEventListener("click", removeFileEvent); 784 } 785 } 786 }, 787 788 789 // Called whenever a file is removed. 790 removedfile(file) { 791 if (file.previewElement != null && file.previewElement.parentNode != null) { 792 file.previewElement.parentNode.removeChild(file.previewElement); 793 } 794 return this._updateMaxFilesReachedClass(); 795 }, 796 797 // Called when a thumbnail has been generated 798 // Receives `file` and `dataUrl` 799 thumbnail(file, dataUrl) { 800 if (file.previewElement) { 801 file.previewElement.classList.remove("dz-file-preview"); 802 for (let thumbnailElement of file.previewElement.querySelectorAll("[data-dz-thumbnail]")) { 803 thumbnailElement.alt = file.name; 804 thumbnailElement.src = dataUrl; 805 } 806 807 return setTimeout((() => file.previewElement.classList.add("dz-image-preview")), 1); 808 } 809 }, 810 811 // Called whenever an error occurs 812 // Receives `file` and `message` 813 error(file, message) { 814 if (file.previewElement) { 815 file.previewElement.classList.add("dz-error"); 816 if ((typeof message !== "String") && message.error) { 817 message = message.error; 818 } 819 for (let node of file.previewElement.querySelectorAll("[data-dz-errormessage]")) { 820 node.textContent = message; 821 } 822 } 823 }, 824 825 errormultiple() { 826 }, 827 828 // Called when a file gets processed. Since there is a cue, not all added 829 // files are processed immediately. 830 // Receives `file` 831 processing(file) { 832 if (file.previewElement) { 833 file.previewElement.classList.add("dz-processing"); 834 if (file._removeLink) { 835 return file._removeLink.innerHTML = this.options.dictCancelUpload; 836 } 837 } 838 }, 839 840 processingmultiple() { 841 }, 842 843 // Called whenever the upload progress gets updated. 844 // Receives `file`, `progress` (percentage 0-100) and `bytesSent`. 845 // To get the total number of bytes of the file, use `file.size` 846 uploadprogress(file, progress, bytesSent) { 847 if (file.previewElement) { 848 for (let node of file.previewElement.querySelectorAll("[data-dz-uploadprogress]")) { 849 node.nodeName === 'PROGRESS' ? 850 (node.value = progress) 851 : 852 (node.style.width = `${progress}%`) 853 } 854 } 855 }, 856 857 // Called whenever the total upload progress gets updated. 858 // Called with totalUploadProgress (0-100), totalBytes and totalBytesSent 859 totaluploadprogress() { 860 }, 861 862 // Called just before the file is sent. Gets the `xhr` object as second 863 // parameter, so you can modify it (for example to add a CSRF token) and a 864 // `formData` object to add additional information. 865 sending() { 866 }, 867 868 sendingmultiple() {}, 869 870 // When the complete upload is finished and successful 871 // Receives `file` 872 success(file) { 873 if (file.previewElement) { 874 return file.previewElement.classList.add("dz-success"); 875 } 876 }, 877 878 successmultiple() {}, 879 880 // When the upload is canceled. 881 canceled(file) {
882 return this.emit("error", file, this.options.dictUploadCanceled); 883 }, 884 885 canceledmultiple() {}, 886 887 // When the upload is finished, either with success or an error. 888 // Receives `file` 889 complete(file) { 890 if (file._removeLink) { 891 file._removeLink.innerHTML = this.options.dictRemoveFile; 892 } 893 if (file.previewElement) { 894 return file.previewElement.classList.add("dz-complete"); 895 } 896 }, 897 898 completemultiple() {}, 899 900 maxfilesexceeded() {}, 901 902 maxfilesreached() {}, 903 904 queuecomplete() {}, 905 906 addedfiles() {} 907 }; 908 909 910 this.prototype._thumbnailQueue = []; 911 this.prototype._processingThumbnail = false; 912 } 913 914 // global utility 915 static extend(target, ...objects) { 916 for (let object of objects) { 917 for (let key in object) { 918 let val = object[key]; 919 target[key] = val; 920 } 921 } 922 return target; 923 } 924 925 constructor(el, options) { 926 super(); 927 let fallback, left; 928 this.element = el; 929 // For backwards compatibility since the version was in the prototype previously 930 this.version = Dropzone.version; 931 932 this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); 933 934 this.clickableElements = []; 935 this.listeners = []; 936 this.files = []; // All files 937 938 if (typeof this.element === "string") { 939 this.element = document.querySelector(this.element); 940 } 941 942 // Not checking if instance of HTMLElement or Element since IE9 is extremely weird. 943 if (!this.element || (this.element.nodeType == null)) { 944 throw new Error("Invalid dropzone element."); 945 } 946 947 if (this.element.dropzone) { 948 throw new Error("Dropzone already attached."); 949 } 950 951 // Now add this dropzone to the instances. 952 Dropzone.instances.push(this); 953 954 // Put the dropzone inside the element itself. 955 this.element.dropzone = this; 956 957 let elementOptions = (left = Dropzone.optionsForElement(this.element)) != null ? left : {}; 958 959 this.options = Dropzone.extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); 960 961 // If the browser failed, just call the fallback and leave 962 if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { 963 return this.options.fallback.call(this); 964 } 965 966 // @options.url = @element.getAttribute "action" unless @options.url? 967 if (this.options.url == null) { 968 this.options.url = this.element.getAttribute("action"); 969 } 970 971 if (!this.options.url) { 972 throw new Error("No URL provided."); 973 } 974 975 if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { 976 throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); 977 } 978 979 if (this.options.uploadMultiple && this.options.chunking) { 980 throw new Error('You cannot set both: uploadMultiple and chunking.'); 981 } 982 983 // Backwards compatibility 984 if (this.options.acceptedMimeTypes) { 985 this.options.acceptedFiles = this.options.acceptedMimeTypes; 986 delete this.options.acceptedMimeTypes; 987 } 988 989 // Backwards compatibility 990 if (this.options.renameFilename != null) { 991 this.options.renameFile = file => this.options.renameFilename.call(this, file.name, file); 992 } 993 994 this.options.method = this.options.method.toUpperCase(); 995 996 if ((fallback = this.getExistingFallback()) && fallback.parentNode) { 997 // Remove the fallback 998 fallback.parentNode.removeChild(fallback); 999 } 1000 1001 // Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false 1002 if (this.options.previewsContainer !== false) { 1003 if (this.options.previewsContainer) { 1004 this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); 1005 } else { 1006 this.previewsContainer = this.element; 1007 } 1008 } 1009 1010 if (this.options.clickable) { 1011 if (this.options.clickable === true) { 1012 this.clickableElements = [this.element]; 1013 } else { 1014 this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); 1015 } 1016 } 1017 1018 1019 this.init(); 1020 } 1021 1022 1023 // Returns all files that have been accepted 1024 getAcceptedFiles() { 1025 return this.files.filter((file) => file.accepted).map((file) => file); 1026 } 1027 1028 // Returns all files that have been rejected
1029 // Not sure when that's going to be useful, but added for completeness. 1030 getRejectedFiles() { 1031 return this.files.filter((file) => !file.accepted).map((file) => file); 1032 } 1033 1034 getFilesWithStatus(status) { 1035 return this.files.filter((file) => file.status === status).map((file) => file); 1036 } 1037 1038 // Returns all files that are in the queue 1039 getQueuedFiles() { 1040 return this.getFilesWithStatus(Dropzone.QUEUED); 1041 } 1042 1043 getUploadingFiles() { 1044 return this.getFilesWithStatus(Dropzone.UPLOADING); 1045 } 1046 1047 getAddedFiles() { 1048 return this.getFilesWithStatus(Dropzone.ADDED); 1049 } 1050 1051 // Files that are either queued or uploading 1052 getActiveFiles() { 1053 return this.files.filter((file) => (file.status === Dropzone.UPLOADING) || (file.status === Dropzone.QUEUED)).map((file) => file); 1054 } 1055 1056 // The function that gets called when Dropzone is initialized. You 1057 // can (and should) setup event listeners inside this function. 1058 init() { 1059 // In case it isn't set already 1060 if (this.element.tagName === "form") { 1061 this.element.setAttribute("enctype", "multipart/form-data"); 1062 } 1063 1064 if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { 1065 this.element.appendChild(Dropzone.createElement(`<div class="dz-default dz-message"><span>${this.options.dictDefaultMessage}</span></div>`)); 1066 } 1067 1068 if (this.clickableElements.length) { 1069 let setupHiddenFileInput = () => { 1070 if (this.hiddenFileInput) { 1071 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); 1072 } 1073 this.hiddenFileInput = document.createElement("input"); 1074 this.hiddenFileInput.setAttribute("type", "file"); 1075 if ( this.element.ariaLabel ) { 1076 this.hiddenFileInput.setAttribute( "aria-label", this.element.ariaLabel ); // 3.26.5 1077 } 1078 if ((this.options.maxFiles === null) || (this.options.maxFiles > 1)) { 1079 this.hiddenFileInput.setAttribute("multiple", "multiple"); 1080 } 1081 this.hiddenFileInput.className = "dz-hidden-input"; 1082 1083 if (this.options.acceptedFiles !== null) { 1084 this.hiddenFileInput.setAttribute("accept", this.options.acceptedFiles); 1085 } 1086 if (this.options.capture !== null) { 1087 this.hiddenFileInput.setAttribute("capture", this.options.capture); 1088 } 1089 1090 // Not setting `display="none"` because some browsers don't accept clicks 1091 // on elements that aren't displayed. 1092 this.hiddenFileInput.style.visibility = "hidden"; 1093 this.hiddenFileInput.style.position = "absolute"; 1094 this.hiddenFileInput.style.top = "0"; 1095 this.hiddenFileInput.style.left = "0"; 1096 this.hiddenFileInput.style.height = "0"; 1097 this.hiddenFileInput.style.width = "0"; 1098 Dropzone.getElement(this.options.hiddenInputContainer, 'hiddenInputContainer').appendChild(this.hiddenFileInput); 1099 return this.hiddenFileInput.addEventListener("change", () => { 1100 let {files} = this.hiddenFileInput; 1101 if (files.length) { 1102 for (let file of files) { 1103 this.addFile(file); 1104 } 1105 } 1106 this.emit("addedfiles", files); 1107 return setupHiddenFileInput(); 1108 }); 1109 }; 1110 setupHiddenFileInput(); 1111 } 1112 1113 this.URL = window.URL !== null ? window.URL : window.webkitURL; 1114 1115 1116 // Setup all event listeners on the Dropzone object itself. 1117 // They're not in @setupEventListeners() because they shouldn't be removed 1118 // again when the dropzone gets disabled. 1119 for (let eventName of this.events) { 1120 this.on(eventName, this.options[eventName]); 1121 } 1122 1123 this.on("uploadprogress", () => this.updateTotalUploadProgress()); 1124 1125 this.on("removedfile", () => this.updateTotalUploadProgress()); 1126 1127 this.on("canceled", file => this.emit("complete", file)); 1128 1129 // Emit a `queuecomplete` event if all files finished uploading. 1130 this.on("complete", file => { 1131 if ((this.getAddedFiles().length === 0) && (this.getUploadingFiles().length === 0) && (this.getQueuedFiles().length === 0)) { 1132 // This needs to be deferred so that `queuecomplete` really triggers after `complete` 1133 return setTimeout((() =>
1133 this.emit("queuecomplete")), 0); 1134 } 1135 }); 1136 1137 1138 let noPropagation = function (e) { 1139 e.stopPropagation(); 1140 if (e.preventDefault) { 1141 return e.preventDefault(); 1142 } else { 1143 return e.returnValue = false; 1144 } 1145 }; 1146 1147 // Create the listeners 1148 this.listeners = [ 1149 { 1150 element: this.element, 1151 events: { 1152 "dragstart": e => { 1153 return this.emit("dragstart", e); 1154 }, 1155 "dragenter": e => { 1156 noPropagation(e); 1157 return this.emit("dragenter", e); 1158 }, 1159 "dragover": e => { 1160 // Makes it possible to drag files from chrome's download bar 1161 // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar 1162 // Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception) 1163 let efct; 1164 try { 1165 efct = e.dataTransfer.effectAllowed; 1166 } catch (error) { 1167 } 1168 e.dataTransfer.dropEffect = ('move' === efct) || ('linkMove' === efct) ? 'move' : 'copy'; 1169 1170 noPropagation(e); 1171 return this.emit("dragover", e); 1172 }, 1173 "dragleave": e => { 1174 return this.emit("dragleave", e); 1175 }, 1176 "drop": e => { 1177 noPropagation(e); 1178 return this.drop(e); 1179 }, 1180 "dragend": e => { 1181 return this.emit("dragend", e); 1182 } 1183 } 1184 1185 // This is disabled right now, because the browsers don't implement it properly. 1186 // "paste": (e) => 1187 // noPropagation e 1188 // @paste e 1189 } 1190 ]; 1191 1192 this.clickableElements.forEach(clickableElement => { 1193 return this.listeners.push({ 1194 element: clickableElement, 1195 events: { 1196 "click": evt => { 1197 // Only the actual dropzone or the message element should trigger file selection 1198 if ((clickableElement !== this.element) || ((evt.target === this.element) || Dropzone.elementInside(evt.target, this.element.querySelector(".dz-message")))) { 1199 this.hiddenFileInput.click(); // Forward the click 1200 } 1201 return true; 1202 } 1203 } 1204 }); 1205 }); 1206 1207 this.enable(); 1208 1209 return this.options.init.call(this); 1210 } 1211 1212 // Not fully tested yet 1213 destroy() { 1214 this.disable(); 1215 this.removeAllFiles(true); 1216 if (this.hiddenFileInput != null ? this.hiddenFileInput.parentNode : undefined) { 1217 this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); 1218 this.hiddenFileInput = null; 1219 } 1220 delete this.element.dropzone; 1221 return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); 1222 } 1223 1224 1225 updateTotalUploadProgress() { 1226 let totalUploadProgress; 1227 let totalBytesSent = 0; 1228 let totalBytes = 0; 1229 1230 let activeFiles = this.getActiveFiles(); 1231 1232 if (activeFiles.length) { 1233 for (let file of this.getActiveFiles()) { 1234 totalBytesSent += file.upload.bytesSent; 1235 totalBytes += file.upload.total; 1236 } 1237 totalUploadProgress = (100 * totalBytesSent) / totalBytes; 1238 } else { 1239 totalUploadProgress = 100; 1240 } 1241 1242 return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); 1243 } 1244 1245 // @options.paramName can be a function taking one parameter rather than a string. 1246 // A parameter name for a file is obtained simply by calling this with an index number. 1247 _getParamName(n) { 1248 if (typeof this.options.paramName === "function") { 1249 return this.options.paramName(n); 1250 } else { 1251 return `${this.options.paramName}${this.options.uploadMultiple ? `[${n}]` : ""}`; 1252 } 1253 } 1254 1255 // If @options.renameFile is a function, 1256 // the function will be used to rename the file.name before appending it to the formData 1257 _renameFile(file) { 1258 if (typeof this.options.renameFile !== "function") { 1259 return file.name; 1260 } 1261 return this.options.renameFile(file); 1262 } 1263 1264 // Returns a form that can be used as fallback if the browser does not support DragnDrop 1265 // 1266 // If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided. 1267 // This code has to pass in IE7 :( 1268 getFallbackForm() { 1269 let existingFallback, form; 1270 if (existingFallback = this.getExistingFallback()) { 1271 return existingFallback; 1272 } 1273 1274 let fieldsString = "<div class=\"dz-fallback\">"; 1275 if (this.options.dictFallbackText) { 1276 fieldsString += `<p>${this.options.dictFallbackText}</p>`; 1277 } 1278 fieldsString += `<input type="file" name="${this._getParamName(0)}" ${this.options.uploadMultiple ? 'multiple="multiple"' : undefined } /><input type="submit" value="Upload!"></div>`; 1279 1280 let fields = Dropzone.createElement(fieldsString); 1281 if (this.element.tagName !== "FORM") { 1282 form = Dropzone.createElement(`<form action="${this.options.url}" enctype="multipart/form-data" method="${this.options.method}"></form>`); 1283 form.appendChild(fields); 1284 } else { 1285 // Make sure that the enctype and method attributes are set properly 1286 this.element.setAttribute("enctype", "multipart/form-data"); 1287 this.element.setAttribute("method", this.options.method); 1288 } 1289 return form != null ? form : fields; 1290 } 1291 1292 1293 // Returns the fallback elements if they exist already 1294 // 1295 // This code has to pass in IE7 :( 1296 getExistingFallback() { 1297 let getFallback = function (elements) { 1298 for (let el of elements) { 1299 if (/(^| )fallback($| )/.test(el.className)) { 1300 return el; 1301 } 1302 } 1303 }; 1304 1305 for (let tagName of ["div", "form"]) { 1306 var fallback; 1307 if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { 1308 return fallback; 1309 } 1310 } 1311 } 1312 1313 1314 // Activates all listeners stored in @listeners 1315 setupEventListeners() { 1316 return this.listeners.map((elementListeners) => 1317 (() => { 1318 let result = []; 1319 for (let event in elementListeners.events) { 1320 let listener = elementListeners.events[event]; 1321 result.push(elementListeners.element.addEventListener(event, listener, false)); 1322 } 1323 return result; 1324 })()); 1325 } 1326 1327 1328 // Deactivates all listeners stored in @listeners 1329 removeEventListeners() { 1330 return this.listeners.map((elementListeners) => 1331 (() => { 1332 let result = []; 1333 for (let event in elementListeners.events) { 1334 let listener = elementListeners.events[event]; 1335 result.push(elementListeners.element.removeEventListener(event, listener, false)); 1336 } 1337 return result; 1338 })()); 1339 } 1340 1341 // Removes all event listeners and cancels all files in the queue or being processed. 1342 disable() {
1343 this.clickableElements.forEach(element => element.classList.remove("dz-clickable")); 1344 this.removeEventListeners(); 1345 this.disabled = true; 1346 1347 return this.files.map((file) => this.cancelUpload(file)); 1348 } 1349 1350 enable() { 1351 delete this.disabled; 1352 this.clickableElements.forEach(element => element.classList.add("dz-clickable")); 1353 return this.setupEventListeners(); 1354 } 1355 1356 // Returns a nicely formatted filesize 1357 filesize(size) { 1358 let selectedSize = 0; 1359 let selectedUnit = "b"; 1360 1361 if (size > 0) { 1362 let units = ['tb', 'gb', 'mb', 'kb', 'b']; 1363 1364 for (let i = 0; i < units.length; i++) { 1365 let unit = units[i]; 1366 let cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; 1367 1368 if (size >= cutoff) { 1369 selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); 1370 selectedUnit = unit; 1371 break; 1372 } 1373 } 1374 1375 selectedSize = Math.round(10 * selectedSize) / 10; // Cutting of digits 1376 } 1377 1378 return `<strong>${selectedSize}</strong> ${this.options.dictFileSizeUnits[selectedUnit]}`; 1379 } 1380 1381 1382 // Adds or removes the `dz-max-files-reached` class from the form. 1383 _updateMaxFilesReachedClass() { 1384 if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) { 1385 if (this.getAcceptedFiles().length === this.options.maxFiles) { 1386 this.emit('maxfilesreached', this.files); 1387 } 1388 return this.element.classList.add("dz-max-files-reached"); 1389 } else { 1390 return this.element.classList.remove("dz-max-files-reached"); 1391 } 1392 } 1393 1394 1395 drop(e) { 1396 if (!e.dataTransfer) { 1397 return; 1398 } 1399 this.emit("drop", e); 1400 1401 // Convert the FileList to an Array 1402 // This is necessary for IE11 1403 let files = []; 1404 for (let i = 0; i < e.dataTransfer.files.length; i++) { 1405 files[i] = e.dataTransfer.files[i]; 1406 } 1407 1408 this.emit("addedfiles", files); 1409 1410 // Even if it's a folder, files.length will contain the folders. 1411 if (files.length) { 1412 let {items} = e.dataTransfer; 1413 if (items && items.length && (items[0].webkitGetAsEntry != null)) { 1414 // The browser supports dropping of folders, so handle items instead of files 1415 this._addFilesFromItems(items); 1416 } else { 1417 this.handleFiles(files); 1418 } 1419 } 1420 } 1421 1422 paste(e) { 1423 if (__guard__(e != null ? e.clipboardData : undefined, x => x.items) == null) { 1424 return; 1425 } 1426 1427 this.emit("paste", e); 1428 let {items} = e.clipboardData; 1429 1430 if (items.length) { 1431 return this._addFilesFromItems(items); 1432 } 1433 } 1434 1435 1436 handleFiles(files) { 1437 for(let file of files) { 1438 this.addFile(file); 1439 } 1440 } 1441 1442 // When a folder is dropped (or files are pasted), items must be handled 1443 // instead of files. 1444 _addFilesFromItems(items) { 1445 return (() => { 1446 let result = []; 1447 for (let item of items) { 1448 var entry; 1449 if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { 1450 if (entry.isFile) { 1451 result.push(this.addFile(item.getAsFile())); 1452 } else if (entry.isDirectory) { 1453 // Append all files from that directory to files 1454 result.push(this._addFilesFromDirectory(entry, entry.name)); 1455 } else { 1456 result.push(undefined); 1457 } 1458 } else if (item.getAsFile != null) { 1459 if ((item.kind == null) || (item.kind === "file")) { 1460 result.push(this.addFile(item.getAsFile())); 1461 } else { 1462 result.push(undefined); 1463 } 1464 } else { 1465 result.push(undefined); 1466 } 1467 } 1468 return result; 1469 })(); 1470 } 1471 1472 1473 // Goes through the directory, and adds each file it finds recursively 1474 _addFilesFromDirectory(directory, path) { 1475 let dirReader = directory.createReader(); 1476 1477 let errorHandler = error => __guardMethod__(console, 'log', o => o.log(error)); 1478 1479 var readEntries = () => { 1480 return dirReader.readEntries(entries => { 1481 if (entries.length > 0) { 1482 for (let entry of entries) { 1483 if (entry.isFile) { 1484 entry.file(file => { 1485 if (this.options.ignoreHiddenFiles && (file.name.substring(0, 1) === '.')) { 1486 return; 1487 } 1488 file.fullPath = `${path}/${file.name}`; 1489 return this.addFile(file); 1490 }); 1491 } else if (entry.isDirectory) { 1492 this._addFilesFromDirectory(entry, `${path}/${entry.name}`); 1493 } 1494 } 1495 1496 // Recursively call readEntries() again, since browser only handle 1497 // the first 100 entries. 1498 // See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries 1499 readEntries(); 1500 } 1501 return null; 1502 } 1503 , errorHandler); 1504 }; 1505 1506 return readEntries(); 1507 } 1508 1509 1510 // If `done()` is called without argument the file is accepted 1511 // If you call it with an error message, the file is rejected 1512 // (This allows for asynchronous validation) 1513 // 1514 // This function checks the filesize, and if the file.type passes the 1515 // `acceptedFiles` check. 1516 accept(file, done) { 1517 if (this.options.maxFilesize && file.size > (this.options.maxFilesize * 1024 * 1024)) { 1518 return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); 1519 } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { 1520 return done(this.options.dictInvalidFileType); 1521 } else if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) { 1522 done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
1523 return this.emit("maxfilesexceeded", file); 1524 } else { 1525 return this.options.accept.call(this, file, done); 1526 } 1527 } 1528 1529 addFile(file) { 1530 file.upload = { 1531 uuid: Dropzone.uuidv4(), 1532 progress: 0, 1533 // Setting the total upload size to file.size for the beginning 1534 // It's actual different than the size to be transmitted. 1535 total: file.size, 1536 bytesSent: 0, 1537 filename: this._renameFile(file), 1538 chunked: this.options.chunking && (this.options.forceChunking || file.size > this.options.chunkSize), 1539 totalChunkCount: Math.ceil(file.size / this.options.chunkSize) 1540 }; 1541 this.files.push(file); 1542 1543 file.status = Dropzone.ADDED; 1544 1545 this.emit("addedfile", file); 1546 1547 this._enqueueThumbnail(file); 1548 1549 return this.accept(file, error => { 1550 if (error) { 1551 file.accepted = false; 1552 this._errorProcessing([file], error); // Will set the file.status 1553 } else { 1554 file.accepted = true; 1555 if (this.options.autoQueue) { 1556 this.enqueueFile(file); 1557 } // Will set .accepted = true 1558 } 1559 return this._updateMaxFilesReachedClass(); 1560 }); 1561 } 1562 1563 1564 // Wrapper for enqueueFile 1565 enqueueFiles(files) { 1566 for (let file of files) { 1567 this.enqueueFile(file); 1568 } 1569 return null; 1570 } 1571 1572 enqueueFile(file) { 1573 if ((file.status === Dropzone.ADDED) && (file.accepted === true)) { 1574 file.status = Dropzone.QUEUED; 1575 if (this.options.autoProcessQueue) { 1576 return setTimeout((() => this.processQueue()), 0); // Deferring the call 1577 } 1578 } else { 1579 throw new Error("This file can't be queued because it has already been processed or was rejected."); 1580 } 1581 } 1582 1583 _enqueueThumbnail(file) { 1584 if (this.options.createImageThumbnails && file.type.match(/image.*/) && (file.size <= (this.options.maxThumbnailFilesize * 1024 * 1024))) { 1585 this._thumbnailQueue.push(file); 1586 return setTimeout((() => this._processThumbnailQueue()), 0); // Deferring the call 1587 } 1588 } 1589 1590 _processThumbnailQueue() { 1591 if (this._processingThumbnail || (this._thumbnailQueue.length === 0)) { 1592 return; 1593 } 1594 1595 this._processingThumbnail = true; 1596 let file = this._thumbnailQueue.shift(); 1597 return this.createThumbnail(file, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.thumbnailMethod, true, dataUrl => { 1598 this.emit("thumbnail", file, dataUrl); 1599 this._processingThumbnail = false; 1600 return this._processThumbnailQueue(); 1601 }); 1602 } 1603 1604 1605 // Can be called by the user to remove a file 1606 removeFile(file) { 1607 if (file.status === Dropzone.UPLOADING) { 1608 this.cancelUpload(file); 1609 } 1610 this.files = without(this.files, file); 1611 1612 this.emit("removedfile", file); 1613 if (this.files.length === 0) { 1614 return this.emit("reset"); 1615 } 1616 } 1617 1618 // Removes all files that aren't currently processed from the list 1619 removeAllFiles(cancelIfNecessary) { 1620 // Create a copy of files since removeFile() changes the @files array. 1621 if (cancelIfNecessary == null) { 1622 cancelIfNecessary = false; 1623 } 1624 for (let file of this.files.slice()) { 1625 if ((file.status !== Dropzone.UPLOADING) || cancelIfNecessary) { 1626 this.removeFile(file); 1627 } 1628 } 1629 return null; 1630 } 1631 1632 // Resizes an image before it gets sent to the server. This function is the default behavior of 1633 // `options.transformFile` if `resizeWidth` or `resizeHeight` are set. The callback is invoked with 1634 // the resized blob. 1635 resizeImage(file, width, height, resizeMethod, callback) { 1636 return this.createThumbnail(file, width, height, resizeMethod, true, (dataUrl, canvas) => { 1637 if (canvas == null) { 1638 // The image has not been resized 1639 return callback(file); 1640 } else { 1641 let {resizeMimeType} = this.options; 1642 if (resizeMimeType == null) { 1643 resizeMimeType = file.type; 1644 } 1645 let resizedDataURL = canvas.toDataURL(resizeMimeType, this.options.resizeQuality); 1646 if ((resizeMimeType === 'image/jpeg') || (resizeMimeType === 'image/jpg')) { 1647 // Now add the original EXIF information 1648 resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL); 1649 } 1650 return callback(Dropzone.dataURItoBlob(resizedDataURL)); 1651 } 1652 }); 1653 } 1654 1655 createThumbnail(file, width, height, resizeMethod, fixOrientation, callback) { 1656 let fileReader = new FileReader; 1657 1658 fileReader.onload = () => { 1659 1660 file.dataURL = fileReader.result; 1661 1662 // Don't bother creating a thumbnail for SVG images since they're vector 1663 if (file.type === "image/svg+xml") { 1664 if (callback != null) { 1665 callback(fileReader.result); 1666 } 1667 return; 1668 } 1669 1670 return this.createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback); 1671 }; 1672
1673 return fileReader.readAsDataURL(file); 1674 } 1675 1676 createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback, crossOrigin) { 1677 // Not using `new Image` here because of a bug in latest Chrome versions. 1678 // See https://github.com/enyo/dropzone/pull/226 1679 let img = document.createElement("img"); 1680 1681 if (crossOrigin) { 1682 img.crossOrigin = crossOrigin; 1683 } 1684 1685 img.onload = () => { 1686 let loadExif = callback => callback(1); 1687 if ((typeof EXIF !== 'undefined' && EXIF !== null) && fixOrientation) { 1688 loadExif = callback => 1689 EXIF.getData(img, function () { 1690 return callback(EXIF.getTag(this, 'Orientation')); 1691 }) 1692 ; 1693 } 1694 1695 return loadExif(orientation => { 1696 file.width = img.width; 1697 file.height = img.height; 1698 1699 let resizeInfo = this.options.resize.call(this, file, width, height, resizeMethod); 1700 1701 let canvas = document.createElement("canvas"); 1702 let ctx = canvas.getContext("2d"); 1703 1704 canvas.width = resizeInfo.trgWidth; 1705 canvas.height = resizeInfo.trgHeight; 1706 1707 if (orientation > 4) { 1708 canvas.width = resizeInfo.trgHeight; 1709 canvas.height = resizeInfo.trgWidth; 1710 } 1711 1712 switch (orientation) { 1713 case 2: 1714 // horizontal flip 1715 ctx.translate(canvas.width, 0); 1716 ctx.scale(-1, 1); 1717 break; 1718 case 3: 1719 // 180° rotate left 1720 ctx.translate(canvas.width, canvas.height); 1721 ctx.rotate(Math.PI); 1722 break; 1723 case 4: 1724 // vertical flip 1725 ctx.translate(0, canvas.height); 1726 ctx.scale(1, -1); 1727 break; 1728 case 5: 1729 // vertical flip + 90 rotate right 1730 ctx.rotate(0.5 * Math.PI); 1731 ctx.scale(1, -1); 1732 break; 1733 case 6: 1734 // 90° rotate right 1735 ctx.rotate(0.5 * Math.PI); 1736 ctx.translate(0, -canvas.width); 1737 break; 1738 case 7: 1739 // horizontal flip + 90 rotate right 1740 ctx.rotate(0.5 * Math.PI); 1741 ctx.translate(canvas.height, -canvas.width); 1742 ctx.scale(-1, 1); 1743 break; 1744 case 8: 1745 // 90° rotate left 1746 ctx.rotate(-0.5 * Math.PI); 1747 ctx.translate(-canvas.height, 0); 1748 break; 1749 } 1750 1751 // This is a bugfix for iOS' scaling bug. 1752 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); 1753 1754 let thumbnail = canvas.toDataURL("image/png"); 1755 1756 if (callback != null) { 1757 return callback(thumbnail, canvas); 1758 } 1759 }); 1760 }; 1761 1762 if (callback != null) { 1763 img.onerror = callback; 1764 } 1765 1766 return img.src = file.dataURL; 1767 } 1768 1769 1770 // Goes through the queue and processes files if there aren't too many already. 1771 processQueue() { 1772 let {parallelUploads} = this.options; 1773 let processingLength = this.getUploadingFiles().length; 1774 let i = processingLength; 1775 1776 // There are already at least as many files uploading than should be 1777 if (processingLength >= parallelUploads) { 1778 return; 1779 } 1780 1781 let queuedFiles = this.getQueuedFiles(); 1782 1783 if (!(queuedFiles.length > 0)) { 1784 return; 1785 } 1786 1787 if (this.options.uploadMultiple) { 1788 // The files should be uploaded in one request 1789 return this.processFiles(queuedFiles.slice(0, (parallelUploads - processingLength))); 1790 } else { 1791 while (i < parallelUploads) { 1792 if (!queuedFiles.length) { 1793 return; 1794 } // Nothing left to process 1795 this.processFile(queuedFiles.shift()); 1796 i++; 1797 } 1798 } 1799 } 1800 1801 1802 // Wrapper for `processFiles` 1803 processFile(file) { 1804 return this.processFiles([file]); 1805 } 1806 1807 1808 // Loads the file, then calls finishedLoading() 1809 processFiles(files) { 1810 for (let file of files) { 1811 file.processing = true; // Backwards compatibility 1812 file.status = Dropzone.UPLOADING; 1813
1814 this.emit("processing", file); 1815 } 1816 1817 if (this.options.uploadMultiple) { 1818 this.emit("processingmultiple", files); 1819 } 1820 1821 return this.uploadFiles(files); 1822 } 1823 1824 1825 _getFilesWithXhr(xhr) { 1826 let files; 1827 return files = (this.files.filter((file) => file.xhr === xhr).map((file) => file)); 1828 } 1829 1830 1831 // Cancels the file upload and sets the status to CANCELED 1832 // **if** the file is actually being uploaded. 1833 // If it's still in the queue, the file is being removed from it and the status 1834 // set to CANCELED. 1835 cancelUpload(file) { 1836 if (file.status === Dropzone.UPLOADING) { 1837 let groupedFiles = this._getFilesWithXhr(file.xhr); 1838 for (let groupedFile of groupedFiles) { 1839 groupedFile.status = Dropzone.CANCELED; 1840 } 1841 if (typeof file.xhr !== 'undefined') { 1842 file.xhr.abort(); 1843 } 1844 for (let groupedFile of groupedFiles) { 1845 this.emit("canceled", groupedFile); 1846 } 1847 if (this.options.uploadMultiple) { 1848 this.emit("canceledmultiple", groupedFiles); 1849 } 1850 1851 } else if (file.status === Dropzone.ADDED || file.status === Dropzone.QUEUED) { 1852 file.status = Dropzone.CANCELED; 1853 this.emit("canceled", file); 1854 if (this.options.uploadMultiple) { 1855 this.emit("canceledmultiple", [file]); 1856 } 1857 } 1858 1859 if (this.options.autoProcessQueue) { 1860 return this.processQueue(); 1861 } 1862 } 1863 1864 resolveOption(option, ...args) { 1865 if (typeof option === 'function') { 1866 return option.apply(this, args); 1867 } 1868 return option; 1869 } 1870 1871 uploadFile(file) { return this.uploadFiles([file]); } 1872 1873 uploadFiles(files) { 1874 this._transformFiles(files, (transformedFiles) => { 1875 if (files[0].upload.chunked) { 1876 // This file should be sent in chunks! 1877 1878 // If the chunking option is set, we **know** that there can only be **one** file, since 1879 // uploadMultiple is not allowed with this option. 1880 let file = files[0]; 1881 let transformedFile = transformedFiles[0]; 1882 let startedChunkCount = 0; 1883 1884 file.upload.chunks = []; 1885 1886 let handleNextChunk = () => { 1887 let chunkIndex = 0; 1888 1889 // Find the next item in file.upload.chunks that is not defined yet. 1890 while (file.upload.chunks[chunkIndex] !== undefined) { 1891 chunkIndex++; 1892 } 1893 1894 // This means, that all chunks have already been started. 1895 if (chunkIndex >= file.upload.totalChunkCount) return; 1896 1897 startedChunkCount++; 1898 1899 let start = chunkIndex * this.options.chunkSize; 1900 let end = Math.min(start + this.options.chunkSize, file.size); 1901 1902 let dataBlock = { 1903 name: this._getParamName(0), 1904 data: transformedFile.webkitSlice ? transformedFile.webkitSlice(start, end) : transformedFile.slice(start, end), 1905 filename: file.upload.filename, 1906 chunkIndex: chunkIndex 1907 }; 1908 1909 file.upload.chunks[chunkIndex] = { 1910 file: file, 1911 index: chunkIndex, 1912 dataBlock: dataBlock, // In case we want to retry. 1913 status: Dropzone.UPLOADING, 1914 progress: 0, 1915 retries: 0 // The number of times this block has been retried. 1916 }; 1917 1918 1919 this._uploadData(files, [dataBlock]); 1920 }; 1921 1922 file.upload.finishedChunkUpload = (chunk) => { 1923 let allFinished = true; 1924 chunk.status = Dropzone.SUCCESS; 1925 1926 // Clear the data from the chunk 1927 chunk.dataBlock = null; 1928 // Leaving this reference to xhr intact here will cause memory leaks in some browsers 1929 chunk.xhr = null; 1930 1931 for (let i = 0; i < file.upload.totalChunkCount; i ++) { 1932 if (file.upload.chunks[i] === undefined) { 1933 return handleNextChunk(); 1934 } 1935 if (file.upload.chunks[i].status !== Dropzone.SUCCESS) { 1936 allFinished = false; 1937 } 1938 } 1939 1940 if (allFinished) { 1941 this.options.chunksUploaded(file, () => { 1942 this._finished(files, '', null); 1943 }); 1944 } 1945 }; 1946 1947 if (this.options.parallelChunkUploads) { 1948 for (let i = 0; i < file.upload.totalChunkCount; i++) { 1949 handleNextChunk(); 1950 } 1951 } 1952 else { 1953 handleNextChunk(); 1954 } 1955 } else { 1956 let dataBlocks = []; 1957 for (let i = 0; i < files.length; i++) { 1958 dataBlocks[i] = { 1959 name: this._getParamName(i), 1960 data: transformedFiles[i], 1961 filename: files[i].upload.filename 1962 }; 1963 } 1964 this._uploadData(files, dataBlocks); 1965 } 1966 }); 1967 } 1968 1969 /// Returns the right chunk for given file and xhr 1970 _getChunk(file, xhr) { 1971 for (let i = 0; i < file.upload.totalChunkCount; i++) { 1972 if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].xhr === xhr) { 1973 return file.upload.chunks[i]; 1974 } 1975 } 1976 } 1977 1978 // This function actually uploads the file(s) to the server. 1979 // If dataBlocks contains the actual data to upload (meaning, that this could either be transformed 1980 // files, or individual chunks for chunked upload). 1981 _uploadData(files, dataBlocks) { 1982 let xhr = new XMLHttpRequest(); 1983 1984 // Put the xhr object in the file objects to be able to reference it later. 1985 for (let file of files) { 1986 file.xhr = xhr; 1987 } 1988 if (files[0].upload.chunked) { 1989 // Put the xhr object in the right chunk object, so it can be associated later, and found with _getChunk 1990 files[0].upload.chunks[dataBlocks[0].chunkIndex].xhr = xhr; 1991 } 1992 1993 let method = this.resolveOption(this.options.method, files); 1994 let url = this.resolveOption(this.options.url, files); 1995 xhr.open(method, url, true); 1996 1997 // Setting the timeout after open because of IE11 issue: https://gitlab.com/meno/dropzone/issues/8 1998 xhr.timeout = this.resolveOption(this.options.timeout, files); 1999 2000 // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179 2001 xhr.withCredentials = !!this.options.withCredentials; 2002 2003 2004 xhr.onload = e => { 2005 this._finishedUploading(files, xhr, e); 2006 }; 2007 2008 xhr.onerror = () => { 2009 this._handleUploadError(files, xhr); 2010 }; 2011 2012 // Some browsers do not have the .upload property 2013 let progressObj = xhr.upload != null ? xhr.upload : xhr; 2014 progressObj.onprogress = (e) => this._updateFilesUploadProgress(files, xhr, e); 2015 2016 let headers = { 2017 "Accept": "application/json", 2018 "Cache-Control": "no-cache", 2019 "X-Requested-With": "XMLHttpRequest", 2020 }; 2021 2022 if (this.options.headers) { 2023 Dropzone.extend(headers, this.options.headers); 2024 } 2025 2026 for (let headerName in headers) { 2027 let headerValue = headers[headerName]; 2028 if (headerValue) { 2029 xhr.setRequestHeader(headerName, headerValue); 2030 } 2031 } 2032 2033 let formData = new FormData(); 2034 2035 // Adding all @options parameters 2036 if (this.options.params) { 2037 let additionalParams = this.options.params; 2038 if (typeof additionalParams === 'function') { 2039 additionalParams = additionalParams.call(this, files, xhr, files[0].upload.chunked ? this._getChunk(files[0], xhr) : null); 2040 } 2041 2042 for (let key in additionalParams) { 2043 let value = additionalParams[key]; 2044 formData.append(key, value); 2045 } 2046 } 2047 2048 // Let the user add additional data if necessary 2049 for (let file of files) {
2050 this.emit("sending", file, xhr, formData); 2051 } 2052 if (this.options.uploadMultiple) { 2053 this.emit("sendingmultiple", files, xhr, formData); 2054 } 2055 2056 2057 this._addFormElementData(formData); 2058 2059 2060 // Finally add the files 2061 // Has to be last because some servers (eg: S3) expect the file to be the last parameter 2062 for (let i = 0; i < dataBlocks.length; i++) { 2063 let dataBlock = dataBlocks[i]; 2064 formData.append(dataBlock.name, dataBlock.data, dataBlock.filename); 2065 } 2066 2067 this.submitRequest(xhr, formData, files); 2068 } 2069 2070 2071 // Transforms all files with this.options.transformFile and invokes done with the transformed files when done. 2072 _transformFiles(files, done) { 2073 let transformedFiles = []; 2074 // Clumsy way of handling asynchronous calls, until I get to add a proper Future library. 2075 let doneCounter = 0; 2076 for (let i = 0; i < files.length; i++) { 2077 this.options.transformFile.call(this, files[i], (transformedFile) => { 2078 transformedFiles[i] = transformedFile; 2079 if (++doneCounter === files.length) { 2080 done(transformedFiles); 2081 } 2082 }); 2083 } 2084 } 2085 2086 // Takes care of adding other input elements of the form to the AJAX request 2087 _addFormElementData(formData) { 2088 // Take care of other input elements 2089 if (this.element.tagName === "FORM") { 2090 for (let input of this.element.querySelectorAll("input, textarea, select, button")) { 2091 let inputName = input.getAttribute("name"); 2092 let inputType = input.getAttribute("type"); 2093 if (inputType) inputType = inputType.toLowerCase(); 2094 2095 // If the input doesn't have a name, we can't use it. 2096 if (typeof inputName === 'undefined' || inputName === null) continue; 2097 2098 if ((input.tagName === "SELECT") && input.hasAttribute("multiple")) { 2099 // Possibly multiple values 2100 for (let option of input.options) { 2101 if (option.selected) { 2102 formData.append(inputName, option.value); 2103 } 2104 } 2105 } else if (!inputType || (inputType !== "checkbox" && inputType !== "radio") || input.checked) { 2106 formData.append(inputName, input.value); 2107 } 2108 } 2109 } 2110 } 2111 2112 // Invoked when there is new progress information about given files. 2113 // If e is not provided, it is assumed that the upload is finished. 2114 _updateFilesUploadProgress(files, xhr, e) { 2115 let progress; 2116 if (typeof e !== 'undefined') { 2117 progress = (100 * e.loaded) / e.total; 2118 2119 if (files[0].upload.chunked) { 2120 let file = files[0]; 2121 // Since this is a chunked upload, we need to update the appropriate chunk progress. 2122 let chunk = this._getChunk(file, xhr); 2123 chunk.progress = progress; 2124 chunk.total = e.total; 2125 chunk.bytesSent = e.loaded; 2126 let fileProgress = 0, fileTotal, fileBytesSent; 2127 file.upload.progress = 0; 2128 file.upload.total = 0; 2129 file.upload.bytesSent = 0; 2130 for (let i = 0; i < file.upload.totalChunkCount; i++) { 2131 if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].progress !== undefined) { 2132 file.upload.progress += file.upload.chunks[i].progress; 2133 file.upload.total += file.upload.chunks[i].total; 2134 file.upload.bytesSent += file.upload.chunks[i].bytesSent; 2135 } 2136 } 2137 file.upload.progress = file.upload.progress / file.upload.totalChunkCount; 2138 } else { 2139 for (let file of files) { 2140 file.upload.progress = progress; 2141 file.upload.total = e.total; 2142 file.upload.bytesSent = e.loaded; 2143 } 2144 } 2145 for (let file of files) { 2146 this.emit("uploadprogress", file, file.upload.progress, file.upload.bytesSent); 2147 } 2148 } else { 2149 // Called when the file finished uploading 2150 2151 let allFilesFinished = true; 2152 2153 progress = 100; 2154 2155 for (let file of files) { 2156 if ((file.upload.progress !== 100) || (file.upload.bytesSent !== file.upload.total)) { 2157 allFilesFinished = false; 2158 } 2159 file.upload.progress = progress; 2160 file.upload.bytesSent = file.upload.total; 2161 } 2162 2163 // Nothing to do, all files already at 100% 2164 if (allFilesFinished) { 2165 return; 2166 } 2167 2168 for (let file of files) {
2169 this.emit("uploadprogress", file, progress, file.upload.bytesSent); 2170 } 2171 } 2172 2173 } 2174 2175 2176 _finishedUploading(files, xhr, e) { 2177 let response; 2178 2179 if (files[0].status === Dropzone.CANCELED) { 2180 return; 2181 } 2182 2183 if (xhr.readyState !== 4) { 2184 return; 2185 } 2186 2187 if ((xhr.responseType !== 'arraybuffer') && (xhr.responseType !== 'blob')) { 2188 response = xhr.responseText; 2189 2190 if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { 2191 try { 2192 response = JSON.parse(response); 2193 } catch (error) { 2194 e = error; 2195 response = "Invalid JSON response from server."; 2196 } 2197 } 2198 } 2199 2200 this._updateFilesUploadProgress(files); 2201 2202 if (!(200 <= xhr.status && xhr.status < 300)) { 2203 this._handleUploadError(files, xhr, response); 2204 } else { 2205 if (files[0].upload.chunked) { 2206 files[0].upload.finishedChunkUpload(this._getChunk(files[0], xhr)); 2207 } else { 2208 this._finished(files, response, e); 2209 } 2210 } 2211 } 2212 2213 _handleUploadError(files, xhr, response) { 2214 if (files[0].status === Dropzone.CANCELED) { 2215 return; 2216 } 2217 2218 if (files[0].upload.chunked && this.options.retryChunks) { 2219 let chunk = this._getChunk(files[0], xhr); 2220 if (chunk.retries++ < this.options.retryChunksLimit) { 2221 this._uploadData(files, [chunk.dataBlock]); 2222 return; 2223 } else { 2224 console.warn('Retried this chunk too often. Giving up.') 2225 } 2226 } 2227 2228 for (let file of files) { 2229 this._errorProcessing(files, response || this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr); 2230 } 2231 } 2232 2233 submitRequest(xhr, formData, files) { 2234 xhr.send(formData); 2235 } 2236 2237 // Called internally when processing is finished. 2238 // Individual callbacks have to be called in the appropriate sections. 2239 _finished(files, responseText, e) { 2240 for (let file of files) { 2241 file.status = Dropzone.SUCCESS; 2242 this.emit("success", file, responseText, e); 2243 this.emit("complete", file); 2244 } 2245 if (this.options.uploadMultiple) { 2246 this.emit("successmultiple", files, responseText, e); 2247 this.emit("completemultiple", files); 2248 } 2249 2250 if (this.options.autoProcessQueue) { 2251 return this.processQueue(); 2252 } 2253 } 2254 2255 // Called internally when processing is finished. 2256 // Individual callbacks have to be called in the appropriate sections. 2257 _errorProcessing(files, message, xhr) { 2258 for (let file of files) { 2259 file.status = Dropzone.ERROR; 2260 this.emit("error", file, message, xhr); 2261 this.emit("complete", file); 2262 } 2263 if (this.options.uploadMultiple) { 2264 this.emit("errormultiple", files, message, xhr); 2265 this.emit("completemultiple", files); 2266 } 2267 2268 if (this.options.autoProcessQueue) { 2269 return this.processQueue(); 2270 } 2271 } 2272 2273 static uuidv4() { 2274 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 2275 let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 2276 return v.toString(16); 2277 }); 2278 }; 2279} 2280Dropzone.initClass(); 2281 2282 2283Dropzone.version = "5.5.0"; 2284 2285 2286// This is a map of options for your different dropzones. Add configurations 2287// to this object for your different dropzone elemens. 2288// 2289// Example: 2290// 2291// Dropzone.options.myDropzoneElementId = { maxFilesize: 1 }; 2292// 2293// To disable autoDiscover for a specific element, you can set `false` as an option: 2294// 2295// Dropzone.options.myDisabledElementId = false; 2296// 2297// And in html: 2298// 2299// <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form> 2300Dropzone.options = {}; 2301 2302 2303// Returns the options for an element or undefined if none available. 2304Dropzone.optionsForElement = function (element) { 2305 // Get the `Dropzone.options.elementId` for this element if it exists 2306 if (element.getAttribute("id")) { 2307 return Dropzone.options[camelize(element.getAttribute("id"))]; 2308 } else { 2309 return undefined; 2310 } 2311}; 2312 2313 2314// Holds a list of all dropzone instances 2315Dropzone.instances = []; 2316 2317// Returns the dropzone for given element if any 2318Dropzone.forElement = function (element) { 2319 if (typeof element === "string") { 2320 element = document.querySelector(element); 2321 } 2322 if ((element != null ? element.dropzone : undefined) == null) { 2323 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."); 2324 } 2325 return element.dropzone; 2326}; 2327 2328 2329// Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements. 2330// 3.27.12, set to false to avoid potential issues with deferred JS executions 2331Dropzone.autoDiscover = false;
2332 2333// Looks for all .dropzone elements and creates a dropzone for them 2334Dropzone.discover = function () { 2335 let dropzones; 2336 if (document.querySelectorAll) { 2337 dropzones = document.querySelectorAll(".dropzone"); 2338 } else { 2339 dropzones = []; 2340 // IE :( 2341 let checkElements = elements => 2342 (() => { 2343 let result = []; 2344 for (let el of elements) { 2345 if (/(^| )dropzone($| )/.test(el.className)) { 2346 result.push(dropzones.push(el)); 2347 } else { 2348 result.push(undefined); 2349 } 2350 } 2351 return result; 2352 })() 2353 ; 2354 checkElements(document.getElementsByTagName("div")); 2355 checkElements(document.getElementsByTagName("form")); 2356 } 2357 2358 return (() => { 2359 let result = []; 2360 for (let dropzone of dropzones) { 2361 // Create a dropzone unless auto discover has been disabled for specific element 2362 if (Dropzone.optionsForElement(dropzone) !== false) { 2363 result.push(new Dropzone(dropzone)); 2364 } else { 2365 result.push(undefined); 2366 } 2367 } 2368 return result; 2369 })(); 2370}; 2371 2372 2373// Since the whole Drag'n'Drop API is pretty new, some browsers implement it, 2374// but not correctly. 2375// So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know. 2376// But what to do when browsers *theoretically* support an API, but crash 2377// when using it. 2378// 2379// This is a list of regular expressions tested against navigator.userAgent 2380// 2381// ** It should only be used on browser that *do* support the API, but 2382// incorrectly ** 2383// 2384Dropzone.blacklistedBrowsers = [ 2385 // The mac os and windows phone version of opera 12 seems to have a problem with the File drag'n'drop API. 2386 /opera.*(Macintosh|Windows Phone).*version\/12/i 2387]; 2388 2389 2390// Checks if the browser is supported 2391Dropzone.isBrowserSupported = function () { 2392 let capableBrowser = true; 2393 2394 if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { 2395 if (!("classList" in document.createElement("a"))) { 2396 capableBrowser = false; 2397 } else { 2398 // The browser supports the API, but may be blacklisted. 2399 for (let regex of Dropzone.blacklistedBrowsers) { 2400 if (regex.test(navigator.userAgent)) { 2401 capableBrowser = false; 2402 continue; 2403 } 2404 } 2405 } 2406 } else { 2407 capableBrowser = false; 2408 } 2409 2410 return capableBrowser; 2411}; 2412 2413Dropzone.dataURItoBlob = function (dataURI) { 2414 // convert base64 to raw binary data held in a string 2415 // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 2416 let byteString = atob(dataURI.split(',')[1]); 2417 2418 // separate out the mime component 2419 let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 2420 2421 // write the bytes of the string to an ArrayBuffer 2422 let ab = new ArrayBuffer(byteString.length); 2423 let ia = new Uint8Array(ab); 2424 for (let i = 0, end = byteString.length, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { 2425 ia[i] = byteString.charCodeAt(i); 2426 } 2427 2428 // write the ArrayBuffer to a blob 2429 return new Blob([ab], {type: mimeString}); 2430}; 2431 2432// Returns an array without the rejected item 2433const without = (list, rejectedItem) => list.filter((item) => item !== rejectedItem).map((item) => item); 2434 2435// abc-def_ghi -> abcDefGhi 2436const camelize = str => str.replace(/[\-_](\w)/g, match => match.charAt(1).toUpperCase()); 2437 2438// Creates an element from string 2439Dropzone.createElement = function (string) { 2440 let div = document.createElement("div"); 2441 div.innerHTML = string; 2442 return div.childNodes[0]; 2443}; 2444 2445// Tests if given element is inside (or simply is) the container 2446Dropzone.elementInside = function (element, container) { 2447 if (element === container) { 2448 return true; 2449 } // Coffeescript doesn't support do/while loops 2450 while ((element = element.parentNode)) { 2451 if (element === container) { 2452 return true; 2453 } 2454 } 2455 return false; 2456}; 2457 2458 2459Dropzone.getElement = function (el, name) { 2460 let element; 2461 if (typeof el === "string") { 2462 element = document.querySelector(el); 2463 } else if (el.nodeType != null) { 2464 element = el; 2465 } 2466 if (element == null) { 2467 throw new Error(`Invalid \`${name}\` option provided. Please provide a CSS selector or a plain HTML element.`); 2468 } 2469 return element; 2470}; 2471 2472 2473Dropzone.getElements = function (els, name) { 2474 let el, elements; 2475 if (els instanceof Array) { 2476 elements = []; 2477 try { 2478 for (el of els) { 2479 elements.push(this.getElement(el, name)); 2480 } 2481 } catch (e) { 2482 elements = null; 2483 } 2484 } else if (typeof els === "string") { 2485 elements = []; 2486 for (el of document.querySelectorAll(els)) { 2487 elements.push(el); 2488 } 2489 } else if (els.nodeType != null) { 2490 elements = [els]; 2491 } 2492 2493 if ((elements == null) || !elements.length) { 2494 throw new Error(`Invalid \`${name}\` option provided. Please provide a CSS selector, a plain HTML element or a list of those.`); 2495 } 2496 2497 return elements; 2498}; 2499 2500// Asks the user the question and calls accepted or rejected accordingly 2501// 2502// The default implementation just uses `window.confirm` and then calls the 2503// appropriate callback. 2504Dropzone.confirm = function (question, accepted, rejected) { 2505 if (window.confirm(question)) { 2506 return accepted(); 2507 } else if (rejected != null) { 2508 return rejected(); 2509 } 2510}; 2511 2512// Validates the mime type like this: 2513// 2514// https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept 2515Dropzone.isValidFile = function (file, acceptedFiles) { 2516 if (!acceptedFiles) { 2517 return true; 2518 } // If there are no accepted mime types, it's OK 2519 acceptedFiles = acceptedFiles.split(","); 2520 2521 let mimeType = file.type; 2522 let baseMimeType = mimeType.replace(/\/.*$/, ""); 2523 2524 for (let validType of acceptedFiles) { 2525 validType = validType.trim(); 2526 if (validType.charAt(0) === ".") { 2527 if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { 2528 return true; 2529 } 2530 } else if (/\/\*$/.test(validType)) { 2531 // This is something like a image/* mime type 2532 if (baseMimeType === validType.replace(/\/.*$/, "")) { 2533 return true; 2534 } 2535 } else { 2536 if (mimeType === validType) { 2537 return true; 2538 } 2539 } 2540 } 2541 2542 return false;
2543}; 2544 2545// Augment jQuery 2546if (typeof jQuery !== 'undefined' && jQuery !== null) { 2547 jQuery.fn.dropzone = function (options) { 2548 return this.each(function () { 2549 return new Dropzone(this, options); 2550 }); 2551 }; 2552} 2553 2554 2555if (typeof module !== 'undefined' && module !== null) { 2556 module.exports = Dropzone; 2557} else { 2558 window.Dropzone = Dropzone; 2559} 2560 2561 2562// Dropzone file status codes 2563Dropzone.ADDED = "added"; 2564 2565Dropzone.QUEUED = "queued"; 2566// For backwards compatibility. Now, if a file is accepted, it's either queued 2567// or uploading. 2568Dropzone.ACCEPTED = Dropzone.QUEUED; 2569 2570Dropzone.UPLOADING = "uploading"; 2571Dropzone.PROCESSING = Dropzone.UPLOADING; // alias 2572 2573Dropzone.CANCELED = "canceled"; 2574Dropzone.ERROR = "error"; 2575Dropzone.SUCCESS = "success"; 2576 2577 2578/* 2579 2580 Bugfix for iOS 6 and 7 2581 Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios 2582 based on the work of https://github.com/stomita/ios-imagefile-megapixel 2583 2584 */ 2585 2586// Detecting vertical squash in loaded image. 2587// Fixes a bug which squash image vertically while drawing into canvas for some images. 2588// This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel 2589let detectVerticalSquash = function (img) { 2590 let iw = img.naturalWidth; 2591 let ih = img.naturalHeight; 2592 let canvas = document.createElement("canvas"); 2593 canvas.width = 1; 2594 canvas.height = ih; 2595 let ctx = canvas.getContext("2d"); 2596 ctx.drawImage(img, 0, 0); 2597 let {data} = ctx.getImageData(1, 0, 1, ih); 2598 2599 2600 // search image edge pixel position in case it is squashed vertically. 2601 let sy = 0; 2602 let ey = ih; 2603 let py = ih; 2604 while (py > sy) { 2605 let alpha = data[((py - 1) * 4) + 3]; 2606 2607 if (alpha === 0) { 2608 ey = py; 2609 } else { 2610 sy = py; 2611 } 2612 2613 py = (ey + sy) >> 1; 2614 } 2615 let ratio = (py / ih); 2616 2617 if (ratio === 0) { 2618 return 1; 2619 } else { 2620 return ratio; 2621 } 2622}; 2623 2624// A replacement for context.drawImage 2625// (args are for source and destination). 2626var drawImageIOSFix = function (ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { 2627 let vertSquashRatio = detectVerticalSquash(img); 2628 return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); 2629}; 2630 2631 2632// Based on MinifyJpeg 2633// Source: http://www.perry.cz/files/ExifRestorer.js 2634// http://elicon.blog57.fc2.com/blog-entry-206.html 2635class ExifRestore { 2636 static initClass() { 2637 this.KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 2638 } 2639 2640 static encode64(input) { 2641 let output = ''; 2642 let chr1 = undefined; 2643 let chr2 = undefined; 2644 let chr3 = ''; 2645 let enc1 = undefined; 2646 let enc2 = undefined; 2647 let enc3 = undefined; 2648 let enc4 = ''; 2649 let i = 0; 2650 while (true) { 2651 chr1 = input[i++]; 2652 chr2 = input[i++]; 2653 chr3 = input[i++]; 2654 enc1 = chr1 >> 2; 2655 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 2656 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 2657 enc4 = chr3 & 63; 2658 if (isNaN(chr2)) { 2659 enc3 = (enc4 = 64); 2660 } else if (isNaN(chr3)) { 2661 enc4 = 64; 2662 } 2663 output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4); 2664 chr1 = (chr2 = (chr3 = '')); 2665 enc1 = (enc2 = (enc3 = (enc4 = ''))); 2666 if (!(i < input.length)) { 2667 break; 2668 } 2669 } 2670 return output; 2671 } 2672 2673 static restore(origFileBase64, resizedFileBase64) { 2674 if (!origFileBase64.match('data:image/jpeg;base64,')) { 2675 return resizedFileBase64; 2676 } 2677 let rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', '')); 2678 let segments = this.slice2Segments(rawImage); 2679 let image = this.exifManipulation(resizedFileBase64, segments); 2680 return `data:image/jpeg;base64,${this.encode64(image)}`; 2681 } 2682 2683 static exifManipulation(resizedFileBase64, segments) { 2684 let exifArray = this.getExifArray(segments); 2685 let newImageArray = this.insertExif(resizedFileBase64, exifArray); 2686 let aBuffer = new Uint8Array(newImageArray); 2687 return aBuffer; 2688 } 2689 2690 static getExifArray(segments) { 2691 let seg = undefined; 2692 let x = 0; 2693 while (x < segments.length) { 2694 seg = segments[x]; 2695 if ((seg[0] === 255) & (seg[1] === 225)) { 2696 return seg; 2697 } 2698 x++; 2699 } 2700 return []; 2701 } 2702 2703 static insertExif(resizedFileBase64, exifArray) { 2704 let imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''); 2705 let buf = this.decode64(imageData); 2706 let separatePoint = buf.indexOf(255, 3); 2707 let mae = buf.slice(0, separatePoint); 2708 let ato = buf.slice(separatePoint); 2709 let array = mae; 2710 array = array.concat(exifArray); 2711 array = array.concat(ato); 2712 return array; 2713 } 2714 2715 static slice2Segments(rawImageArray) { 2716 let head = 0; 2717 let segments = []; 2718 while (true) { 2719 var length; 2720 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 218)) { 2721 break; 2722 } 2723 if ((rawImageArray[head] === 255) & (rawImageArray[head + 1] === 216)) { 2724 head += 2; 2725 } else { 2726 length = (rawImageArray[head + 2] * 256) + rawImageArray[head + 3]; 2727 let endPoint = head + length + 2; 2728 let seg = rawImageArray.slice(head, endPoint); 2729 segments.push(seg); 2730 head = endPoint; 2731 } 2732 if (head > rawImageArray.length) { 2733 break; 2734 } 2735 } 2736 return segments; 2737 } 2738 2739 static decode64(input) { 2740 let output = '';
2741 let chr1 = undefined; 2742 let chr2 = undefined; 2743 let chr3 = ''; 2744 let enc1 = undefined; 2745 let enc2 = undefined; 2746 let enc3 = undefined; 2747 let enc4 = ''; 2748 let i = 0; 2749 let buf = []; 2750 // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 2751 let base64test = /[^A-Za-z0-9\+\/\=]/g; 2752 if (base64test.exec(input)) { 2753 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.'); 2754 } 2755 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); 2756 while (true) { 2757 enc1 = this.KEY_STR.indexOf(input.charAt(i++)); 2758 enc2 = this.KEY_STR.indexOf(input.charAt(i++)); 2759 enc3 = this.KEY_STR.indexOf(input.charAt(i++)); 2760 enc4 = this.KEY_STR.indexOf(input.charAt(i++)); 2761 chr1 = (enc1 << 2) | (enc2 >> 4); 2762 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 2763 chr3 = ((enc3 & 3) << 6) | enc4; 2764 buf.push(chr1); 2765 if (enc3 !== 64) { 2766 buf.push(chr2); 2767 } 2768 if (enc4 !== 64) { 2769 buf.push(chr3); 2770 } 2771 chr1 = (chr2 = (chr3 = '')); 2772 enc1 = (enc2 = (enc3 = (enc4 = ''))); 2773 if (!(i < input.length)) { 2774 break; 2775 } 2776 } 2777 return buf; 2778 } 2779} 2780ExifRestore.initClass(); 2781 2782 2783/* 2784 * contentloaded.js 2785 * 2786 * Author: Diego Perini (diego.perini at gmail.com) 2787 * Summary: cross-browser wrapper for DOMContentLoaded 2788 * Updated: 20101020 2789 * License: MIT 2790 * Version: 1.2 2791 * 2792 * URL: 2793 * http://javascript.nwbox.com/ContentLoaded/ 2794 * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 2795 */ 2796 2797// @win window reference 2798// @fn function reference 2799let contentLoaded = function (win, fn) { 2800 let done = false; 2801 let top = true; 2802 let doc = win.document; 2803 let root = doc.documentElement; 2804 let add = (doc.addEventListener ? "addEventListener" : "attachEvent"); 2805 let rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); 2806 let pre = (doc.addEventListener ? "" : "on"); 2807 var init = function (e) { 2808 if ((e.type === "readystatechange") && (doc.readyState !== "complete")) { 2809 return; 2810 } 2811 ((e.type === "load" ? win : doc))[rem](pre + e.type, init, false); 2812 if (!done && (done = true)) { 2813 return fn.call(win, e.type || e); 2814 } 2815 }; 2816 2817 var poll = function () { 2818 try { 2819 root.doScroll("left"); 2820 } catch (e) { 2821 setTimeout(poll, 50); 2822 return; 2823 } 2824 return init("poll"); 2825 }; 2826 2827 if (doc.readyState !== "complete") { 2828 if (doc.createEventObject && root.doScroll) { 2829 try { 2830 top = !win.frameElement; 2831 } catch (error) { 2832 } 2833 if (top) { 2834 poll(); 2835 } 2836 } 2837 doc[add](pre + "DOMContentLoaded", init, false); 2838 doc[add](pre + "readystatechange", init, false); 2839 return win[add](pre + "load", init, false); 2840 } 2841}; 2842 2843 2844// As a single function to be able to write tests. 2845Dropzone._autoDiscoverFunction = function () { 2846 if (Dropzone.autoDiscover) { 2847 return Dropzone.discover(); 2848 } 2849}; 2850contentLoaded(window, Dropzone._autoDiscoverFunction); 2851 2852function __guard__(value, transform) { 2853 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; 2854} 2855function __guardMethod__(obj, methodName, transform) { 2856 if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { 2857 return transform(obj, methodName); 2858 } else { 2859 return undefined; 2860 } 2861}
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.