diff --git a/README.md b/README.md index e096f358..ddca1478 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,8 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l *ngf-accept="'image/*'" // standard HTML accept attr, browser specific select popup window +ngf-allow-dir="boolean" // default true, allow dropping files only for Chrome webkit browser + +ngf-include-dir="boolean" //default false, include directories in the dropped file array. + //You can detect if they are directory or not by checking the type === 'directory'. +ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}" or "'myDragOverClass'" or "calcDragOverClass($event)" // default "dragover". drag over css class behaviour. could be a string, a function @@ -197,16 +199,19 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l +ngf-enable-firefox-paste="boolean" // *experimental* default false, enable firefox image paste by making element contenteditable ngf-resize="{width: 100, height: 100, quality: .8, type: 'image/jpeg', - ratio: '1:2', centerCrop: true, pattern='.jpg'}" + ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}" // resizes the image to the given width/height or ratio. Quality is optional between 0.1 and 1.0), // type is optional convert it to the given image type format. // centerCrop true will center crop the image if it does not fit within the given width/height or ratio. // centerCrop false (default) will not crop the image and will fit it within the given width/height or ratio // so the resulting image width (or height) could be less than given width (or height). // pattern is to resize only the files that their name or type matches the pattern similar to ngf-pattern. + // restoreExif boolean default true, will restore exif info on the resized image. ngf-resize-if="$width > 1000 || $height > 1000" or "resizeCondition($file, $width, $height)" // apply ngf-resize only if this function returns true. To filter specific images to be resized. - + ngf-validate-after-resize="false" // (defulat) boolean if true all validation will be run after + // the images are being resized, so any validation error before resize will be ignored. + //validations: ngf-max-files="10" // maximum number of files allowed to be selected or dropped, validate error name: maxFiles ngf-pattern="'.pdf,.jpg,video/*,!.jog'" // comma separated wildcard to filter file names and types allowed diff --git a/demo/src/main/webapp/js/FileAPI.min.js b/demo/src/main/webapp/js/FileAPI.min.js index d204b828..df6985af 100644 --- a/demo/src/main/webapp/js/FileAPI.min.js +++ b/demo/src/main/webapp/js/FileAPI.min.js @@ -1,4 +1,4 @@ -/*! 11.1.1 */ +/*! 11.2.0 */ /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git * FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF. */ diff --git a/demo/src/main/webapp/js/ng-file-upload-all.js b/demo/src/main/webapp/js/ng-file-upload-all.js index 5e4e9753..3242f3ee 100644 --- a/demo/src/main/webapp/js/ng-file-upload-all.js +++ b/demo/src/main/webapp/js/ng-file-upload-all.js @@ -3,7 +3,7 @@ * progress, resize, thumbnail, preview, validation and CORS * FileAPI Flash shim for old browsers not supporting FormData * @author Danial - * @version 11.1.1 + * @version 11.2.0 */ (function () { @@ -424,7 +424,7 @@ if (!window.FileReader) { * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, * progress, resize, thumbnail, preview, validation and CORS * @author Danial - * @version 11.1.1 + * @version 11.2.0 */ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { @@ -445,11 +445,11 @@ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { var ngFileUpload = angular.module('ngFileUpload', []); -ngFileUpload.version = '11.1.1'; +ngFileUpload.version = '11.2.0'; ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { var upload = this; - upload.promises = []; + upload.promisesCount = 0; this.isResumeSupported = function () { return window.Blob && (window.Blob instanceof Function) && new window.Blob().slice; @@ -478,7 +478,9 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, function getNotifyEvent(n) { if (config._start != null && resumeSupported) { return { - loaded: n.loaded + config._start, total: config._file.size, type: n.type, config: config, + loaded: n.loaded + config._start, + total: (config._file && config._file.size) || n.total, + type: n.type, config: config, lengthComputable: true, target: n.target }; } else { @@ -489,7 +491,7 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, if (!config.disableProgress) { config.headers.__setXHR_ = function () { return function (xhr) { - if (!xhr || !(xhr instanceof XMLHttpRequest)) return; + if (!xhr) return; config.__XHR = xhr; if (config.xhrFn) config.xhrFn(xhr); xhr.upload.addEventListener('progress', function (e) { @@ -509,18 +511,24 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, function uploadWithAngular() { $http(config).then(function (r) { - if (resumeSupported && config._chunkSize && !config._finished) { - notifyProgress({loaded: config._end, total: config._file.size, config: config, type: 'progress'}); - upload.upload(config, true); - } else { - if (config._finished) delete config._finished; - deferred.resolve(r); + if (resumeSupported && config._chunkSize && !config._finished && config._file) { + notifyProgress({ + loaded: config._end, + total: config._file && config._file.size, + config: config, type: 'progress' + } + ); + upload.upload(config, true); + } else { + if (config._finished) delete config._finished; + deferred.resolve(r); + } + }, function (e) { + deferred.reject(e); + }, function (n) { + deferred.notify(n); } - }, function (e) { - deferred.reject(e); - }, function (n) { - deferred.notify(n); - }); + ); } if (!resumeSupported) { @@ -594,18 +602,15 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, return promise; }; - upload.promises.push(promise); + upload.promisesCount++; promise['finally'](function () { - var i = upload.promises.indexOf(promise); - if (i > -1) { - upload.promises.splice(i, 1); - } + upload.promisesCount--; }); return promise; } - this.isUploadInProgress = function() { - return upload.promises.length > 0; + this.isUploadInProgress = function () { + return upload.promisesCount > 0; }; this.rename = function (file, name) { @@ -877,10 +882,10 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadE if (f.type.indexOf('image') === 0) { if (param.pattern && !upload.validatePattern(f, param.pattern)) return; var promise = upload.resize(f, param.width, param.height, param.quality, - param.type, param.ratio, param.centerCrop, function(width, height) { + param.type, param.ratio, param.centerCrop, function (width, height) { return upload.attrGetter('ngfResizeIf', attr, scope, {$width: width, $height: height, $file: f}); - }); + }, param.restoreExif !== false); promises.push(promise); promise.then(function (resizedFile) { files.splice(i, 1, resizedFile); @@ -967,36 +972,54 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadE if (keepResult.keep && (!newFiles || !newFiles.length)) return; - upload.attrGetter('ngfBeforeModelChange', attr, scope, {$files: files, + upload.attrGetter('ngfBeforeModelChange', attr, scope, { + $files: files, $file: files && files.length ? files[0] : null, $duplicateFiles: dupFiles, - $event: evt}); + $event: evt + }); + + var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope); + var invalids = []; + function separateInvalids() { + var valids = []; + angular.forEach(files, function (file) { + if (file.$error) { + invalids.push(file); + } else { + valids.push(file); + } + }); + files = valids; + } upload.validate(newFiles, ngModel, attr, scope).then(function () { if (noDelay) { update(files, [], newFiles, dupFiles, isSingleModel); } else { var options = upload.attrGetter('ngModelOptions', attr, scope); - if (!options || !options.allowInvalid) { - var valids = [], invalids = []; - angular.forEach(files, function (file) { - if (file.$error) { - invalids.push(file); - } else { - valids.push(file); - } - }); - files = valids; + if ((!options || !options.allowInvalid) && !validateAfterResize) { + separateInvalids(); } var fixOrientation = upload.emptyPromise(files); if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) { fixOrientation = applyExifRotations(files, attr, scope); } fixOrientation.then(function () { - resize(files, attr, scope).then(function () { + function updateModel() { $timeout(function () { update(files, invalids, newFiles, dupFiles, isSingleModel); }, options && options.debounce ? options.debounce.change || options.debounce : 0); + } + resize(files, attr, scope).then(function () { + if (validateAfterResize) { + upload.validate(files, ngModel, attr, scope).then(function () { + separateInvalids(); + updateModel(); + }); + } else { + updateModel(); + } }, function (e) { throw 'Could not resize files ' + e; }); @@ -2030,13 +2053,20 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadVa }); } - upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf) { + upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) { if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file); var deferred = $q.defer(); upload.dataUrl(file, true).then(function (url) { resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf) .then(function (dataUrl) { + if (file.type === 'image/jpeg' && restoreExif) { + try { + dataUrl = upload.restoreExif(url, dataUrl); + } catch (e) { + setTimeout(function () {throw e;}, 1); + } + } deferred.resolve(upload.dataUrltoBlob(dataUrl, file.name)); }, function (r) { if (r === 'resizeIf') { @@ -2273,13 +2303,18 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadVa function extractFiles(evt, allowDir, multiple) { var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE; var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE; + var includeDir = attrGetter('ngfIncludeDir', scope); var files = [], totalSize = 0; function traverseFileTree(entry, path) { var defer = $q.defer(); if (entry != null) { if (entry.isDirectory) { - var filePath = (path || '') + entry.name; - var promises = [upload.emptyPromise({name: entry.name, type: 'directory', path: filePath})]; + var promises = [upload.emptyPromise()]; + if (includeDir) { + var file = {type : 'directory'}; + file.name = file.path = (path || '') + entry.name + entry.name; + files.push(file); + } var dirReader = entry.createReader(); var entries = []; var readEntries = function () { @@ -2312,6 +2347,9 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadVa entry.file(function (file) { try { file.path = (path ? path : '') + file.name; + if (includeDir) { + file = upload.rename(file, file.path); + } files.push(file); totalSize += file.size; defer.resolve(); @@ -2368,7 +2406,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadVa $q.all(promises).then(function () { if (!multiple) { var i = 0; - while (files[i].type === 'directory') i++; + while (files.length && files[i].type === 'directory') i++; defer.resolve([files[i]]); } else { defer.resolve(files); @@ -2392,134 +2430,10 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadVa ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) { var upload = UploadResize; - function findEXIFinJPEG(file) { - var dataView = new DataView(file); - - if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) { - return 'Not a valid JPEG'; - } - - var offset = 2, - length = file.byteLength, - marker; - - while (offset < length) { - if (dataView.getUint8(offset) !== 0xFF) { - return 'Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset); - } - - marker = dataView.getUint8(offset + 1); - if (marker === 225) { - return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); - } else { - offset += 2 + dataView.getUint16(offset + 2); - } - } - } - - function readOrientation(file, tiffStart, dirStart, bigEnd) { - var entries = file.getUint16(dirStart, !bigEnd), - entryOffset, i; - - for (i = 0; i < entries; i++) { - entryOffset = dirStart + i * 12 + 2; - var val = file.getUint16(entryOffset, !bigEnd); - if (0x0112 === val) { - return readTagValue(file, entryOffset, tiffStart, bigEnd); - } - } - return null; - } - - function readTagValue(file, entryOffset, tiffStart, bigEnd) { - var numValues = file.getUint32(entryOffset + 4, !bigEnd), - valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, offset, vals, n; - - if (numValues === 1) { - return file.getUint16(entryOffset + 8, !bigEnd); - } else { - offset = numValues > 2 ? valueOffset : (entryOffset + 8); - vals = []; - for (n = 0; n < numValues; n++) { - vals[n] = file.getUint16(offset + 2 * n, !bigEnd); - } - return vals; - } - } - - function getStringFromDB(buffer, start, length) { - var outstr = ''; - for (var n = start; n < start + length; n++) { - outstr += String.fromCharCode(buffer.getUint8(n)); - } - return outstr; - } - - function readEXIFData(file, start) { - if (getStringFromDB(file, start, 4) !== 'Exif') { - return 'Not valid EXIF data! ' + getStringFromDB(file, start, 4); - } - - var bigEnd, - tiffOffset = start + 6; - - // test for TIFF validity and endianness - if (file.getUint16(tiffOffset) === 0x4949) { - bigEnd = false; - } else if (file.getUint16(tiffOffset) === 0x4D4D) { - bigEnd = true; - } else { - return 'Not valid TIFF data! (no 0x4949 or 0x4D4D)'; - } - - if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) { - return 'Not valid TIFF data! (no 0x002A)'; - } - - var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd); - - if (firstIFDOffset < 0x00000008) { - return 'Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd); - } - - return readOrientation(file, tiffOffset, tiffOffset + firstIFDOffset, bigEnd); - - } - upload.isExifSupported = function () { return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported(); }; - upload.orientation = function (file) { - if (file.$ngfOrientation != null) { - return upload.emptyPromise(file.$ngfOrientation); - } - var defer = $q.defer(); - var fileReader = new FileReader(); - fileReader.onload = function (e) { - var orientation; - try { - orientation = findEXIFinJPEG(e.target.result); - } catch (e) { - defer.reject(e); - return; - } - if (angular.isString(orientation)) { - defer.resolve(1); - } else { - file.$ngfOrientation = orientation; - defer.resolve(orientation); - } - }; - fileReader.onerror = function (e) { - defer.reject(e); - }; - - fileReader.readAsArrayBuffer(file); - return defer.promise; - }; - - function applyTransform(ctx, orientation, width, height) { switch (orientation) { case 2: @@ -2539,15 +2453,65 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize } } + upload.readOrientation = function(file) { + var defer = $q.defer(); + var reader = new FileReader(); + var slicedFile = file.slice(0, 64 * 1024); + reader.readAsArrayBuffer(slicedFile); + reader.onerror = function (e) { + return defer.reject(e); + }; + reader.onload = function (e) { + var view = new DataView(this.result); + if (view.getUint16(0, false) !== 0xFFD8) return defer.reject(); + var length = view.byteLength, + offset = 2; + while (offset < length) { + var marker = view.getUint16(offset, false); + offset += 2; + if (marker === 0xFFE1) { + if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.reject(); + var little = view.getUint16(offset += 6, false) === 0x4949; + offset += view.getUint32(offset + 4, little); + var tags = view.getUint16(offset, little); + offset += 2; + for (var i = 0; i < tags; i++) + if (view.getUint16(offset + (i * 12), little) === 0x0112) { + var orientation = view.getUint16(offset + (i * 12) + 8, little); + var result = {orientation: orientation}; + if (orientation >= 2 && orientation <= 8) { + view.setUint16(offset + (i * 12) + 8, 1, little); + result.fixedArrayBuffer = e.target.result; + } + return defer.resolve(result); + } + } else if ((marker & 0xFF00) !== 0xFF00) break; + else offset += view.getUint16(offset, false); + } + defer.reject(); + }; + return defer.promise; + }; + + function arrayBufferToBase64( buffer ) { + var binary = ''; + var bytes = new Uint8Array( buffer ); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + return window.btoa( binary ); + } + upload.applyExifRotation = function (file) { if (file.type.indexOf('image/jpeg') !== 0) { return upload.emptyPromise(file); } var deferred = $q.defer(); - upload.orientation(file).then(function (orientation) { - if (!orientation || orientation < 2 || orientation > 8) { - deferred.resolve(file); + upload.readOrientation(file).then(function (result) { + if (result.orientation < 2 || result.orientation > 8) { + return deferred.resolve(file); } upload.dataUrl(file, true).then(function (url) { var canvas = document.createElement('canvas'); @@ -2555,17 +2519,17 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize img.onload = function () { try { - canvas.width = orientation > 4 ? img.height : img.width; - canvas.height = orientation > 4 ? img.width : img.height; + canvas.width = result.orientation > 4 ? img.height : img.width; + canvas.height = result.orientation > 4 ? img.width : img.height; var ctx = canvas.getContext('2d'); - applyTransform(ctx, orientation, img.width, img.height); + applyTransform(ctx, result.orientation, img.width, img.height); ctx.drawImage(img, 0, 0); var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934); - dataUrl = restoreExif(url, dataUrl); + dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl); var blob = upload.dataUrltoBlob(dataUrl, file.name); deferred.resolve(blob); } catch (e) { - deferred.reject(e); + return deferred.reject(e); } }; img.onerror = function () { @@ -2581,8 +2545,7 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize return deferred.promise; }; - function restoreExif(orig, resized) { - + upload.restoreExif = function(orig, resized) { var ExifRestorer = {}; ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' + @@ -2626,11 +2589,11 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize }; ExifRestorer.restore = function (origFileBase64, resizedFileBase64) { - if (!origFileBase64.match('data:image/jpeg;base64,')) { - return resizedFileBase64; + if (origFileBase64.match('data:image/jpeg;base64,')) { + origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', ''); } - var rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', '')); + var rawImage = this.decode64(origFileBase64); var segments = this.slice2Segments(rawImage); var image = this.exifManipulation(resizedFileBase64, segments); @@ -2710,7 +2673,7 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize var base64test = /[^A-Za-z0-9\+\/\=]/g; if (base64test.exec(input)) { console.log('There were invalid base64 characters in the input text.\n' + - 'Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and "="\n' + + 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' + 'Expect errors in decoding.'); } input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); @@ -2743,7 +2706,7 @@ ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize }; return ExifRestorer.restore(orig, resized); //<= EXIF - } + }; return upload; }]); diff --git a/demo/src/main/webapp/js/ng-file-upload-all.min.js b/demo/src/main/webapp/js/ng-file-upload-all.min.js index 6e546634..f668804f 100644 --- a/demo/src/main/webapp/js/ng-file-upload-all.min.js +++ b/demo/src/main/webapp/js/ng-file-upload-all.min.js @@ -1,4 +1,3 @@ -/*! 11.1.1 */ -!function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}if(window.FileAPI||(window.FileAPI={}),!window.XMLHttpRequest)throw"AJAX is not supported. XMLHttpRequest is not defined.";if(FileAPI.shouldLoad=!window.FormData||FileAPI.forceLoad,FileAPI.shouldLoad){var c=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,d,e){c(this),this.__url=d;try{a.apply(this,[b,d,e])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",e]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,d){if("__setXHR_"===b){c(this);var e=d(this);e instanceof Function&&e(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=d,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var d=arguments[0],e={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){a&&angular.isString(a)&&-1!==a.indexOf("#2174")&&(a=null),c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0===d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0===d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},progress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};e.data={},e.files={};for(var f=0;f-1){e=h.substring(0,g+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=e),i.setAttribute("src",d||e+"FileAPI.min.js"),document.getElementsByTagName("head")[0].appendChild(i)}FileAPI.ngfFixIE=function(d,e,f){if(!b())throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';var g=function(){var b=e.parent();d.attr("disabled")?b&&b.removeClass("js-fileapi-wrapper"):(e.attr("__ngf_flash_")||(e.unbind("change"),e.unbind("click"),e.bind("change",function(a){h.apply(this,[a]),f.apply(this,[a])}),e.attr("__ngf_flash_","true")),b.addClass("js-fileapi-wrapper"),a(d)||(b.css("position","absolute").css("top",c(d[0]).top+"px").css("left",c(d[0]).left+"px").css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("filter","alpha(opacity=0)").css("display",d.css("display")).css("overflow","hidden").css("z-index","900000").css("visibility","visible"),e.css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("position","absolute").css("top","0px").css("left","0px")))};d.bind("mouseenter",g);var h=function(a){for(var b=FileAPI.getFiles(a),c=0;c-1&&f.promises.splice(a,1)}),k}function e(a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}var f=this;f.promises=[],this.isResumeSupported=function(){return window.Blob&&window.Blob instanceof Function&&(new window.Blob).slice};var g=this.isResumeSupported();this.isUploadInProgress=function(){return f.promises.length>0},this.rename=function(a,b){return a.ngfName=b,a},this.jsonBlob=function(a){null==a||angular.isString(a)||(a=JSON.stringify(a));var b=new window.Blob([a],{type:"application/json"});return b._ngfBlob=!0,b},this.json=function(a){return angular.toJson(a)},this.upload=function(a,b){function c(a){return null!=a&&(a instanceof window.Blob||a.flashId&&a.name&&a.size)}function h(b,c){if(b._ngfBlob)return b;if(a._file=a._file||b,null!=a._start&&g){a._end&&a._end>=b.size&&(a._finished=!0,a._end=b.size);var d=b.slice(a._start,a._end||b.size);return d.name=b.name,d.ngfName=b.ngfName,a._chunkSize&&(c.append("_chunkSize",a._chunkSize),c.append("_currentChunkSize",a._end-a._start),c.append("_chunkNumber",Math.floor(a._start/a._chunkSize)),c.append("_totalSize",a._file.size)),d}return b}function i(b,d,e){if(void 0!==d)if(angular.isDate(d)&&(d=d.toISOString()),angular.isString(d))b.append(e,d);else if(c(d)){var f=h(d,b),g=e.split(",");g[1]&&(f.ngfName=g[1].replace(/^\s+|\s+$/g,""),e=g[0]),a._fileKey=a._fileKey||e,b.append(e,f,f.ngfName||f.name)}else if(angular.isObject(d)){if(d.$$ngfCircularDetection)throw"ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: "+e;d.$$ngfCircularDetection=!0;try{for(var j in d)if(d.hasOwnProperty(j)&&"$$ngfCircularDetection"!==j){var k=null==a.objectKey?"[i]":a.objectKey;d.length&&parseInt(j)>-1&&(k=null==a.arrayKey?k:a.arrayKey),i(b,d[j],e+k.replace(/[ik]/g,j))}}finally{delete d.$$ngfCircularDetection}}else b.append(e,d)}function j(){a._chunkSize=f.translateScalars(a.resumeChunkSize),a._chunkSize=a._chunkSize?parseInt(a._chunkSize.toString()):null,a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?angular.isArray(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(b){var c,d=new window.FormData;b=b||a.fields||{},a.file&&(b.file=a.file);for(c in b)if(b.hasOwnProperty(c)){var e=b[c];a.formDataAppender?a.formDataAppender(d,c,e):i(d,e,c)}return d})}return b||(a=e(a)),a._isDigested||(a._isDigested=!0,j()),d(a)},this.http=function(b){return b=e(b),b.transformRequest=b.transformRequest||function(b){return window.ArrayBuffer&&b instanceof window.ArrayBuffer||b instanceof window.Blob?b:a.defaults.transformRequest[0].apply(this,arguments)},b._chunkSize=f.translateScalars(b.resumeChunkSize),b._chunkSize=b._chunkSize?parseInt(b._chunkSize.toString()):null,d(b)},this.translateScalars=function(a){if(angular.isString(a)){if(a.search(/kb/i)===a.length-2)return parseFloat(1e3*a.substring(0,a.length-2));if(a.search(/mb/i)===a.length-2)return parseFloat(1e6*a.substring(0,a.length-2));if(a.search(/gb/i)===a.length-2)return parseFloat(1e9*a.substring(0,a.length-2));if(a.search(/b/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/s/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/m/i)===a.length-1)return parseFloat(60*a.substring(0,a.length-1));if(a.search(/h/i)===a.length-1)return parseFloat(3600*a.substring(0,a.length-1))}return a},this.setDefaults=function(a){this.defaults=a||{}},this.defaults={},this.version=ngFileUpload.version}]),ngFileUpload.service("Upload",["$parse","$timeout","$compile","$q","UploadExif",function(a,b,c,d,e){function f(a,b,c){var e=[i.emptyPromise()];return angular.forEach(a,function(d,f){0===d.type.indexOf("image/jpeg")&&i.attrGetter("ngfFixOrientation",b,c,{$file:d})&&e.push(i.happyPromise(i.applyExifRotation(d),d).then(function(b){a.splice(f,1,b)}))}),d.all(e)}function g(a,b,c){var e=i.attrGetter("ngfResize",b,c);if(!(e&&angular.isObject(e)&&i.isResizeSupported()&&a.length))return i.emptyPromise();var f=[i.emptyPromise()];return angular.forEach(a,function(d,g){if(0===d.type.indexOf("image")){if(e.pattern&&!i.validatePattern(d,e.pattern))return;var h=i.resize(d,e.width,e.height,e.quality,e.type,e.ratio,e.centerCrop,function(a,e){return i.attrGetter("ngfResizeIf",b,c,{$width:a,$height:e,$file:d})});f.push(h),h.then(function(b){a.splice(g,1,b)},function(a){d.$error="resize",d.$errorParam=(a?(a.message?a.message:a)+": ":"")+(d&&d.name)})}}),d.all(f)}function h(a,b,c,d){var e=[],f=i.attrGetter("ngfKeep",c,d);if(f){var g=!1;if("distinct"===f||i.attrGetter("ngfKeepDistinct",c,d)===!0){var h=b.length;if(a)for(var j=0;jk;k++)if(a[j].name===b[k].name){e.push(a[j]);break}k===h&&(b.push(a[j]),g=!0)}a=b}else a=b.concat(a||[])}return{files:a,dupFiles:e,keep:f}}var i=e;return i.getAttrWithDefaults=function(a,b){if(null!=a[b])return a[b];var c=i.defaults[b];return null==c?c:angular.isString(c)?c:JSON.stringify(c)},i.attrGetter=function(b,c,d,e){var f=this.getAttrWithDefaults(c,b);if(!d)return f;try{return e?a(f)(d,e):a(f)(d)}catch(g){if(b.search(/min|max|pattern/i))return f;throw g}},i.shouldUpdateOn=function(a,b,c){var d=i.attrGetter("ngModelOptions",b,c);return d&&d.updateOn?d.updateOn.split(" ").indexOf(a)>-1:!0},i.emptyPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.resolve.apply(a,c)}),a.promise},i.rejectPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.reject.apply(a,c)}),a.promise},i.happyPromise=function(a,c){var e=d.defer();return a.then(function(a){e.resolve(a)},function(a){b(function(){throw a}),e.resolve(c)}),e.promise},i.updateModel=function(c,d,e,j,k,l,m){function n(f,g,h,k,m){var n=f&&f.length?f[0]:null;c&&(i.applyModelValidation(c,f),c.$setViewValue(m?n:f)),j&&a(j)(e,{$files:f,$file:n,$newFiles:h,$duplicateFiles:k,$invalidFiles:g,$event:l});var o=i.attrGetter("ngfModelInvalid",d);o&&b(function(){a(o).assign(e,g)}),b(function(){})}var o=k,p=c&&c.$modelValue&&(angular.isArray(c.$modelValue)?c.$modelValue:[c.$modelValue]);p=(p||d.$$ngfPrevFiles||[]).slice(0);var q=h(k,p,d,e);k=q.files;var r=q.dupFiles,s=!i.attrGetter("ngfMultiple",d,e)&&!i.attrGetter("multiple",d)&&!q.keep;if(d.$$ngfPrevFiles=k,!q.keep||o&&o.length){i.attrGetter("ngfBeforeModelChange",d,e,{$files:k,$file:k&&k.length?k[0]:null,$duplicateFiles:r,$event:l}),i.validate(o,c,d,e).then(function(){if(m)n(k,[],o,r,s);else{var a=i.attrGetter("ngModelOptions",d,e);if(!a||!a.allowInvalid){var c=[],h=[];angular.forEach(k,function(a){a.$error?h.push(a):c.push(a)}),k=c}var j=i.emptyPromise(k);i.attrGetter("ngfFixOrientation",d,e)&&i.isExifSupported()&&(j=f(k,d,e)),j.then(function(){g(k,d,e).then(function(){b(function(){n(k,h,o,r,s)},a&&a.debounce?a.debounce.change||a.debounce:0)},function(a){throw"Could not resize files "+a})})}});for(var t=p.length;t--;){var u=p[t];window.URL&&u.blobUrl&&(URL.revokeObjectURL(u.blobUrl),delete u.blobUrl)}}},i}]),ngFileUpload.directive("ngfSelect",["$parse","$timeout","$compile","Upload",function(a,b,c,d){function e(a){var b=a.match(/Android[^\d]*(\d+)\.(\d+)/);if(b&&b.length>2){var c=d.defaults.androidFixMinorVersion||4;return parseInt(b[1])<4||parseInt(b[1])===c&&parseInt(b[2])');n(a);var c=angular.element("");return c.css("visibility","hidden").css("position","absolute").css("overflow","hidden").css("width","0px").css("height","0px").css("border","none").css("margin","0px").css("padding","0px").attr("tabindex","-1"),g.push({el:b,ref:c}),document.body.appendChild(c.append(a)[0]),a}function p(c){if(b.attr("disabled"))return!1;if(!t("ngfSelectDisabled",a)){var d=q(c);if(null!=d)return d;r(c);try{k()||document.body.contains(w[0])||(g.push({el:b,ref:w.parent()}),document.body.appendChild(w[0].parent()),w.bind("change",m))}catch(f){}return e(navigator.userAgent)?setTimeout(function(){w[0].click()},0):w[0].click(),!1}}function q(a){var b=a.changedTouches||a.originalEvent&&a.originalEvent.changedTouches;if("touchstart"===a.type)return v=b?b[0].clientY:0,!0;if(a.stopPropagation(),a.preventDefault(),"touchend"===a.type){var c=b?b[0].clientY:0;if(Math.abs(c-v)>20)return!1}}function r(b){j.shouldUpdateOn("click",c,a)&&w.val()&&(w.val(null),j.updateModel(d,c,a,l(),null,b,!0))}function s(a){if(w&&!w.attr("__ngf_ie10_Fix_")){if(!w[0].parentNode)return void(w=null);a.preventDefault(),a.stopPropagation(),w.unbind("click");var b=w.clone();return w.replaceWith(b),w=b,w.attr("__ngf_ie10_Fix_","true"),w.bind("change",m),w.bind("click",s),w[0].click(),!1}w.removeAttr("__ngf_ie10_Fix_")}var t=function(a,b){return j.attrGetter(a,c,b)};j.registerModelChangeValidator(d,c,a);var u=[];u.push(a.$watch(t("ngfMultiple"),function(){w.attr("multiple",t("ngfMultiple",a))})),u.push(a.$watch(t("ngfCapture"),function(){w.attr("capture",t("ngfCapture",a))})),u.push(a.$watch(t("ngfAccept"),function(){w.attr("accept",t("ngfAccept",a))})),c.$observe("accept",function(){w.attr("accept",t("accept"))}),u.push(function(){c.$$observers&&delete c.$$observers.accept});var v=0,w=b;k()||(w=o()),w.bind("change",m),k()?b.bind("click",r):b.bind("click touchstart touchend",p),-1!==navigator.appVersion.indexOf("MSIE 10")&&w.bind("click",s),d&&d.$formatters.push(function(a){return(null==a||0===a.length)&&w.val()&&w.val(null),a}),a.$on("$destroy",function(){k()||w.parent().remove(),angular.forEach(u,function(a){a()})}),h(function(){for(var a=0;a2&&"/"===a[0]&&"/"===a[a.length-1])b=a.substring(1,a.length-1);else{var e=a.split(",");if(e.length>1)for(var f=0;f|:\\-]","g"),"\\$&")+"$",b=b.replace(/\\\*/g,".*").replace(/\\\?/g,"."))}return{regexp:b,excludes:c}}function e(a,b){null==b||a.$dirty||(a.$setDirty?a.$setDirty():a.$dirty=!0)}var f=a;return f.validatePattern=function(a,b){if(!b)return!0;var c=d(b),e=!0;if(c.regexp&&c.regexp.length){var f=new RegExp(c.regexp,"i");e=null!=a.type&&f.test(a.type)||null!=a.name&&f.test(a.name)}for(var g=c.excludes.length;g--;){var h=new RegExp(c.excludes[g],"i");e=e&&(null==a.type||h.test(a.type))&&(null==a.name||h.test(a.name))}return e},f.ratioToFloat=function(a){var b=a.toString(),c=b.search(/[x:]/i);return b=c>-1?parseFloat(b.substring(0,c))/parseFloat(b.substring(c+1)):parseFloat(b)},f.registerModelChangeValidator=function(a,b,c){a&&a.$formatters.push(function(d){a.$dirty&&f.validate(d,a,b,c).then(function(){f.applyModelValidation(a,d)})})},f.applyModelValidation=function(a,b){e(a,b),angular.forEach(a.$ngfValidations,function(b){a.$setValidity(b.name,b.valid)})},f.getValidationAttr=function(a,b,c,d,e){var g="ngf"+c[0].toUpperCase()+c.substr(1),h=f.attrGetter(g,a,b,{$file:e});if(null==h&&(h=f.attrGetter("ngfValidate",a,b,{$file:e}))){var i=(d||c).split(".");h=h[i[0]],i.length>1&&(h=h&&h[i[1]])}return h},f.validate=function(a,c,d,e){function g(b,g,h){if(a){for(var i=a.length,j=null;i--;){var k=a[i];if(k){var l=f.getValidationAttr(d,e,b,g,k);null!=l&&(h(k,l)||(k.$error=b,k.$errorParam=l,a.splice(i,1),j=!1))}}null!==j&&c.$ngfValidations.push({name:b,valid:j})}}function h(g,h,j,k,l){function m(a,b,c){null!=c?k(b,c).then(function(d){l(d,c)?a.resolve():(b.$error=g,b.$errorParam=c,a.reject())},function(){i("ngfValidateForce",{$file:b})?(b.$error=g,b.$errorParam=c,a.reject()):a.resolve()}):a.resolve()}var n=[f.emptyPromise()];return a?(a=void 0===a.length?[a]:a,angular.forEach(a,function(a){var c=b.defer();return n.push(c.promise),!j||null!=a.type&&0===a.type.search(j)?void("dimensions"===g&&null!=f.attrGetter("ngfDimensions",d)?f.imageDimensions(a).then(function(b){m(c,a,i("ngfDimensions",{$file:a,$width:b.width,$height:b.height}))},function(){c.reject()}):"duration"===g&&null!=f.attrGetter("ngfDuration",d)?f.mediaDuration(a).then(function(b){m(c,a,i("ngfDuration",{$file:a,$duration:b}))},function(){c.reject()}):m(c,a,f.getValidationAttr(d,e,g,h,a))):void c.resolve()}),b.all(n).then(function(){c.$ngfValidations.push({name:g,valid:!0})},function(){c.$ngfValidations.push({name:g,valid:!1})})):void 0}c=c||{},c.$ngfValidations=c.$ngfValidations||[],angular.forEach(c.$ngfValidations,function(a){a.valid=!0});var i=function(a,b){return f.attrGetter(a,d,e,b)};if(null==a||0===a.length)return f.emptyPromise(c);a=void 0===a.length?[a]:a.slice(0);var j=a.length;g("maxFiles",null,function(a,b){return b>=j}),g("pattern",null,f.validatePattern),g("minSize","size.min",function(a,b){return a.size>=f.translateScalars(b)}),g("maxSize","size.max",function(a,b){return a.size<=f.translateScalars(b)});var k=0;if(g("maxTotalSize",null,function(b,c){return k+=b.size,k>f.translateScalars(c)?(a.splice(0,a.length),!1):!0}),g("validateFn",null,function(a,b){return b===!0||null===b||""===b}),!a.length)return f.emptyPromise(c,c.$ngfValidations);var l=b.defer(),m=[];return m.push(f.happyPromise(h("maxHeight","height.max",/image/,this.imageDimensions,function(a,b){return a.height<=b}))),m.push(f.happyPromise(h("minHeight","height.min",/image/,this.imageDimensions,function(a,b){return a.height>=b}))),m.push(f.happyPromise(h("maxWidth","width.max",/image/,this.imageDimensions,function(a,b){return a.width<=b}))),m.push(f.happyPromise(h("minWidth","width.min",/image/,this.imageDimensions,function(a,b){return a.width>=b}))),m.push(f.happyPromise(h("dimensions",null,/image/,function(a,b){return f.emptyPromise(b)},function(a){return a}))),m.push(f.happyPromise(h("ratio",null,/image/,this.imageDimensions,function(a,b){for(var c=b.toString().split(","),d=!1,e=0;e-1e-4}))),m.push(f.happyPromise(h("maxDuration","duration.max",/audio|video/,this.mediaDuration,function(a,b){return a<=f.translateScalars(b)}))),m.push(f.happyPromise(h("minDuration","duration.min",/audio|video/,this.mediaDuration,function(a,b){return a>=f.translateScalars(b)}))),m.push(f.happyPromise(h("duration",null,/audio|video/,function(a,b){return f.emptyPromise(b)},function(a){return a}))),m.push(f.happyPromise(h("validateAsyncFn",null,null,function(a,b){return b},function(a){return a===!0||null===a||""===a}))),b.all(m).then(function(){l.resolve(c,c.$ngfValidations)})},f.imageDimensions=function(a){if(a.$ngfWidth&&a.$ngfHeight){var d=b.defer();return c(function(){d.resolve({width:a.$ngfWidth,height:a.$ngfHeight})}),d.promise}if(a.$ngfDimensionPromise)return a.$ngfDimensionPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("image")?void e.reject("not image"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].clientWidth,c=h[0].clientHeight;h.remove(),a.$ngfWidth=b,a.$ngfHeight=c,e.resolve({width:b,height:c})}function f(){h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].clientWidth?d():i>10?f():g())},1e3)}var h=angular.element("").attr("src",b).css("visibility","hidden").css("position","fixed");h.on("load",d),h.on("error",f);var i=0;g(),angular.element(document.getElementsByTagName("body")[0]).append(h)},function(){e.reject("load error")})}),a.$ngfDimensionPromise=e.promise,a.$ngfDimensionPromise["finally"](function(){delete a.$ngfDimensionPromise}),a.$ngfDimensionPromise},f.mediaDuration=function(a){if(a.$ngfDuration){var d=b.defer();return c(function(){d.resolve(a.$ngfDuration)}),d.promise}if(a.$ngfDurationPromise)return a.$ngfDurationPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("audio")&&0!==a.type.indexOf("video")?void e.reject("not media"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].duration;a.$ngfDuration=b,h.remove(),e.resolve(b)}function f(){h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].duration?d():i>10?f():g())},1e3)}var h=angular.element(0===a.type.indexOf("audio")?"