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