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