[
  {
    "path": ".gitignore",
    "content": "node_modules\n.tmp\nbower_components\n.settings\n.metadata\n*.war\nRemoteSystemsTempFiles/\n.DS_Store\n.DS_Store?\nehthumbs.db\nThumbs.db\n.Spotlight-V100\n.Trashes\nclasses/\n._*\n*.jar\nrelease-local.sh\nnpm-debug.log\n.idea/\ntarget/\n*.iml\n\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n  \"node\": true,\n  \"browser\": true,\n  \"esnext\": true,\n  \"camelcase\": true,\n  \"curly\": false,\n  \"eqeqeq\": true,\n  \"eqnull\": true,\n  \"immed\": true,\n  \"indent\": 4,\n  \"latedef\": true,\n  \"newcap\": true,\n  \"noarg\": true,\n  \"quotmark\": \"single\",\n  \"undef\": true,\n  \"unused\": true,\n  \"trailing\": true,\n  \"smarttabs\": true,\n  \"jquery\": true,\n  \"evil\": true,\n  \"globals\": {\n    \"angular\":false,\n    \"FileAPI\":false,\n    \"ngFileUpload\":true,\n    \"FormData\":true,\n    \"Blob\":true,\n    \"ActiveXObject\":false,\n    \"$document\":false\n  }\n}\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "'use strict';\n\nmodule.exports = function (grunt) {\n  // Load grunt tasks automatically\n  require('load-grunt-tasks')(grunt);\n\n  grunt.initConfig({\n    pkg: grunt.file.readJSON('package.json'),\n    concat: {\n      all: {\n        options: {\n          process: function (content) {\n            return grunt.template.process(content);\n          }\n        },\n        files: {\n          'dist/ng-file-upload.js': ['src/upload.js', 'src/model.js', 'src/select.js', 'src/data-url.js',\n            'src/validate.js', 'src/resize.js', 'src/drop.js', 'src/exif.js'],\n          'dist/ng-file-upload-shim.js': ['src/shim-upload.js', 'src/shim-elem.js', 'src/shim-filereader.js'],\n          'dist/ng-file-upload-all.js': ['dist/ng-file-upload-shim.js', 'dist/ng-file-upload.js']\n        }\n      }\n    },\n    uglify: {\n      options: {\n        preserveComments: 'some',\n        banner: '/*! <%= pkg.version %> */\\n'\n      },\n\n      build: {\n        files: [{\n          'dist/ng-file-upload.min.js': 'dist/ng-file-upload.js',\n          'dist/ng-file-upload-shim.min.js': 'dist/ng-file-upload-shim.js',\n          'dist/ng-file-upload-all.min.js': 'dist/ng-file-upload-all.js',\n          'dist/FileAPI.min.js': 'dist/FileAPI.js'\n        }]\n      }\n    },\n    copy: {\n      build: {\n        files: [{\n          expand: true,\n          cwd: 'dist/',\n          src: '*',\n          dest: 'demo/src/main/webapp/js/',\n          flatten: true,\n          filter: 'isFile'\n        }]\n      },\n      fileapi: {\n        files: {\n          'dist/FileAPI.flash.swf': 'src/FileAPI.flash.swf',\n          'dist/FileAPI.js': 'src/FileAPI.js'\n        }\n      },\n      bower: {\n        files: [{\n          expand: true,\n          cwd: 'dist/',\n          src: '*',\n          dest: '../angular-file-upload-bower/',\n          flatten: true,\n          filter: 'isFile'\n        }, {\n          expand: true,\n          cwd: 'dist/',\n          src: '*',\n          dest: '../angular-file-upload-shim-bower/',\n          flatten: true,\n          filter: 'isFile'\n        }]\n      }\n    },\n    serve: {\n      options: {\n        port: 9000\n      },\n      'path': 'demo/src/main/webapp'\n    },\n    watch: {\n      js: {\n        files: ['src/{,*/}*.js'],\n        tasks: ['jshint:all', 'concat:all', 'copy:build']\n      }\n    },\n    jshint: {\n      options: {\n        jshintrc: '.jshintrc',\n        reporter: require('jshint-stylish')\n      },\n      all: [\n        'Gruntfile.js',\n        'src/{,*/}*.js',\n        '!src/FileAPI*.*',\n        'test/spec/{,*/}*.js'\n      ]\n    },\n    replace: {\n      version: {\n        src: ['nuget/Package.nuspec', '../angular-file-upload-bower/package.js'],\n        overwrite: true,\n        replacements: [{\n          from: /\"version\" *: *\".*\"/g,\n          to: '\"version\": \"<%= pkg.version %>\"'\n        }, {\n          from: /<version>.*<\\/version>/g,\n          to: '<version><%= pkg.version %></version>'\n        }]\n      }\n    },\n    clean: {\n      dist: {\n        files: [{\n          dot: true,\n          src: [\n            'dist',\n            '!dist/.git*'\n          ]\n        }]\n      }\n    }\n  });\n\n  grunt.registerTask('dev', ['jshint:all', 'concat:all', 'uglify', 'copy:build', 'watch']);\n  grunt.registerTask('default', ['jshint:all', 'clean:dist', 'concat:all',\n    'copy:fileapi', 'uglify', 'copy:build', 'copy:bower', 'replace:version']);\n\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 danialfarid\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm version](https://badge.fury.io/js/ng-file-upload.svg)](http://badge.fury.io/js/ng-file-upload)\n[![Downloads](http://img.shields.io/npm/dm/ng-file-upload.svg)](https://npmjs.org/package/ng-file-upload)\n[![Issue Stats](http://issuestats.com/github/danialfarid/ng-file-upload/badge/pr)](http://issuestats.com/github/danialfarid/ng-file-upload)\n[![Issue Stats](http://issuestats.com/github/danialfarid/ng-file-upload/badge/issue)](http://issuestats.com/github/danialfarid/ng-file-upload)<br/>\n[![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=danial%2efarid%40gmail%2ecom&lc=CA&item_name=ng%2dfile%2dupload&item_number=ng%2dfile%2dupload&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)\n[![Gratipay donate button](https://img.shields.io/gratipay/danialfarid.svg?label=donate)](https://gratipay.com/ng-file-upload/)\n\nng-file-upload\n===================\n\nLightweight Angular directive to upload files.\n\n**See the <a href=\"https://angular-file-upload.appspot.com/\" target=\"_blank\">DEMO</a> page.** Reference docs [here](https://github.com/danialfarid/ng-file-upload/blob/master/README.md#full-reference)\n\n**Migration notes**: [version 3.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.0.0) [version 3.1.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.1.0) [version 3.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.2.3) [version 4.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/4.0.0) [version 5.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/5.0.0) [version 6.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/6.0.0) [version 6.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/6.2.0) [version 7.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/7.0.0) [version 7.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/7.2.0) [version 8.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/8.0.1) [version 9.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/9.0.0) [version 10.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/10.0.0) [version 11.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/11.0.0) [version 12.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.0.0) [version 12.1.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.1.0) [version 12.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.2.3)\n\n\n\nAsk questions on [StackOverflow](http://stackoverflow.com/) under the [ng-file-upload](http://stackoverflow.com/tags/ng-file-upload/) tag.<br/>\nFor bug report or feature request please search through existing [issues](https://github.com/danialfarid/ng-file-upload/issues) first then open a new one  [here](https://github.com/danialfarid/ng-file-upload/issues/new). For faster response provide steps to reproduce/versions with a jsfiddle link. If you need support for your company contact [me](mailto:danial.farid@gmail.com).<br/>\nIf you like this plugin give it a thumbs up at [ngmodules](http://ngmodules.org/modules/ng-file-upload) or get me a <a target=\"_blank\" href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=danial%2efarid%40gmail%2ecom&lc=CA&item_name=ng%2dfile%2dupload&item_number=ng%2dfile%2dupload&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted\">cup of tea <img src=\"https://angular-file-upload.appspot.com/img/tea.png\" width=\"40\" height=\"24\" title=\"Icon made by Freepik.com\"></a>. Contributions are welcomed.\n\n\nTable of Content:\n* [Features](#features)\n* [Install](#install) ([Manual](#manual), [Bower](#bower), [NuGet](#nuget), [NPM](#npm))\n* [Usage](#usage)\n* [Old Browsers](#old_browsers)\n* [Server Side](#server)\n  * [Samples](#server) ([Java](#java), [Spring](#spring), [Node.js](#node), [Rails](#rails), [PHP](#php), [.Net](#net))\n  * [CORS](#cors)\n  * [Amazon S3 Upload](#s3)\n\n## <a name=\"features\"></a> Features\n* file upload progress, cancel/abort\n* file drag and drop (html5 only)\n* image paste from clipboard and drag and drop from browser pages (html5 only).\n* image resize and center crop (native) and user controlled crop through [ngImgCrop](https://github.com/alexk111/ngImgCrop). See [crop sample](http://jsfiddle.net/danialfarid/xxo3sk41/590/) (html5 only)\n* orientation fix for jpeg image files with exif orientation data\n* resumable uploads: pause/resume upload (html5 only)\n* native validation support for file type/size, image width/height/aspect ratio, video/audio duration, and `ng-required` with pluggable custom sync or async validations.\n* show thumbnail or preview of selected images/audio/videos\n* supports CORS and direct upload of file's binary data using `Upload.$http()`\n* plenty of sample server side code, available on nuget\n* on demand flash [FileAPI](https://github.com/mailru/FileAPI) shim loading no extra load for html5 browsers.\n* HTML5 FileReader.readAsDataURL shim for IE8-9\n* available on [npm](https://www.npmjs.com/package/ng-file-upload), [bower](https://libraries.io/bower/ng-file-upload), [meteor](https://atmospherejs.com/danialf/ng-file-upload), [nuget](https://www.nuget.org/packages/angular-file-upload)\n\n## <a name=\"install\"></a> Install\n\n* <a name=\"manual\"></a>**Manual**: download latest from [here](https://github.com/danialfarid/ng-file-upload-bower/releases/latest)\n* <a name=\"bower\"></a>**Bower**:\n  * `bower install ng-file-upload-shim --save`(for non html5 suppport)\n  * `bower install ng-file-upload --save`\n* <a name=\"nuget\"></a>**NuGet**: `PM> Install-Package angular-file-upload` (thanks to [Georgios Diamantopoulos](https://github.com/georgiosd))\n* <a name=\"npm\"></a>**NPM**: `npm install ng-file-upload`\n```html\n<script src=\"angular(.min).js\"></script>\n<script src=\"ng-file-upload-shim(.min).js\"></script> <!-- for no html5 browsers support -->\n<script src=\"ng-file-upload(.min).js\"></script>\n```\n\n## <a name=\"usage\"></a> Usage\n\n### Samples:\n* Upload with form submit and validations: [http://jsfiddle.net/danialfarid/maqbzv15/1118/](http://jsfiddle.net/danialfarid/maqbzv15/1118/)\n* Upload multiple files one by one on file select:\n[http://jsfiddle.net/danialfarid/2vq88rfs/136/](http://jsfiddle.net/danialfarid/2vq88rfs/136/)\n* Upload multiple files in one request on file select (html5 only):\n[http://jsfiddle.net/danialfarid/huhjo9jm/5/](http://jsfiddle.net/danialfarid/huhjo9jm/5/)\n* Upload single file on file select:\n[http://jsfiddle.net/danialfarid/0mz6ff9o/135/](http://jsfiddle.net/danialfarid/0mz6ff9o/135/)\n* Drop and upload with $watch:\n[http://jsfiddle.net/danialfarid/s8kc7wg0/400/](http://jsfiddle.net/danialfarid/s8kc7wg0/400/)\n* Image Crop and Upload\n[http://jsfiddle.net/danialfarid/xxo3sk41/590/](http://jsfiddle.net/danialfarid/xxo3sk41/590/)\n```html\n<script src=\"angular.min.js\"></script>\n<!-- shim is needed to support non-HTML5 FormData browsers (IE8-9)-->\n<script src=\"ng-file-upload-shim.min.js\"></script>\n<script src=\"ng-file-upload.min.js\"></script>\n\nUpload on form submit or button click\n<form ng-app=\"fileUpload\" ng-controller=\"MyCtrl\" name=\"form\">\n  Single Image with validations\n  <div class=\"button\" ngf-select ng-model=\"file\" name=\"file\" ngf-pattern=\"'image/*'\"\n    ngf-accept=\"'image/*'\" ngf-max-size=\"20MB\" ngf-min-height=\"100\"\n    ngf-resize=\"{width: 100, height: 100}\">Select</div>\n  Multiple files\n  <div class=\"button\" ngf-select ng-model=\"files\" ngf-multiple=\"true\">Select</div>\n  Drop files: <div ngf-drop ng-model=\"files\" class=\"drop-box\">Drop</div>\n  <button type=\"submit\" ng-click=\"submit()\">submit</button>\n</form>\n\nUpload right away after file selection:\n<div class=\"button\" ngf-select=\"upload($file)\">Upload on file select</div>\n<div class=\"button\" ngf-select=\"uploadFiles($files)\" multiple=\"multiple\">Upload on file select</div>\n  Drop File:\n<div ngf-drop=\"uploadFiles($files)\" class=\"drop-box\"\n  ngf-drag-over-class=\"'dragover'\" ngf-multiple=\"true\"\n  ngf-pattern=\"'image/*,application/pdf'\">Drop Images or PDFs files here</div>\n<div ngf-no-file-drop>File Drag/Drop is not supported for this browser</div>\n\nImage thumbnail: <img ngf-thumbnail=\"file || '/thumb.jpg'\">\nAudio preview: <audio controls ngf-src=\"file\"></audio>\nVideo preview: <video controls ngf-src=\"file\"></video>\n```\nJavascript code:\n```js\n//inject directives and services.\nvar app = angular.module('fileUpload', ['ngFileUpload']);\n\napp.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) {\n    // upload later on form submit or something similar\n    $scope.submit = function() {\n      if ($scope.form.file.$valid && $scope.file) {\n        $scope.upload($scope.file);\n      }\n    };\n\n    // upload on file select or drop\n    $scope.upload = function (file) {\n        Upload.upload({\n            url: 'upload/url',\n            data: {file: file, 'username': $scope.username}\n        }).then(function (resp) {\n            console.log('Success ' + resp.config.data.file.name + 'uploaded. Response: ' + resp.data);\n        }, function (resp) {\n            console.log('Error status: ' + resp.status);\n        }, function (evt) {\n            var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);\n            console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name);\n        });\n    };\n    // for multiple files:\n    $scope.uploadFiles = function (files) {\n      if (files && files.length) {\n        for (var i = 0; i < files.length; i++) {\n          Upload.upload({..., data: {file: files[i]}, ...})...;\n        }\n        // or send them all together for HTML5 browsers:\n        Upload.upload({..., data: {file: files}, ...})...;\n      }\n    }\n}]);\n```\n\n### Full reference\n\n#### File select and drop\n\nAt least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to link to the element.\n`ngf-select` only attributes are marked with * and `ngf-drop` only attributes are marked with +.\n\n```html\n<div|button|input type=\"file\"|ngf-select|ngf-drop...\n  ngf-select=\"\" or \"upload($files, ...)\"  <!-- called when files are selected or cleared -->\n  ngf-drop=\"\" or \"upload($files, ...)\" <!--  called when files being dropped\n    You can use ng-model or ngf-change instead of specifying function for ngf-drop and ngf-select\n    function parameters are the same as ngf-change -->\n  ngf-change=\"upload($files, $file, $newFiles, $duplicateFiles, $invalidFiles, $event)\"\n    <!--  called when files are selected, dropped, or cleared -->\n  ng-model=\"myFiles\" <!--  binds the valid selected/dropped file or files to the scope model\n    could be an array or single file depending on ngf-multiple and ngf-keep values. -->\n  ngf-model-options=\"{updateOn: 'change click drop dropUrl paste', allowInvalid: false, debounce: 0}\"\n    <!--  updateOn could be used to disable resetting on click, or updating on paste, browser \n    image drop, etc. allowInvalid default is false could allow invalid files in the model\n    debouncing will postpone model update (miliseconds). See angular ng-model-options for more details. -->\n  ngf-model-invalid=\"invalidFile(s)\" <!--  binds the invalid selected/dropped file or files to this model. -->\n  ngf-before-model-change=\"beforeChange($files, ...)\" <!--  called after file select/drop and before\n    model change, validation and resize is processed -->\n  ng-disabled=\"boolean\" <!--  disables this element -->\n  ngf-select-disabled=\"boolean\" <!--  default false, disables file select on this element -->\n  ngf-drop-disabled=\"boolean\" <!--  default false, disables file drop on this element -->\n  ngf-multiple=\"boolean\" <!--  default false, allows selecting multiple files -->\n  ngf-keep=\"true|false|'distinct'\" <!--  default false, keep the previous ng-model files and \n    append the new files. \"'distinct'\" removes duplicate files\n    $newFiles and $duplicateFiles are set in ngf-change/select/drop functions. -->\n  ngf-fix-orientation=\"boolean\" <!-- default false, would rotate the jpeg image files that have\n    exif orientation data. See #745. Could be a boolean function like shouldFixOrientation($file)\n    to decide wethere to fix that file or not. -->\n\n  *ngf-capture=\"'camera'\" or \"'other'\" <!--  allows mobile devices to capture using camera -->\n  *ngf-accept=\"'image/*'\" <!--  standard HTML accept attr, browser specific select popup window -->\n\n  +ngf-allow-dir=\"boolean\" <!--  default true, allow dropping files only for Chrome webkit browser -->\n  +ngf-include-dir=\"boolean\" <!-- default false, include directories in the dropped file array.\n    You can detect if they are directory or not by checking the type === 'directory'. -->\n  +ngf-drag-over-class=\"{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}\"\n                    or \"'myDragOverClass'\" or \"calcDragOverClass($event)\"\n    <!--  default \"dragover\". drag over css class behaviour. could be a string, a function\n    returning class name or a json object.\n    accept/reject class only works in Chrome, validating only the file mime type.\n    if pattern is not specified ngf-pattern will be used. See following docs for more info. -->\n  +ngf-drag=\"drag($isDragging, $class, $event)\" <!--  function called on drag over/leave events.\n    $isDragging: boolean true if is dragging over(dragover), false if drag has left (dragleave)\n    $class is the class that is being set for the element calculated by ngf-drag-over-class -->\n  +ngf-drop-available=\"dropSupported\" <!--  set the value of scope model to true or false \n    based on file drag&drop support for this browser -->\n  +ngf-stop-propagation=\"boolean\" <!--  default false, whether to propagate drag/drop events. -->\n  +ngf-hide-on-drop-not-available=\"boolean\" <!--  default false, hides element if file drag&drop is not -->\n  +ngf-enable-firefox-paste=\"boolean\" <!--  *experimental* default false, enable firefox image paste \n    by making element contenteditable -->\n\n  ngf-resize=\"{width: 100, height: 100, quality: .8, type: 'image/jpeg',\n               ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}\"\n               or resizeOptions() <!--  a function returning a promise which resolves into the options.\n    resizes the image to the given width/height or ratio. Quality is optional between 0.1 and 1.0),\n    type is optional convert it to the given image type format.\n    centerCrop true will center crop the image if it does not fit within the given width/height or ratio.\n    centerCrop false (default) will not crop the image and will fit it within the given width/height or ratio\n    so the resulting image width (or height) could be less than given width (or height).\n    pattern is to resize only the files that their name or type matches the pattern similar to ngf-pattern.\n    restoreExif boolean default true, will restore exif info on the resized image. -->\n  ngf-resize-if=\"$width > 1000 || $height > 1000\" or \"resizeCondition($file, $width, $height)\"\n    <!--  apply ngf-resize only if this function returns true. To filter specific images to be resized. -->\n  ngf-validate-after-resize=\"boolean\" <!--  default false, if true all validation will be run after\n    the images are being resized, so any validation error before resize will be ignored. -->\n\n  <!-- validations: -->\n  ngf-max-files=\"10\" <!--  maximum number of files allowed to be selected or dropped, validate error name: maxFiles -->\n  ngf-pattern=\"'.pdf,.jpg,video/*,!.jog'\" <!--  comma separated wildcard to filter file names and \n    types allowed you can exclude specific files by ! at the beginning. validate error name: pattern -->\n  ngf-min-size, ngf-max-size, ngf-max-total-size=\"100\" in bytes or \"'10KB'\" or \"'10MB'\" or \"'10GB'\"\n    <!--  validate as form.file.$error.maxSize=true and file.$error='maxSize'\n    ngf-max-total-size is for multiple file select and validating the total size of all files. -->\n  ngf-min-height, ngf-max-height, ngf-min-width, ngf-max-width=\"1000\" in pixels only images\n    <!--  validate error names: minHeight, maxHeight, minWidth, maxWidth -->\n  ngf-ratio=\"8:10,1.6\" <!--  list of comma separated valid aspect ratio of images in float or 2:3 format\n    validate error name: ratio -->\n  ngf-min-ratio, ngf-max-ratio=\"8:10\" <!--  min or max allowed aspect ratio for the image. -->\n  ngf-dimensions=\"$width > 1000 || $height > 1000\" or \"validateDimension($file, $width, $height)\"\n    <!--  validate the image dimensions, validate error name: dimensions -->\n  ngf-min-duration, ngf-max-duration=\"100.5\" in seconds or \"'10s'\" or \"'10m'\" or \"'10h'\" only audio, video\n    <!--  validate error name: maxDuration -->\n  ngf-duration=\"$duration > 1000\" or \"validateDuration($file, $duration)\"\n    <!--  validate the media duration, validate error name: duration -->\n\n  ngf-validate=\"{size: {min: 10, max: '20MB'}, width: {min: 100, max:10000}, height: {min: 100, max: 300}\n                ratio: '2x1', duration: {min: '10s', max: '5m'}, pattern: '.jpg'}\"\n    <!-- shorthand form for above validations in one place. -->\n  ngf-validate-fn=\"validate($file)\" <!--  custom validation function, return boolean or string \n    containing the error. validate error name: validateFn -->\n  ngf-validate-async-fn=\"validate($file)\" <!--  custom validation function, return a promise that \n    resolve to boolean or string containing the error. validate error name: validateAsyncFn -->\n  ngf-validate-force=\"boolean\" <!--  default false, if true file.$error will be set if \n    the dimension or duration values for validations cannot be calculated for example \n    image load error or unsupported video by the browser. by default it would assume the file \n    is valid if the duration or dimension cannot be calculated by the browser. -->\n  ngf-ignore-invalid=\"'pattern maxSize'\" <!--  ignore the files that fail the specified validations. \n    They will just be ignored and will not show up in ngf-model-invalid or make the form invalid.\n    space separated list of validate error names. -->\n  ngf-run-all-validations=\"boolean\" <!--  default false. Runs all the specified validate directives. \n    By default once a validation fails for a file it would stop running other validations for that file. -->\n\n>Upload/Drop</div>\n\n<div|... ngf-no-file-drop>File Drag/drop is not supported</div>\n\n<!--  filter to convert the file to base64 data url. -->\n<a href=\"file | ngfDataUrl\">image</a>\n```\n\n#### File preview\n```html\n<img|audio|video|div\n  *ngf-src=\"file\" <!-- To preview the selected file, sets src attribute to the file data url. -->\n  *ngf-background=\"file\" <!-- sets background-image style to the file data url. -->\n  ngf-resize=\"{width: 20, height: 20, quality: 0.9}\" <!--  only for image resizes the image \n    before setting it as src or background image. quality is optional. -->\n  ngf-no-object-url=\"true or false\" <!--  see #887 to force base64 url generation instead of \n    object url. Default false -->\n>\n\n<div|span|...\n *ngf-thumbnail=\"file\" <!-- Generates a thumbnail version of the image file -->\n ngf-size=\"{width: 20, height: 20, quality: 0.9}\" the image will be resized to this size\n        <!--  if not specified will be resized to this element`s client width and height. -->\n ngf-as-background=\"boolean\" <!-- if true it will set the background image style instead of src attribute. -->\n>\n```\n\n#### Upload service:\n```js\nvar upload = Upload.upload({\n  *url: 'server/upload/url', // upload.php script, node.js route, or servlet url\n  /*\n  Specify the file and optional data to be sent to the server.\n  Each field including nested objects will be sent as a form data multipart.\n  Samples: {pic: file, username: username}\n    {files: files, otherInfo: {id: id, person: person,...}} multiple files (html5)\n    {profiles: {[{pic: file1, username: username1}, {pic: file2, username: username2}]} nested array multiple files (html5)\n    {file: file, info: Upload.json({id: id, name: name, ...})} send fields as json string\n    {file: file, info: Upload.jsonBlob({id: id, name: name, ...})} send fields as json blob, 'application/json' content_type\n    {picFile: Upload.rename(file, 'profile.jpg'), title: title} send file with picFile key and profile.jpg file name*/\n  *data: {key: file, otherInfo: uploadInfo},\n  /*\n  This is to accommodate server implementations expecting nested data object keys in .key or [key] format.\n  Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file\n     data: {rec: {name: 'N', pic: file}}, objectKey: '.k' sent as: rec.name -> N, rec.pic -> file */\n  objectKey: '[k]' or '.k' // default is '[k]'\n  /*\n  This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or\n  ''(multiple entries with same key) format.\n  Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],...\n    data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/\n  arrayKey: '[i]' or '[]' or '.i' or '' //default is '[i]'\n  method: 'POST' or 'PUT'(html5), default POST,\n  headers: {'Authorization': 'xxx'}, // only for html5\n  withCredentials: boolean,\n  /*\n  See resumable upload guide below the code for more details (html5 only) */\n  resumeSizeUrl: '/uploaded/size/url?file=' + file.name // uploaded file size so far on the server.\n  resumeSizeResponseReader: function(data) {return data.size;} // reads the uploaded file size from resumeSizeUrl GET response\n  resumeSize: function() {return promise;} // function that returns a prommise which will be\n                                            // resolved to the upload file size on the server.\n  resumeChunkSize: 10000 or '10KB' or '10MB' // upload in chunks of specified size\n  disableProgress: boolean // default false, experimental as hotfix for potential library conflicts with other plugins\n  ... and all other angular $http() options could be used here.\n})\n\n// returns a promise\nupload.then(function(resp) {\n  // file is uploaded successfully\n  console.log('file ' + resp.config.data.file.name + 'is uploaded successfully. Response: ' + resp.data);\n}, function(resp) {\n  // handle error\n}, function(evt) {\n  // progress notify\n  console.log('progress: ' + parseInt(100.0 * evt.loaded / evt.total) + '% file :'+ evt.config.data.file.name);\n});\nupload.catch(errorCallback);\nupload.finally(callback, notifyCallback);\n\n/* access or attach event listeners to the underlying XMLHttpRequest */\nupload.xhr(function(xhr){\n  xhr.upload.addEventListener(...)\n});\n\n/* cancel/abort the upload in progress. */\nupload.abort();\n\n/*\nalternative way of uploading, send the file binary with the file's content-type.\nCould be used to upload files to CouchDB, imgur, etc... html5 FileReader is needed.\nThis is equivalent to angular $http() but allow you to listen to the progress event for HTML5 browsers.*/\nUpload.http({\n  url: '/server/upload/url',\n  headers : {\n    'Content-Type': file.type\n  },\n  data: file\n})\n\n/* Set the default values for ngf-select and ngf-drop directives*/\nUpload.setDefaults({ngfMinSize: 20000, ngfMaxSize:20000000, ...})\n\n// These two defaults could be decreased if you experience out of memory issues\n// or could be increased if your app needs to show many images on the page.\n// Each image in ngf-src, ngf-background or ngf-thumbnail is stored and referenced as a blob url\n// and will only be released if the max value of the followings is reached.\nUpload.defaults.blobUrlsMaxMemory = 268435456 // default max total size of files stored in blob urls.\nUpload.defaults.blobUrlsMaxQueueSize = 200 // default max number of blob urls stored by this application.\n\n/* Convert a single file or array of files to a single or array of\nbase64 data url representation of the file(s).\nCould be used to send file in base64 format inside json to the databases */\nUpload.base64DataUrl(files).then(function(urls){...});\n\n/* Convert the file to blob url object or base64 data url based on boolean disallowObjectUrl value */\nUpload.dataUrl(file, boolean).then(function(url){...});\n\n/* Get image file dimensions*/\nUpload.imageDimensions(file).then(function(dimensions){console.log(dimensions.width, dimensions.height);});\n\n/* Get audio/video duration*/\nUpload.mediaDuration(file).then(function(durationInSeconds){...});\n\n/* Resizes an image. Returns a promise */\n// options: width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif\n//resizeIf(width, height) returns boolean. See ngf-resize directive for more details of options.\nUpload.resize(file, options).then(function(resizedFile){...});\n\n/* returns boolean showing if image resize is supported by this browser*/\nUpload.isResizeSupported()\n/* returns boolean showing if resumable upload is supported by this browser*/\nUpload.isResumeSupported()\n\n/* returns a file which will be uploaded with the newName instead of original file name */\nUpload.rename(file, newName)\n/* converts the object to a Blob object with application/json content type\nfor jsob byte streaming support #359 (html5 only)*/\nUpload.jsonBlob(obj)\n/* converts the value to json to send data as json string. Same as angular.toJson(obj) */\nUpload.json(obj)\n/* converts a dataUrl to Blob object.*/\nvar blob = upload.dataUrltoBlob(dataurl, name);\n/* returns true if there is an upload in progress. Can be used to prompt user before closing browser tab */\nUpload.isUploadInProgress() boolean\n/* downloads and converts a given url to Blob object which could be added to files model */\nUpload.urlToBlob(url).then(function(blob) {...});\n/* returns boolean to check if the object is file and could be used as file in Upload.upload()/http() */\nUpload.isFile(obj);\n/* fixes the exif orientation of the jpeg image file*/\nUpload.applyExifRotation(file).then(...)\n```\n**ng-model**\nThe model value will be a single file instead of an array if all of the followings are true:\n  * `ngf-multiple` is not set or is resolved to false.\n  * `multiple` attribute is not set on the element\n  * `ngf-keep` is not set or is resolved to false.\n\n**validation**\nWhen any of the validation directives specified the form validation will take place and\nyou can access the value of the validation using `myForm.myFileInputName.$error.<validate error name>`\nfor example `form.file.$error.pattern`.\nIf multiple file selection is allowed you can specify `ngf-model-invalid=\"invalidFiles\"` to assing the invalid files to\na model and find the error of each individual file with `file.$error` and description of it with `file.$errorParam`.\nYou can use angular ngf-model-options to allow invalid files to be set to the ng-model  `ngf-model-options=\"{allowInvalid: true}\"`.\n\n**Upload multiple files**: Only for HTML5 FormData browsers (not IE8-9) you have an array of files or more than one file in your `data` to\nsend them all in one request . Non-html5 browsers due to flash limitation will upload each file one by one in a separate request.\nYou should iterate over the files and send them one by one for a cross browser solution.\n\n**drag and drop styling**: For file drag and drop, `ngf-drag-over-class` could be used to style the drop zone.\nIt can be a function that returns a class name based on the $event. Default is \"dragover\" string.\nOnly in chrome It could be a json object `{accept: 'a', 'reject': 'r', pattern: 'image/*', delay: 10}` that specify the\nclass name for the accepted or rejected drag overs. The `pattern` specified or `ngf-pattern` will be used to validate the file's `mime-type`\nsince that is the only property of the file that is reported by the browser on drag. So you cannot validate\nthe file name/extension, size or other validations on drag. There is also some limitation on some file types which are not reported by Chrome.\n`delay` default is 100, and is used to fix css3 transition issues from dragging over/out/over [#277](https://github.com/danialfarid/angular-file-upload/issues/277).\n\n**Upload.setDefaults()**:\nIf you have many file selects or drops you can set the default values for the directives by calling `Upload.setDefaults(options)`. `options` would be a json object with directive names in camelcase and their default values.\n\n**Resumable Uploads:**\nThe plugin supports resumable uploads for large files.\nOn your server you need to keep track of what files are being uploaded and how much of the file is uploaded.\n * `url` upload endpoint need to reassemble the file chunks by appending uploading content to the end of the file or correct chunk position if it already exists.\n * `resumeSizeUrl` server endpoint to return uploaded file size so far on the server to be able to resume the upload from\n where it is ended. It should return zero if the file has not been uploaded yet. <br/>A GET request will be made to that\n url for each upload to determine if part of the file is already uploaded or not. You need a unique way of identifying the file\n  on the server so you can pass the file name or generated id for the file as a request parameter.<br/>\n By default it will assume that the response\n content is an integer or a json object with `size` integer property. If you return other formats from the endpoint you can specify\n `resumeSizeResponseReader` function to return the size value from the response. Alternatively instead of `resumeSizeUrl` you can use\n `resumeSize` function which returns a promise that resolves to the size of the uploaded file so far.\n Make sure when the file is fully uploaded without any error/abort this endpoint returns zero for the file size\n if you want to let the user to upload the same file again. Or optionally you could have a restart endpoint to\n set that back to zero to allow re-uploading the same file.\n * `resumeChunkSize` optionally you can specify this to upload the file in chunks to the server. This will allow uploading to GAE or other servers that have\n file size limitation and trying to upload the whole request before passing it for internal processing.<br/>\n If this option is set the requests will have the following extra fields:\n `_chunkSize`, `_currentChunkSize`, `_chunkNumber` (zero starting), and `_totalSize` to help the server to write the uploaded chunk to\n the correct position.\n Uploading in chunks could slow down the overall upload time specially if the chunk size is too small.\n When you provide `resumeChunkSize` option one of the `resumeSizeUrl` or `resumeSize` is mandatory to know how much of the file is uploaded so far.\n\n\n\n\n## <a name=\"old_browsers\"></a> Old browsers\n\nFor browsers not supporting HTML5 FormData (IE8, IE9, ...) [FileAPI](https://github.com/mailru/FileAPI) module is used.\n**Note**: You need Flash installed on your browser since `FileAPI` uses Flash to upload files.\n\nThese two files  **`FileAPI.min.js`, `FileAPI.flash.swf`** will be loaded by the module on demand (no need to be included in the html) if the browser does not supports HTML5 FormData to avoid extra load for HTML5 browsers.\nYou can place these two files beside `angular-file-upload-shim(.min).js` on your server to be loaded automatically from the same path or you can specify the path to those files if they are in a different path using the following script:\n```html\n<script>\n    //optional need to be loaded before angular-file-upload-shim(.min).js\n    FileAPI = {\n        //only one of jsPath or jsUrl.\n        jsPath: '/js/FileAPI.min.js/folder/',\n        jsUrl: 'yourcdn.com/js/FileAPI.min.js',\n\n        //only one of staticPath or flashUrl.\n        staticPath: '/flash/FileAPI.flash.swf/folder/',\n        flashUrl: 'yourcdn.com/js/FileAPI.flash.swf',\n\n        //forceLoad: true, html5: false //to debug flash in HTML5 browsers\n        //noContentTimeout: 10000 (see #528)\n    }\n</script>\n<script src=\"angular-file-upload-shim.min.js\"></script>...\n```\n**Old browsers known issues**:\n* Because of a Flash limitation/bug if the server doesn't send any response body the status code of the response will be always `204 'No Content'`. So if you have access to your server upload code at least return a character in the response for the status code to work properly.\n* Custom headers will not work due to a Flash limitation [#111](https://github.com/danialfarid/ng-file-upload/issues/111) [#224](https://github.com/danialfarid/ng-file-upload/issues/224) [#129](https://github.com/danialfarid/ng-file-upload/issues/129)\n* Due to Flash bug [#92](https://github.com/danialfarid/ng-file-upload/issues/92) Server HTTP error code 400 will be returned as 200 to the client. So avoid returning 400 on your server side for upload response otherwise it will be treated as a success response on the client side.\n* In case of an error response (http code >= 400) the custom error message returned from the server may not be available. For some error codes flash just provide a generic error message and ignores the response text. [#310](https://github.com/danialfarid/ng-file-upload/issues/310)\n* Older browsers won't allow `PUT` requests. [#261](https://github.com/danialfarid/ng-file-upload/issues/261)\n\n## <a name=\"server\"></a>Server Side\n\n* <a name=\"java\"></a>**Java**\nYou can find the sample server code in Java/GAE [here](https://github.com/danialfarid/ng-file-upload/blob/master/demo/src/main/java/com/df/angularfileupload/)\n* <a name=\"spring\"></a>**Spring MVC**\n[Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/spring-mvc-example) provided by [zouroto](https://github.com/zouroto)\n* <a name=\"node\"></a>**Node.js**\n[Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/node.js-example) provided by [chovy](https://github.com/chovy).\n[Another wiki](https://github.com/danialfarid/ng-file-upload/wiki/Node-example) using Express 4.0 and the Multiparty provided by [Jonathan White](https://github.com/JonathanZWhite)\n* <a name=\"rails\"></a>**Rails**\n  * [Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/Rails-Example) provided by [guptapriyank](https://github.com/guptapriyank).\n  * [Blog post](http://www.coshx.com/blog/2015/07/10/file-attachments-in-angular/)\nprovided by [Coshx Labs](http://www.coshx.com/).\n  * **Rails progress event**: If your server is Rails and Apache you may need to modify server configurations for the server to support upload progress. See [#207](https://github.com/danialfarid/ng-file-upload/issues/207)\n* <a name=\"php\"></a>**PHP**\n[Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/PHP-Example) and related issue [only one file in $_FILES when uploading multiple files](https://github.com/danialfarid/ng-file-upload/issues/475)\n* <a name=\"net\"></a>**.Net**\n  * [Demo](https://github.com/stewartm83/angular-fileupload-sample) showing how to use ng-file-upload with Asp.Net Web Api.\n  * Sample client and server code [demo/C#](https://github.com/danialfarid/ng-file-upload/tree/master/demo/C%23) provided by [AtomStar](https://github.com/AtomStar)\n\n## <a name=\"cors\"></a>CORS\nTo support CORS upload your server needs to allow cross domain requests. You can achieve that by having a filter or interceptor on your upload file server to add CORS headers to the response similar to this:\n([sample java code](https://github.com/danialfarid/ng-file-upload/blob/master/demo/src/main/java/com/df/angularfileupload/CORSFilter.java))\n```java\nhttpResp.setHeader(\"Access-Control-Allow-Methods\", \"POST, PUT, OPTIONS\");\nhttpResp.setHeader(\"Access-Control-Allow-Origin\", \"your.other.server.com\");\nhttpResp.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\"));\n```\nFor non-HTML5 IE8-9 browsers you would also need a `crossdomain.xml` file at the root of your server to allow CORS for flash:\n<a name=\"crossdomain\"></a>([sample xml](https://angular-file-upload.appspot.com/crossdomain.xml))\n```xml\n<cross-domain-policy>\n  <site-control permitted-cross-domain-policies=\"all\"/>\n  <allow-access-from domain=\"angular-file-upload.appspot.com\"/>\n  <allow-http-request-headers-from domain=\"*\" headers=\"*\" secure=\"false\"/>\n</cross-domain-policy>\n```\n\n#### <a name=\"s3\"></a>Amazon AWS S3 Upload\nFor Amazon authentication version 4 [see this comment](https://github.com/danialfarid/ng-file-upload/issues/1128#issuecomment-196203268)\n\nThe <a href=\"https://angular-file-upload.appspot.com/\" target=\"_blank\">demo</a> page has an option to upload to S3.\nHere is a sample config options:\n```js\nUpload.upload({\n    url: 'https://angular-file-upload.s3.amazonaws.com/', //S3 upload url including bucket name\n    method: 'POST',\n    data: {\n        key: file.name, // the key to store the file on S3, could be file name or customized\n        AWSAccessKeyId: <YOUR AWS AccessKey Id>,\n        acl: 'private', // sets the access to the uploaded file in the bucket: private, public-read, ...\n        policy: $scope.policy, // base64-encoded json policy (see article below)\n        signature: $scope.signature, // base64-encoded signature based on policy string (see article below)\n        \"Content-Type\": file.type != '' ? file.type : 'application/octet-stream', // content type of the file (NotEmpty)\n        filename: file.name, // this is needed for Flash polyfill IE8-9\n        file: file\n    }\n});\n```\n[This article](http://aws.amazon.com/articles/1434/) explains more about these fields and provides instructions on how to generate the policy and signature using a server side tool.\nThese two values are generated from the json policy document which looks like this:\n```js\n{\n    \"expiration\": \"2020-01-01T00:00:00Z\",\n    \"conditions\": [\n        {\"bucket\": \"angular-file-upload\"},\n        [\"starts-with\", \"$key\", \"\"],\n        {\"acl\": \"private\"},\n        [\"starts-with\", \"$Content-Type\", \"\"],\n        [\"starts-with\", \"$filename\", \"\"],\n        [\"content-length-range\", 0, 524288000]\n    ]\n}\n```\nThe [demo](https://angular-file-upload.appspot.com/) page provide a helper tool to generate the policy and signature from you from the json policy document. **Note**: Please use https protocol to access demo page if you are using this tool to generate signature and policy to protect your aws secret key which should never be shared.\n\nMake sure that you provide upload and CORS post to your bucket at AWS -> S3 -> bucket name -> Properties -> Edit bucket policy and Edit CORS Configuration. Samples of these two files:\n```js\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"UploadFile\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"arn:aws:iam::xxxx:user/xxx\"\n      },\n      \"Action\": [\n        \"s3:GetObject\",\n        \"s3:PutObject\"\n      ],\n      \"Resource\": \"arn:aws:s3:::angular-file-upload/*\"\n    },\n    {\n      \"Sid\": \"crossdomainAccess\",\n      \"Effect\": \"Allow\",\n      \"Principal\": \"*\",\n      \"Action\": \"s3:GetObject\",\n      \"Resource\": \"arn:aws:s3:::angular-file-upload/crossdomain.xml\"\n    }\n  ]\n}\n```\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n    <CORSRule>\n        <AllowedOrigin>http://angular-file-upload.appspot.com</AllowedOrigin>\n        <AllowedMethod>POST</AllowedMethod>\n        <AllowedMethod>GET</AllowedMethod>\n        <AllowedMethod>HEAD</AllowedMethod>\n        <MaxAgeSeconds>3000</MaxAgeSeconds>\n        <AllowedHeader>*</AllowedHeader>\n    </CORSRule>\n</CORSConfiguration>\n```\n\nFor IE8-9 flash polyfill you need to have a <a href='#crossdomain'>crossdomain.xml</a> file at the root of you S3 bucket. Make sure the content-type of crossdomain.xml is text/xml and you provide read access to this file in your bucket policy.\n\n\nYou can also have a look at [https://github.com/nukulb/s3-angular-file-upload](https://github.com/nukulb/s3-angular-file-upload) for another example with [this](https://github.com/danialfarid/ng-file-upload/issues/814#issuecomment-112198426) fix.\n\n\n\n\n"
  },
  {
    "path": "demo/C#/UploadController.js",
    "content": "﻿(function () {\n    'use strict';\n\n    angular\n        .module('app')\n        .controller('UploadCtrl', UploadCtrl);\n\n    UploadCtrl.$inject = ['$location', '$upload'];\n\n    function UploadCtrl($location, $upload) {\n        /* jshint validthis:true */\n        var vm = this;\n        vm.title = 'UploadCtrl';\n\n        vm.onFileSelect = function ($files, user) {\n            //$files: an array of files selected, each file has name, size, and type.\n            for (var i = 0; i < $files.length; i++) {\n                var file = $files[i];\n                vm.upload = $upload.upload({\n                    url: 'Uploads/UploadHandler.ashx',\n                    data: { name: user.Name },\n                    file: file, // or list of files ($files) for html5 only\n                }).progress(function (evt) {\n                    //console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));\n                }).success(function (data, status, headers, config) {\n                    alert('Uploaded successfully ' + file.name);\n                }).error(function (err) {\n                    alert('Error occured during upload');\n                });\n            }\n        };\n       \n    }\n})();\n"
  },
  {
    "path": "demo/C#/UploadHandler.ashx",
    "content": "﻿<%@ WebHandler Language=\"C#\" CodeBehind=\"UploadHandler.ashx.cs\" Class=\"MyApp.Uploads.UploadHandler\" %>\n"
  },
  {
    "path": "demo/C#/UploadHandler.ashx.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Web;\n\nnamespace MyApp.Uploads\n{\n    /// <summary>\n    /// Summary description for UploadHandler\n    /// </summary>\n    public class UploadHandler : IHttpHandler\n    {\n\n        public void ProcessRequest(HttpContext context)\n        {\n            if (context.Request.Files.Count > 0)\n            {\n                HttpFileCollection files = context.Request.Files;\n                var userName = context.Request.Form[\"name\"];\n                for (int i = 0; i < files.Count; i++)\n                {\n                    HttpPostedFile file = files[i];\n\n                    string fname = context.Server.MapPath(\"Uploads\\\\\" + userName.ToUpper() + \"\\\\\" + file.FileName);\n                    file.SaveAs(fname);\n                }\n            }\n            context.Response.ContentType = \"text/plain\";\n            context.Response.Write(\"File/s uploaded successfully!\");\n        }\n\n        public bool IsReusable\n        {\n            get\n            {\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "demo/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n    <packaging>war</packaging>\n    <version>0.0.1</version>\n    <artifactId>ng-file-upload-demo-server</artifactId>\n    <groupId>com.df.ng-file-upload.demo.server</groupId>\n\n    <properties>\n        <appengine.app.version>1</appengine.app.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    </properties>\n\n    <prerequisites>\n        <maven>3.1.0</maven>\n    </prerequisites>\n\n    <dependencies>\n        <dependency>\n            <groupId>commons-fileupload</groupId>\n            <artifactId>commons-fileupload</artifactId>\n            <version>1.2</version>\n        </dependency>\n\n        <!-- Compile/runtime dependencies -->\n        <dependency>\n            <groupId>com.google.appengine</groupId>\n            <artifactId>appengine-api-1.0-sdk</artifactId>\n            <version>1.9.18</version>\n        </dependency>\n        <dependency>\n            <groupId>com.google.appengine</groupId>\n            <artifactId>appengine-endpoints</artifactId>\n            <version>1.9.18</version>\n        </dependency>\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>servlet-api</artifactId>\n            <version>2.5</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.inject</groupId>\n            <artifactId>javax.inject</artifactId>\n            <version>1</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <!-- for hot reload of the web application-->\n        <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <version>3.2</version>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.7</source>\n                    <target>1.7</target>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-war-plugin</artifactId>\n                <version>2.6</version>\n                <configuration>\n                    <archiveClasses>true</archiveClasses>\n                    <webResources>\n                        &lt;!&ndash; in order to interpolate version from pom into appengine-web.xml &ndash;&gt;\n                        <resource>\n                            <directory>${basedir}/src/main/webapp/WEB-INF</directory>\n                            <filtering>true</filtering>\n                            <targetPath>WEB-INF</targetPath>\n                        </resource>\n                    </webResources>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>com.google.appengine</groupId>\n                <artifactId>appengine-maven-plugin</artifactId>\n                <version>1.9.18</version>\n                <configuration>\n                    <enableJarClasses>false</enableJarClasses>\n                    <!-- Comment in the below snippet to bind to all IPs instead of just localhost -->\n                    <address>0.0.0.0</address>\n                    <port>8888</port>\n                    <fullScanSeconds>1</fullScanSeconds>\n                    <jvmFlags>\n                        <jvmFlag>-Xdebug</jvmFlag>\n                        <jvmFlag>-Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n</jvmFlag>\n                    </jvmFlags>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "demo/src/main/java/com/df/angularfileupload/CORSFilter.java",
    "content": "package com.df.angularfileupload;\n\nimport java.io.IOException;\n\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic class CORSFilter implements Filter {\n\n\t@Override\n\tpublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,\n\t\t\tServletException {\n\t\tHttpServletResponse httpResp = (HttpServletResponse) resp;\n\t\tHttpServletRequest httpReq = (HttpServletRequest) req;\n\n\t\thttpResp.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, OPTIONS\");\n\t\thttpResp.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n\t\tif (httpReq.getMethod().equalsIgnoreCase(\"OPTIONS\")) {\n\t\t\thttpResp.setHeader(\"Access-Control-Allow-Headers\",\n\t\t\t\t\thttpReq.getHeader(\"Access-Control-Request-Headers\"));\n\t\t}\n\t\tchain.doFilter(req, resp);\n\t}\n\n\t@Override\n\tpublic void init(FilterConfig arg0) throws ServletException {\n\t}\n\n\t@Override\n\tpublic void destroy() {\n\t}\n}"
  },
  {
    "path": "demo/src/main/java/com/df/angularfileupload/FileUpload.java",
    "content": "package com.df.angularfileupload;\n\nimport com.google.appengine.repackaged.org.joda.time.LocalDateTime;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.Enumeration;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.logging.Logger;\n\npublic class FileUpload extends HttpServlet {\n    private static final long serialVersionUID = -8244073279641189889L;\n    private final Logger log = Logger.getLogger(FileUpload.class.getName());\n\n    class SizeEntry {\n        public int size;\n        public LocalDateTime time;\n    }\n\n    static Map<String, SizeEntry> sizeMap = new ConcurrentHashMap<>();\n    int counter;\n\n    @Override\n    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {\n        try {\n            clearOldValuesInSizeMap();\n\n            String ipAddress = req.getHeader(\"X-FORWARDED-FOR\");\n            if (ipAddress == null) {\n                ipAddress = req.getRemoteAddr();\n            }\n            if (req.getMethod().equalsIgnoreCase(\"GET\")) {\n                if (req.getParameter(\"restart\") != null) {\n                    sizeMap.remove(ipAddress + req.getParameter(\"name\"));\n                }\n                SizeEntry entry = sizeMap.get(ipAddress + req.getParameter(\"name\"));\n                res.getWriter().write(\"{\\\"size\\\":\" + (entry == null ? 0 : entry.size) + \"}\");\n                res.setContentType(\"application/json\");\n                return;\n            }\n            req.setCharacterEncoding(\"utf-8\");\n            if (!\"OPTIONS\".equalsIgnoreCase(req.getMethod()) && req.getParameter(\"errorCode\") != null) {\n//\t\t\t\tres.getWriter().write(req.getParameter(\"errorMessage\"));\n//\t\t\t\tres.getWriter().flush();\n                res.sendError(Integer.parseInt(req.getParameter(\"errorCode\")), req.getParameter(\"errorMessage\"));\n                return;\n            }\n            StringBuilder sb = new StringBuilder(\"{\\\"result\\\": [\");\n\n            if (req.getHeader(\"Content-Type\") != null\n                    && req.getHeader(\"Content-Type\").startsWith(\"multipart/form-data\")) {\n                ServletFileUpload upload = new ServletFileUpload();\n\n                FileItemIterator iterator = upload.getItemIterator(req);\n\n                while (iterator.hasNext()) {\n                    FileItemStream item = iterator.next();\n                    sb.append(\"{\");\n                    sb.append(\"\\\"fieldName\\\":\\\"\").append(item.getFieldName()).append(\"\\\",\");\n                    if (item.getName() != null) {\n                        sb.append(\"\\\"name\\\":\\\"\").append(item.getName()).append(\"\\\",\");\n                    }\n                    if (item.getName() != null) {\n                        sb.append(\"\\\"size\\\":\\\"\").append(size(ipAddress + item.getName(), item.openStream())).append(\"\\\"\");\n                    } else {\n                        sb.append(\"\\\"value\\\":\\\"\").append(read(item.openStream()).replace(\"\\\"\", \"'\")).append(\"\\\"\");\n                    }\n                    sb.append(\"}\");\n                    if (iterator.hasNext()) {\n                        sb.append(\",\");\n                    }\n                }\n            } else {\n                sb.append(\"{\\\"size\\\":\\\"\" + size(ipAddress, req.getInputStream()) + \"\\\"}\");\n            }\n\n            sb.append(\"]\");\n            sb.append(\", \\\"requestHeaders\\\": {\");\n            @SuppressWarnings(\"unchecked\")\n            Enumeration<String> headerNames = req.getHeaderNames();\n            while (headerNames.hasMoreElements()) {\n                String header = headerNames.nextElement();\n                sb.append(\"\\\"\").append(header).append(\"\\\":\\\"\").append(req.getHeader(header)).append(\"\\\"\");\n                if (headerNames.hasMoreElements()) {\n                    sb.append(\",\");\n                }\n            }\n            sb.append(\"}}\");\n            res.setCharacterEncoding(\"utf-8\");\n            res.getWriter().write(sb.toString());\n        } catch (Exception ex) {\n            throw new ServletException(ex);\n        }\n    }\n\n    private void clearOldValuesInSizeMap() {\n        if (counter++ == 100) {\n            for (Map.Entry<String, SizeEntry> entry : sizeMap.entrySet()) {\n                if (entry.getValue().time.isBefore(LocalDateTime.now().minusHours(1))) {\n                    sizeMap.remove(entry.getKey());\n                }\n            }\n            counter = 0;\n        }\n    }\n\n    protected int size(String key, InputStream stream) {\n        int length = sizeMap.get(key) == null ? 0 : sizeMap.get(key).size;\n        try {\n            byte[] buffer = new byte[200000];\n            int size;\n            while ((size = stream.read(buffer)) != -1) {\n                length += size;\n                SizeEntry entry = new SizeEntry();\n                entry.size = length;\n                entry.time = LocalDateTime.now();\n                sizeMap.put(key, entry);\n//\t\t\t\t for (int i = 0; i < size; i++) {\n//\t\t\t\t System.out.print((char) buffer[i]);\n//\t\t\t\t }\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        System.out.println(length);\n        return length;\n\n    }\n\n    protected String read(InputStream stream) {\n        StringBuilder sb = new StringBuilder();\n        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));\n        try {\n            String line;\n            while ((line = reader.readLine()) != null) {\n                sb.append(line);\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            try {\n                reader.close();\n            } catch (IOException e) {\n                //ignore\n            }\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/df/angularfileupload/S3Signature.java",
    "content": "package com.df.angularfileupload;\n\nimport com.google.api.server.spi.IoUtil;\nimport com.google.appengine.repackaged.com.google.common.io.BaseEncoding;\n\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\n\npublic class S3Signature extends HttpServlet {\n\t@Override\n\tprotected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {\n\t\tString policy_document = IoUtil.readStream(req.getInputStream());\n\t\tSystem.out.println(policy_document);\n\t\tString policy = BaseEncoding.base64().encode(policy_document.getBytes(\"UTF-8\")).\n\t\t    replaceAll(\"\\n\",\"\").replaceAll(\"\\r\",\"\");\n\n\t\tMac hmac;\n\t\ttry {\n\t\t\thmac = Mac.getInstance(\"HmacSHA1\");\n\t\t\tString aws_secret_key = req.getParameter(\"aws-secret-key\");\n\t\t\tSystem.out.println(aws_secret_key);\n\n\t\t\thmac.init(new SecretKeySpec(aws_secret_key.getBytes(\"UTF-8\"), \"HmacSHA1\"));\n\t\t\tString signature = BaseEncoding.base64().encode(\n\t\t\t    hmac.doFinal(policy.getBytes(\"UTF-8\")))\n\t\t\t    .replaceAll(\"\\n\", \"\");\n\t\t\tres.setStatus(HttpServletResponse.SC_OK);\n\t\t\tres.setContentType(\"application/json\");\n\t\t\tres.getWriter().write(\"{\\\"signature\\\":\\\"\" + signature + \"\\\",\\\"policy\\\":\\\"\" + policy + \"\\\"}\");\n\t\t} catch (NoSuchAlgorithmException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t} catch (InvalidKeyException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n}"
  },
  {
    "path": "demo/src/main/resources/META-INF/jdoconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<jdoconfig xmlns=\"http://java.sun.com/xml/ns/jdo/jdoconfig\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://java.sun.com/xml/ns/jdo/jdoconfig http://java.sun.com/xml/ns/jdo/jdoconfig_3_0.xsd\">\n\n   <persistence-manager-factory name=\"transactions-optional\">\n       <property name=\"javax.jdo.PersistenceManagerFactoryClass\"\n           value=\"org.datanucleus.api.jdo.JDOPersistenceManagerFactory\"/>\n       <property name=\"javax.jdo.option.ConnectionURL\" value=\"appengine\"/>\n       <property name=\"javax.jdo.option.NontransactionalRead\" value=\"true\"/>\n       <property name=\"javax.jdo.option.NontransactionalWrite\" value=\"true\"/>\n       <property name=\"javax.jdo.option.RetainValues\" value=\"true\"/>\n       <property name=\"datanucleus.appengine.autoCreateDatastoreTxns\" value=\"true\"/>\n       <property name=\"datanucleus.appengine.singletonPMFForName\" value=\"true\"/>\n   </persistence-manager-factory>\n</jdoconfig>\n"
  },
  {
    "path": "demo/src/main/resources/META-INF/persistence.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<persistence xmlns=\"http://java.sun.com/xml/ns/persistence\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://java.sun.com/xml/ns/persistence\n        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd\" version=\"1.0\">\n\n    <persistence-unit name=\"transactions-optional\">\n        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>\n        <properties>\n            <property name=\"datanucleus.NontransactionalRead\" value=\"true\"/>\n            <property name=\"datanucleus.NontransactionalWrite\" value=\"true\"/>\n            <property name=\"datanucleus.ConnectionURL\" value=\"appengine\"/>\n        </properties>\n    </persistence-unit>\n</persistence>\n"
  },
  {
    "path": "demo/src/main/resources/log4j.properties",
    "content": "# A default log4j configuration for log4j users.\n#\n# To use this configuration, deploy it into your application's WEB-INF/classes\n# directory.  You are also encouraged to edit it as you like.\n\n# Configure the console as our one appender\nlog4j.appender.A1=org.apache.log4j.ConsoleAppender\nlog4j.appender.A1.layout=org.apache.log4j.PatternLayout\nlog4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n\n\n# tighten logging on the DataNucleus Categories\nlog4j.category.DataNucleus.JDO=WARN, A1\nlog4j.category.DataNucleus.Persistence=WARN, A1\nlog4j.category.DataNucleus.Cache=WARN, A1\nlog4j.category.DataNucleus.MetaData=WARN, A1\nlog4j.category.DataNucleus.General=WARN, A1\nlog4j.category.DataNucleus.Utility=WARN, A1\nlog4j.category.DataNucleus.Transaction=WARN, A1\nlog4j.category.DataNucleus.Datastore=WARN, A1\nlog4j.category.DataNucleus.ClassLoading=WARN, A1\nlog4j.category.DataNucleus.Plugin=WARN, A1\nlog4j.category.DataNucleus.ValueGeneration=WARN, A1\nlog4j.category.DataNucleus.Enhancer=WARN, A1\nlog4j.category.DataNucleus.SchemaTool=WARN, A1\n"
  },
  {
    "path": "demo/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <application>angular-file-upload</application>\n  <version>9-0-0</version>\n\n  <!--\n    Allows App Engine to send multiple requests to one instance in parallel:\n  -->\n  <threadsafe>true</threadsafe>\n\n  <!-- Configure java.util.logging -->\n  <system-properties>\n    <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n  </system-properties>\n\n  <!--\n    HTTP Sessions are disabled by default. To enable HTTP sessions specify:\n\n      <sessions-enabled>true</sessions-enabled>\n\n    It's possible to reduce request latency by configuring your application to\n    asynchronously write HTTP session data to the datastore:\n\n      <async-session-persistence enabled=\"true\" />\n\n    With this feature enabled, there is a very small chance your app will see\n    stale session data. For details, see\n    http://code.google.com/appengine/docs/java/config/appconfig.html#Enabling_Sessions\n  -->\n  <static-files>\n  \t<exclude path=\"/*.html\" />\n  </static-files>\n\n</appengine-web-app>\n"
  },
  {
    "path": "demo/src/main/webapp/WEB-INF/logging.properties",
    "content": "# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "demo/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\" xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" version=\"2.5\"\n\txsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\">\n\t<servlet>\n\t\t<servlet-name>file_upload</servlet-name>\n\t\t<servlet-class>com.df.angularfileupload.FileUpload</servlet-class>\n\t</servlet>\n\t<servlet-mapping>\n\t\t<servlet-name>file_upload</servlet-name>\n\t\t<url-pattern>/upload</url-pattern>\n\t</servlet-mapping>\n\t<servlet>\n\t\t<servlet-name>s3_sign</servlet-name>\n\t\t<servlet-class>com.df.angularfileupload.S3Signature</servlet-class>\n\t</servlet>\n\t<servlet-mapping>\n\t\t<servlet-name>s3_sign</servlet-name>\n\t\t<url-pattern>/s3sign</url-pattern>\n\t</servlet-mapping>\n\t<welcome-file-list>\n\t\t<welcome-file>index.html</welcome-file>\n\t</welcome-file-list>\n\t<servlet>\n\t\t<servlet-name>SystemServiceServlet</servlet-name>\n\t\t<servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>\n\t\t<init-param>\n\t\t\t<param-name>services</param-name>\n\t\t\t<param-value />\n\t\t</init-param>\n\t</servlet>\n\t<servlet-mapping>\n\t\t<servlet-name>SystemServiceServlet</servlet-name>\n\t\t<url-pattern>/_ah/spi/*</url-pattern>\n\t</servlet-mapping>\n\t<filter>\n\t\t<filter-name>cors_filter</filter-name>\n\t\t<filter-class>com.df.angularfileupload.CORSFilter</filter-class>\n\t</filter>\n\t<filter-mapping>\n\t\t<filter-name>cors_filter</filter-name>\n\t\t<url-pattern>*</url-pattern>\n\t</filter-mapping>\n</web-app>"
  },
  {
    "path": "demo/src/main/webapp/common.css",
    "content": "body {\n    font-family: Helvetica, arial, freesans, clean, sans-serif;\n}\n\n/* object {\n\tborder: 3px solid red;\n} */\n\n.upload-buttons input[type=\"file\"] {\n    width: 6.3em \\0/ IE9;\n}\n\n.upload-button {\n    Height: 26px;\n    line-height: 30px;\n    padding: 0 10px;\n    background: #CCC;\n    appearance: button;\n    -moz-appearance: button; /* Firefox */\n    -webkit-appearance: button; /* Safari and Chrome */\n    position: relative;\n    text-align: center;\n    top: 7px;\n    cursor: pointer;\n}\n\n.sel-file {\n    padding: 1px 5px;\n    font-size: smaller;\n    color: grey;\n}\n\n.response {\n    padding: 0;\n    padding-top: 10px;\n    margin: 3px 0;\n    clear: both;\n    list-style: none;\n}\n\n.response .sel-file li, .response .reqh {\n    color: blue;\n    padding-bottom: 5px;\n}\n\nfieldset {\n    border: 1px solid #DDD;\n    width: 620px;\n    padding: 10px;\n    line-height: 23px;\n}\n\nfieldset label {\n    /*font-size: smaller;*/\n}\n\n.progress {\n    display: inline-block;\n    width: 100px;\n    border: 3px groove #CCC;\n}\n\n.progress div {\n    font-size: smaller;\n    background: orange;\n    width: 0;\n}\n\n.drop-box {\n    background: #F8F8F8;\n    border: 5px dashed #DDD;\n    width: 170px;\n    text-align: center;\n    padding: 50px 10px;\n    margin-left: 10px;\n}\n\n.up-buttons {\n    float: right;\n}\n\n.drop-box.dragover {\n    border: 5px dashed blue;\n}\n\n.drop-box.dragover-err {\n    border: 5px dashed red;\n}\n\n/* for IE*/\n.js-fileapi-wrapper {\n    display: inline-block;\n    vertical-align: middle;\n}\n\nbutton {\n    padding: 1px 5px;\n    font-size: smaller;\n    margin: 0 3px;\n}\n\n.ng-v {\n    float: right;\n}\n\n.thumb {\n    float: left;\n    width: 18px;\n    height: 18px;\n    padding-right: 10px;\n}\n\nform .thumb {\n    width: 24px;\n    height: 24px;\n    float: none;\n    position: relative;\n    top: 7px;\n}\n\nform .progress {\n    line-height: 15px;\n}\n\n.edit-area {\n    font-size: 14px;\n    background: black;\n    color: #f9f9f9;\n    padding: 5px 1px;\n}\n\n#htmlEdit {\n    margin-bottom: 25px;\n}\n\n.edit-div {\n    font-size: smaller;\n}\n\n.CodeMirror {\n    font-size: 14px;\n    border: 1px solid #ccc;\n    margin-bottom: 15px;\n}\n\nform button {\n    padding: 3px 10px;\n    font-weight: bold;\n    margin-top: 10px;\n}\n\n.sub {\n    font-size: smaller;\n    color: #777;\n    padding-top: 5px;\n    padding-left: 25px;\n}\n\n.err {\n    font-size: 12px;\n    color: #C53F00;\n    margin: 15px;\n    padding: 15px;\n    background-color: #F0F0F0;\n    border: 1px solid black;\n}\n\n.s3 {\n    font-size: smaller;\n    color: #333;\n    margin-left: 20px;\n}\n\n.s3 fieldset {\n    border: 1px solid #AAA;\n}\n\n.s3 label {\n    width: 180px;\n    display: inline-block;\n}\n\n.s3 input {\n    width: 300px;\n}\n\n.s3 .helper {\n    margin-left: 5px;\n}\n\n.howto {\n    margin-left: 10px;\n    line-height: 20px;\n}\n\n.server {\n    margin-bottom: 20px;\n}\n\n.srv-title {\n    font-weight: bold;\n    padding: 5px 0 10px 0;\n}\n\n:not(output):-moz-ui-invalid {\n    box-shadow: none;\n}\n\n.preview {\n    clear: both;\n}\n\n.preview img, .preview audio, .preview video {\n    max-width: 300px;\n    max-height: 150px;\n    float: right;\n}\n\n.custom {\n    font-size: 14px;\n    margin-left: 20px;\n}\n"
  },
  {
    "path": "demo/src/main/webapp/crossdomain.xml",
    "content": "<?xml version=\"1.0\" ?>\n<cross-domain-policy>\n\t<site-control permitted-cross-domain-policies=\"all\" />\n\t<allow-access-from domain=\"*\" secure=\"false\" />\n\t<allow-access-from domain=\"angular-file-upload-cors.appspot.com\" />\n\t<allow-access-from domain=\"angular-file-upload-cors-srv.appspot.com\" />\n\t<allow-http-request-headers-from domain=\"*\" headers=\"*\" secure=\"false\" />\n</cross-domain-policy>"
  },
  {
    "path": "demo/src/main/webapp/donate.html",
    "content": "<!doctype html>\n<html>\n<head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<link type=\"text/css\" rel=\"stylesheet\" href=\"common.css\">\n<title>Angular file upload donate</title>\n</head>\n\n<body>\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_top\" id=\"frm\">\n<input type=\"hidden\" name=\"cmd\" value=\"_donations\">\n<input type=\"hidden\" name=\"business\" value=\"danial.farid@gmail.com\">\n<input type=\"hidden\" name=\"lc\" value=\"CA\">\n<input type=\"hidden\" name=\"item_name\" value=\"angular-file-upload plugin\">\n<input type=\"hidden\" name=\"no_note\" value=\"0\">\n<input type=\"hidden\" name=\"currency_code\" value=\"USD\">\n<input type=\"hidden\" name=\"bn\" value=\"PP-DonationsBF:tea.jpg:NonHostedGuest\">\n<input type=\"image\" src=\"img/tea.jpg\" border=\"0\" name=\"submit\" alt=\"PayPal - The safer, easier way to pay online!\">\n<img alt=\"\" border=\"0\" src=\"https://www.paypalobjects.com/en_US/i/scr/pixel.gif\" width=\"1\" height=\"1\">\n</form>\n<script>\nwindow.onload = function() {\n\tdocument.getElementById(\"frm\").submit();\n}\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "demo/src/main/webapp/index.html",
    "content": "<!doctype html>\n<html>\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <link type=\"text/css\" rel=\"stylesheet\" href=\"common.css\">\n  <title>Angular file upload sample</title>\n\n  <script type=\"text/javascript\">\n    FileAPI = {\n      debug: true,\n      //forceLoad: true, html5: false //to debug flash in HTML5 browsers\n      //wrapInsideDiv: true, //experimental for fixing css issues\n      //only one of jsPath or jsUrl.\n      //jsPath: '/js/FileAPI.min.js/folder/',\n      //jsUrl: 'yourcdn.com/js/FileAPI.min.js',\n\n      //only one of staticPath or flashUrl.\n      //staticPath: '/flash/FileAPI.flash.swf/folder/'\n      //flashUrl: 'yourcdn.com/js/FileAPI.flash.swf'\n    };\n  </script>\n\n  <!-- <script src=\"//code.jquery.com/jquery-1.9.0.min.js\"></script> -->\n  <script type=\"text/javascript\">\n    // load angularjs specific version\n    var angularVersion = window.location.hash.substring(1);\n    if (angularVersion.indexOf('/') == 0) angularVersion = angularVersion.substring(1);\n    document.write('<script type=\"text/javascript\" src=\"https://ajax.googleapis.com/ajax/libs/angularjs/' +\n        (angularVersion || '1.2.24') + '/angular.js\"><\\/script>');\n  </script>\n  <script src=\"js/ng-file-upload-shim.js\"></script>\n  <script src=\"js/ng-file-upload.js\"></script>\n  <script src=\"js/upload.js\"></script>\n\n  <link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.css\">\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.js\"></script>\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/htmlmixed/htmlmixed.min.js\"></script>\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/htmlembedded/htmlembedded.min.js\"></script>\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/xml/xml.min.js\"></script>\n  <script src=\"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/javascript/javascript.min.js\"></script>\n</head>\n\n<body ng-app=\"fileUpload\" ng-controller=\"MyCtrl\">\n<div class=\"ng-v\">\n  Angular Version: <input type=\"text\" ng-model=\"angularVersion\">\n  <input type=\"button\" data-ng-click=\"changeAngularVersion()\" value=\"Go\">\n</div>\n<h2>Angular file upload Demo</h2>\n\n<h3>\n  Visit <a href=\"https://github.com/danialfarid/ng-file-upload\">ng-file-upload</a> on github\n</h3>\n\n<div class=\"upload-div\">\n  <div class=\"edit-div\">\n    <a ng-click=\"showEdit = !showEdit\" href=\"javascript:void(0)\">+ edit upload html</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n    <a ng-show=\"showEdit\" ng-click=\"confirm() && (editHtml = defaultHtml)\" href=\"javascript:void(0)\">reset to\n      default</a><br/><br/>\n\n    <div ng-show=\"showEdit\" id=\"htmlEdit\"></div>\n  </div>\n\n  <div class=\"upload-buttons\">\n    <div id=\"editArea\">\n      <div>\n        <div>\n          <form name=\"myForm\">\n            <fieldset>\n              <legend>Upload on form submit</legend>\n              Username: <input type=\"text\" name=\"userName\" ng-model=\"username\" size=\"39\" required>\n              <i ng-show=\"myForm.userName.$error.required\">*required</i><br>\n              Profile Picture: <input type=\"file\" ngf-select ng-model=\"picFile\" name=\"file\" ngf-accept=\"'image/*'\" required>\n              <i ng-show=\"myForm.file.$error.required\">*required</i>\n              <br/>\n\n              <button ng-disabled=\"!myForm.$valid\" ng-click=\"uploadPic(picFile)\">Submit</button>\n              <img ngf-src=\"picFile\" class=\"thumb\">\n              <span class=\"progress\" ng-show=\"picFile.progress >= 0\">\n                  <div style=\"width:{{picFile.progress}}%\" ng-bind=\"picFile.progress + '%'\"></div>\n              </span>\n              <span ng-show=\"picFile.result\">Upload Successful</span>\n            </fieldset>\n            <br/>\n          </form>\n        </div>\n        <fieldset>\n          <legend>Play with options</legend>\n          <div class=\"up-buttons\">\n            <div ngf-select ngf-drop ng-model=\"files\" ngf-model-invalid=\"invalidFiles\"\n                 ngf-model-options=\"modelOptionsObj\"\n                 ngf-multiple=\"multiple\" ngf-pattern=\"pattern\" ngf-accept=\"acceptSelect\"\n                 ng-disabled=\"disabled\" ngf-capture=\"capture\"\n                 ngf-drag-over-class=\"dragOverClassObj\"\n                 ngf-validate=\"validateObj\"\n                 ngf-resize=\"resizeObj\"\n                 ngf-resize-if=\"resizeIfFn($file, $width, $height)\"\n                 ngf-dimensions=\"dimensionsFn($file, $width, $height)\"\n                 ngf-duration=\"durationFn($file, $duration)\"\n                 ngf-keep=\"keepDistinct ? 'distinct' : keep\"\n                 ngf-fix-orientation=\"orientation\"\n                 ngf-max-files=\"maxFiles\"\n                 ngf-ignore-invalid=\"ignoreInvalid\"\n                 ngf-run-all-validations=\"runAllValidations\"\n                 ngf-allow-dir=\"allowDir\" class=\"drop-box\" ngf-drop-available=\"dropAvailable\">Select File\n              <span ng-show=\"dropAvailable\"> or Drop File</span>\n            </div>\n            <br/>\n            <div ngf-drop ng-model=\"files\" ngf-model-invalid=\"invalidFiles\"\n                 ngf-model-options=\"modelOptionsObj\"\n                 ngf-multiple=\"multiple\" ngf-pattern=\"'image/*'\"\n                 ng-disabled=\"disabled\"\n                 ngf-drag-over-class=\"dragOverClassObj\"\n                 ngf-validate=\"validateObj\"\n                 ngf-resize=\"resizeObj\"\n                 ngf-resize-if=\"resizeIfFn($file, $width, $height)\"\n                 ngf-dimensions=\"dimensionsFn($file, $width, $height)\"\n                 ngf-duration=\"durationFn($file, $duration)\"\n                 ngf-keep=\"keepDistinct ? 'distinct' : keep\"\n                 ngf-enable-firefox-paste=\"true\"\n                 ngf-fix-orientation=\"orientation\"\n                 ngf-max-files=\"maxFiles\"\n                 ngf-ignore-invalid=\"ignoreInvalid\"\n                 ngf-run-all-validations=\"runAllValidations\"\n                 ngf-allow-dir=\"allowDir\" class=\"drop-box\" ng-show=\"dropAvailable\">\n              <span>Paste or Drop Image from browser</span></div>\n          </div>\n          <div class=\"custom\">\n            <label>accept (for select browser dependent): <input type=\"text\" ng-model=\"acceptSelect\"></label><br/>\n            <label>ngf-capture (for mobile): <input type=\"text\" ng-model=\"capture\"></label><br/>\n            <label>ngf-pattern (validate file model): <input type=\"text\" ng-model=\"pattern\"></label><br/>\n            <label>ngf-validate: <input type=\"text\" ng-model=\"validate\" size=\"49\"></label><br/>\n            <label>ngf-drag-over-class (chrome): <input size=\"31\" type=\"text\" ng-model=\"dragOverClass\"></label><br/>\n            <label>ngf-model-options: <input type=\"text\" size=\"43\" ng-model=\"modelOptions\"></label><br/>\n            <label>ngf-resize: <input type=\"text\" size=\"43\" ng-model=\"resize\"></label><br/>\n            <label>ngf-resize-if: <input type=\"text\" size=\"43\" ng-model=\"resizeIf\"></label><br/>\n            <label>ngf-dimensions: <input type=\"text\" size=\"43\" ng-model=\"dimensions\"></label><br/>\n            <label>ngf-duration: <input type=\"text\" size=\"43\" ng-model=\"duration\"></label><br/>\n            <label>ngf-max-files: <input type=\"text\" size=\"43\" ng-model=\"maxFiles\"></label><br/>\n            <label>ngf-ignore-invalid: <input type=\"text\" size=\"43\" ng-model=\"ignoreInvalid\"></label><br/>\n            <label><input type=\"checkbox\" ng-model=\"multiple\">ngf-multiple (allow multiple files)</label>\n            <label><input type=\"checkbox\" ng-model=\"disabled\">ng-disabled</label><br/>\n            <label><input type=\"checkbox\" ng-model=\"allowDir\">ngf-allow-dir (allow directory drop Chrome\n              only)</label><br/>\n            <label><input type=\"checkbox\" ng-model=\"keep\">ngf-keep (keep the previous model values in\n              ng-model)</label><br/>\n            <label><input type=\"checkbox\" ng-model=\"keepDistinct\">ngf-keep=\"distinct\" (do not allow\n              duplicates)</label><br/>\n            <label><input type=\"checkbox\" ng-model=\"orientation\">ngf-fix-orientation (for exif jpeg files)</label><br/>\n            <label><input type=\"checkbox\" ng-model=\"runAllValidations\">ngf-run-all-validations</label><br/>\n            <label>Upload resumable chunk size: <input type=\"text\" ng-model=\"chunkSize\"></label><br/>\n          </div>\n\n          <div class=\"preview\">\n            <div>Preview image/audio/video:</div>\n            <img ngf-src=\"!files[0].$error && files[0]\">\n            <audio controls ngf-src=\"!files[0].$error && files[0]\"></audio>\n            <video controls ngf-src=\"!files[0].$error && files[0]\"></video>\n          </div>\n        </fieldset>\n        <br/>\n      </div>\n    </div>\n  </div>\n  <ul style=\"clear:both\" class=\"response\">\n    <li class=\"sel-file\" ng-repeat=\"f in files\">\n      <div>\n        <img ngf-thumbnail=\"!f.$error && f\" class=\"thumb\">\n\t\t\t\t<span class=\"progress\" ng-show=\"f.progress >= 0\">\n\t\t\t\t\t<div style=\"width:{{f.progress}}%\">{{f.progress}}%</div>\n\t\t\t\t</span>\n        <button class=\"button\" ng-click=\"f.upload.abort();f.upload.aborted=true\"\n                ng-show=\"f.upload != null && f.progress < 100 && !f.upload.aborted\">\n          Abort<span ng-show=\"isResumeSupported\">/Pause</span>\n        </button>\n        <button class=\"button\" ng-click=\"upload(f, true);f.upload.aborted=false\"\n                ng-show=\"isResumeSupported && f.upload != null && f.upload.aborted\">Resume\n        </button>\n        <button class=\"button\" ng-click=\"restart(f);f.upload.aborted=false\"\n                ng-show=\"isResumeSupported && f.upload != null && (f.progress == 100 || f.upload.aborted)\">Restart\n        </button>\n        {{f.name}} - size: {{f.size}}B - type: {{f.type}}\n        <a ng-show=\"f.result\" href=\"javascript:void(0)\" ng-click=\"f.showDetail = !f.showDetail\">details</a>\n\n        <div ng-show=\"f.showDetail\">\n          <br/>\n\n          <div data-ng-show=\"f.result.result == null\">{{f.result}}</div>\n          <ul>\n            <li ng-repeat=\"item in f.result.result\">\n              <div data-ng-show=\"item.name\">file name: {{item.name}}</div>\n              <div data-ng-show=\"item.fieldName\">name: {{item.fieldName}}</div>\n              <div data-ng-show=\"item.size\">size on the serve: {{item.size}}</div>\n              <div data-ng-show=\"item.value\">value: {{item.value}}</div>\n            </li>\n          </ul>\n          <div data-ng-show=\"f.result.requestHeaders\" class=\"reqh\">request headers: {{f.result.requestHeaders}}</div>\n        </div>\n      </div>\n    </li>\n    <li class=\"sel-file\" ng-repeat=\"f in invalidFiles\">\n      <div>Invalid File: {{f.$errorMessages}} {{f.$errorParam}}, {{f.name}} - size: {{f.size}}B - type:\n        {{f.type}}\n      </div>\n    </li>\n  </ul>\n\n  <br/>\n\n  <div style=\"clear:both\" class=\"err\" ng-show=\"errorMsg != null\">{{errorMsg}}</div>\n</div>\n\n<div style=\"clear:both\" class=\"server\">\n  <div class=\"srv-title\">How to upload to the server:</div>\n\n  <div class=\"howto\">\n    <label><input type=\"radio\" name=\"howToSend\" ng-model=\"howToSend\" value=\"1\" ng-init=\"howToSend = 1\">Upload.upload():\n      multipart/form-data upload cross browser</label>\n    <br/>\n    <label><input type=\"radio\" name=\"howToSend\" ng-model=\"howToSend\" value=\"2\"\n                  ng-disabled=\"usingFlash\">Upload.http(): binary content with file's\n      Content-Type</label>\n\n    <div class=\"sub\">Can be used to upload files directory into <a\n        href=\"https://github.com/danialfarid/angular-file-upload/issues/88\">CouchDB</a>,\n      <a href=\"https://github.com/danialfarid/angular-file-upload/issues/87\">imgur</a>, etc... without multipart form\n      data (HTML5 FileReader browsers only)<br/>\n    </div>\n    <label><input type=\"radio\" name=\"howToSend\" ng-model=\"howToSend\" value=\"3\">Amazon S3 bucket upload</label>\n\n    <form ng-show=\"howToSend==3\" class=\"s3\" id=\"s3form\" action=\"http://localhost:8888\" onsubmit=\"return false\">\n      <br/>\n      Provide S3 upload parameters. <a href=\"http://aws.amazon.com/articles/1434/\" target=\"_blank\">Click here for\n      detailed documentation</a><br/></br>\n      <label>S3 upload url including bucket name:</label> <input type=\"text\" ng-model=\"s3url\"> <br/>\n      <label>AWSAccessKeyId:</label> <input type=\"Text\" name=\"AWSAccessKeyId\" ng-model=\"AWSAccessKeyId\"> <br/>\n      <label>acl (private or public):</label> <input type=\"text\" ng-model=\"acl\" value=\"private\"><br/>\n      <label>success_action_redirect:</label> <input type=\"text\" ng-model=\"success_action_redirect\"><br/>\n      <label>policy:</label> <input type=\"text\" ng-model=\"policy\"><br/>\n      <label>signature:</label> <input type=\"text\" ng-model=\"signature\"><br/>\n      <br/>\n      <button ng-click=\"showHelper=!showHelper\">S3 Policy signing helper (Optional)</button>\n      <br/><br/>\n\n      <div class=\"helper\" ng-show=\"showHelper\">\n        If you don't have your policy and signature you can use this tool to generate them by providing these two fields\n        and clicking on sign<br/>\n        <label>AWS Secret Key:</label> <input type=\"text\" ng-model=\"AWSSecretKey\"><br/>\n        <label>JSON policy:</label><br/> <textarea type=\"text\" ng-model=\"jsonPolicy\" rows=10 cols=70></textarea>\n        <button ng-click=\"generateSignature()\">Sign</button>\n      </div>\n    </form>\n    <br/>\n    <br/>\n\n    <div ng-show=\"howToSend != 3\">\n      <input type=\"checkbox\" ng-model=\"generateErrorOnServer\">Return server error with http code: <input type=\"text\"\n                                                                                                         ng-model=\"serverErrorCode\"\n                                                                                                         size=\"5\"> and\n      message: <input type=\"text\" ng-model=\"serverErrorMsg\">\n      <br/>\n    </div>\n    <br/>\n  </div>\n</div>\n<a style=\"position:fixed;bottom:28px;right:10px;font-size:smaller;\" target=\"_blank\"\n   href=\"https://angular-file-upload.appspot.com/donate.html\">donate</a>\n<a style=\"position:fixed;bottom:45px;right:10px;font-size:smaller;\"\n   href=\"https://github.com/danialfarid/angular-file-upload/issues/new\">Feedback/Issues</a>\n</body>\n</html>\n"
  },
  {
    "path": "demo/src/main/webapp/js/FileAPI.js",
    "content": "/*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git\n * 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.\n */\n\n/*\n * JavaScript Canvas to Blob 2.0.5\n * https://github.com/blueimp/JavaScript-Canvas-to-Blob\n *\n * Copyright 2012, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n *\n * Based on stackoverflow user Stoive's code snippet:\n * http://stackoverflow.com/q/4998908\n */\n\n/*jslint nomen: true, regexp: true */\n/*global window, atob, Blob, ArrayBuffer, Uint8Array */\n\n(function (window) {\n    'use strict';\n    var CanvasPrototype = window.HTMLCanvasElement &&\n            window.HTMLCanvasElement.prototype,\n        hasBlobConstructor = window.Blob && (function () {\n            try {\n                return Boolean(new Blob());\n            } catch (e) {\n                return false;\n            }\n        }()),\n        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&\n            (function () {\n                try {\n                    return new Blob([new Uint8Array(100)]).size === 100;\n                } catch (e) {\n                    return false;\n                }\n            }()),\n        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||\n            window.MozBlobBuilder || window.MSBlobBuilder,\n        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&\n            window.ArrayBuffer && window.Uint8Array && function (dataURI) {\n                var byteString,\n                    arrayBuffer,\n                    intArray,\n                    i,\n                    mimeString,\n                    bb;\n                if (dataURI.split(',')[0].indexOf('base64') >= 0) {\n                    // Convert base64 to raw binary data held in a string:\n                    byteString = atob(dataURI.split(',')[1]);\n                } else {\n                    // Convert base64/URLEncoded data component to raw binary data:\n                    byteString = decodeURIComponent(dataURI.split(',')[1]);\n                }\n                // Write the bytes of the string to an ArrayBuffer:\n                arrayBuffer = new ArrayBuffer(byteString.length);\n                intArray = new Uint8Array(arrayBuffer);\n                for (i = 0; i < byteString.length; i += 1) {\n                    intArray[i] = byteString.charCodeAt(i);\n                }\n                // Separate out the mime component:\n                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];\n                // Write the ArrayBuffer (or ArrayBufferView) to a blob:\n                if (hasBlobConstructor) {\n                    return new Blob(\n                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],\n                        {type: mimeString}\n                    );\n                }\n                bb = new BlobBuilder();\n                bb.append(arrayBuffer);\n                return bb.getBlob(mimeString);\n            };\n    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {\n        if (CanvasPrototype.mozGetAsFile) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {\n                    callback(dataURLtoBlob(this.toDataURL(type, quality)));\n                } else {\n                    callback(this.mozGetAsFile('blob', type));\n                }\n            };\n        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                callback(dataURLtoBlob(this.toDataURL(type, quality)));\n            };\n        }\n    }\n    window.dataURLtoBlob = dataURLtoBlob;\n})(window);\n\n/*jslint evil: true */\n/*global window, URL, webkitURL, ActiveXObject */\n\n(function (window, undef){\n\t'use strict';\n\n\tvar\n\t\tgid = 1,\n\t\tnoop = function (){},\n\n\t\tdocument = window.document,\n\t\tdoctype = document.doctype || {},\n\t\tuserAgent = window.navigator.userAgent,\n\n\t\t// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48\n\t\tapiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),\n\n\t\tBlob = window.Blob,\n\t\tFile = window.File,\n\t\tFileReader = window.FileReader,\n\t\tFormData = window.FormData,\n\n\n\t\tXMLHttpRequest = window.XMLHttpRequest,\n\t\tjQuery = window.jQuery,\n\n\t\thtml5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))\n\t\t\t\t&& !(/safari\\//i.test(userAgent) && !/chrome\\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25\n\n\t\tcors = html5 && ('withCredentials' in (new XMLHttpRequest)),\n\n\t\tchunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),\n\n\t\t// https://github.com/blueimp/JavaScript-Canvas-to-Blob\n\t\tdataURLtoBlob = window.dataURLtoBlob,\n\n\n\t\t_rimg = /img/i,\n\t\t_rcanvas = /canvas/i,\n\t\t_rimgcanvas = /img|canvas/i,\n\t\t_rinput = /input/i,\n\t\t_rdata = /^data:[^,]+,/,\n\n\t\t_toString = {}.toString,\n\n\n\t\tMath = window.Math,\n\n\t\t_SIZE_CONST = function (pow){\n\t\t\tpow = new window.Number(Math.pow(1024, pow));\n\t\t\tpow.from = function (sz){ return Math.round(sz * this); };\n\t\t\treturn\tpow;\n\t\t},\n\n\t\t_elEvents = {}, // element event listeners\n\t\t_infoReader = [], // list of file info processors\n\n\t\t_readerEvents = 'abort progress error load loadend',\n\t\t_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),\n\n\t\tcurrentTarget = 'currentTarget', // for minimize\n\t\tpreventDefault = 'preventDefault', // and this too\n\n\t\t_isArray = function (ar) {\n\t\t\treturn\tar && ('length' in ar);\n\t\t},\n\n\t\t/**\n\t\t * Iterate over a object or array\n\t\t */\n\t\t_each = function (obj, fn, ctx){\n\t\t\tif( obj ){\n\t\t\t\tif( _isArray(obj) ){\n\t\t\t\t\tfor( var i = 0, n = obj.length; i < n; i++ ){\n\t\t\t\t\t\tif( i in obj ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[i], i, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfor( var key in obj ){\n\t\t\t\t\t\tif( obj.hasOwnProperty(key) ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[key], key, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Merge the contents of two or more objects together into the first object\n\t\t */\n\t\t_extend = function (dst){\n\t\t\tvar args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };\n\t\t\tfor( ; i < args.length; i++ ){\n\t\t\t\t_each(args[i], _ext);\n\t\t\t}\n\t\t\treturn  dst;\n\t\t},\n\n\t\t/**\n\t\t * Add event listener\n\t\t */\n\t\t_on = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el);\n\n\t\t\t\tif( !_elEvents[uid] ){\n\t\t\t\t\t_elEvents[uid] = {};\n\t\t\t\t}\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.add(el, type, fn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif( !_elEvents[uid][type] ){\n\t\t\t\t\t\t\t_elEvents[uid][type] = [];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t_elEvents[uid][type].push(fn);\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.addEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.attachEvent ){ el.attachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = fn; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t/**\n\t\t * Remove event listener\n\t\t */\n\t\t_off = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el), events = _elEvents[uid] || {};\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.remove(el, type, fn);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar fns = events[type] || [], i = fns.length;\n\n\t\t\t\t\t\twhile( i-- ){\n\t\t\t\t\t\t\tif( fns[i] === fn ){\n\t\t\t\t\t\t\t\tfns.splice(i, 1);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.removeEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.detachEvent ){ el.detachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = null; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t_one = function(el, type, fn){\n\t\t\t_on(el, type, function _(evt){\n\t\t\t\t_off(el, type, _);\n\t\t\t\tfn(evt);\n\t\t\t});\n\t\t},\n\n\n\t\t_fixEvent = function (evt){\n\t\t\tif( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }\n\t\t\tif( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }\n\t\t\treturn  evt;\n\t\t},\n\n\n\t\t_supportInputAttr = function (attr){\n\t\t\tvar input = document.createElement('input');\n\t\t\tinput.setAttribute('type', \"file\");\n\t\t\treturn attr in input;\n\t\t},\n\n\t\t/**\n\t\t * FileAPI (core object)\n\t\t */\n\t\tapi = {\n\t\t\tversion: '2.0.7',\n\n\t\t\tcors: false,\n\t\t\thtml5: true,\n\t\t\tmedia: false,\n\t\t\tformData: true,\n\t\t\tmultiPassResize: true,\n\n\t\t\tdebug: false,\n\t\t\tpingUrl: false,\n\t\t\tmultiFlash: false,\n\t\t\tflashAbortTimeout: 0,\n\t\t\twithCredentials: true,\n\n\t\t\tstaticPath: './dist/',\n\n\t\t\tflashUrl: 0, // @default: './FileAPI.flash.swf'\n\t\t\tflashImageUrl: 0, // @default: './FileAPI.flash.image.swf'\n\n\t\t\tpostNameConcat: function (name, idx){\n\t\t\t\treturn\tname + (idx != null ? '['+ idx +']' : '');\n\t\t\t},\n\n\t\t\text2mime: {\n\t\t\t\t  jpg:\t'image/jpeg'\n\t\t\t\t, tif:\t'image/tiff'\n\t\t\t\t, txt:\t'text/plain'\n\t\t\t},\n\n\t\t\t// Fallback for flash\n\t\t\taccept: {\n\t\t\t\t  'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'\n\t\t\t\t, 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'\n\t\t\t\t, 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'\n\t\t\t},\n\n\t\t\tuploadRetry : 0,\n\t\t\tnetworkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down\n\n\t\t\tchunkSize : 0,\n\t\t\tchunkUploadRetry : 0,\n\t\t\tchunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down\n\n\t\t\tKB: _SIZE_CONST(1),\n\t\t\tMB: _SIZE_CONST(2),\n\t\t\tGB: _SIZE_CONST(3),\n\t\t\tTB: _SIZE_CONST(4),\n\n\t\t\tEMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',\n\n\t\t\texpando: 'fileapi' + (new Date).getTime(),\n\n\t\t\tuid: function (obj){\n\t\t\t\treturn\tobj\n\t\t\t\t\t? (obj[api.expando] = obj[api.expando] || api.uid())\n\t\t\t\t\t: (++gid, api.expando + gid)\n\t\t\t\t;\n\t\t\t},\n\n\t\t\tlog: function (){\n\t\t\t\t// ngf fix for IE8 #1071\n\t\t\t\tif( api.debug && api._supportConsoleLog ){\n\t\t\t\t\tif( api._supportConsoleLogApply ){\n\t\t\t\t\t\tconsole.log.apply(console, arguments);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tconsole.log([].join.call(arguments, ' '));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Create new image\n\t\t\t *\n\t\t\t * @param {String} [src]\n\t\t\t * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element\n\t\t\t * @returns {HTMLElement}\n\t\t\t */\n\t\t\tnewImage: function (src, fn){\n\t\t\t\tvar img = document.createElement('img');\n\t\t\t\tif( fn ){\n\t\t\t\t\tapi.event.one(img, 'error load', function (evt){\n\t\t\t\t\t\tfn(evt.type == 'error', img);\n\t\t\t\t\t\timg = null;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\timg.src = src;\n\t\t\t\treturn\timg;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Get XHR\n\t\t\t * @returns {XMLHttpRequest}\n\t\t\t */\n\t\t\tgetXHR: function (){\n\t\t\t\tvar xhr;\n\n\t\t\t\tif( XMLHttpRequest ){\n\t\t\t\t\txhr = new XMLHttpRequest;\n\t\t\t\t}\n\t\t\t\telse if( window.ActiveXObject ){\n\t\t\t\t\ttry {\n\t\t\t\t\t\txhr = new ActiveXObject('MSXML2.XMLHttp.3.0');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\txhr = new ActiveXObject('Microsoft.XMLHTTP');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tisArray: _isArray,\n\n\t\t\tsupport: {\n\t\t\t\tdnd:     cors && ('ondrop' in document.createElement('div')),\n\t\t\t\tcors:    cors,\n\t\t\t\thtml5:   html5,\n\t\t\t\tchunked: chunked,\n\t\t\t\tdataURI: true,\n\t\t\t\taccept:   _supportInputAttr('accept'),\n\t\t\t\tmultiple: _supportInputAttr('multiple')\n\t\t\t},\n\n\t\t\tevent: {\n\t\t\t\t  on: _on\n\t\t\t\t, off: _off\n\t\t\t\t, one: _one\n\t\t\t\t, fix: _fixEvent\n\t\t\t},\n\n\n\t\t\tthrottle: function(fn, delay) {\n\t\t\t\tvar id, args;\n\n\t\t\t\treturn function _throttle(){\n\t\t\t\t\targs = arguments;\n\n\t\t\t\t\tif( !id ){\n\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\tid = setTimeout(function (){\n\t\t\t\t\t\t\tid = 0;\n\t\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\t}, delay);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\n\t\t\tF: function (){},\n\n\n\t\t\tparseJSON: function (str){\n\t\t\t\tvar json;\n\t\t\t\tif( window.JSON && JSON.parse ){\n\t\t\t\t\tjson = JSON.parse(str);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tjson = (new Function('return ('+str.replace(/([\\r\\n])/g, '\\\\$1')+');'))();\n\t\t\t\t}\n\t\t\t\treturn json;\n\t\t\t},\n\n\n\t\t\ttrim: function (str){\n\t\t\t\tstr = String(str);\n\t\t\t\treturn\tstr.trim ? str.trim() : str.replace(/^\\s+|\\s+$/g, '');\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Simple Defer\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\tdefer: function (){\n\t\t\t\tvar\n\t\t\t\t\t  list = []\n\t\t\t\t\t, result\n\t\t\t\t\t, error\n\t\t\t\t\t, defer = {\n\t\t\t\t\t\tresolve: function (err, res){\n\t\t\t\t\t\t\tdefer.resolve = noop;\n\t\t\t\t\t\t\terror\t= err || false;\n\t\t\t\t\t\t\tresult\t= res;\n\n\t\t\t\t\t\t\twhile( res = list.shift() ){\n\t\t\t\t\t\t\t\tres(error, result);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tthen: function (fn){\n\t\t\t\t\t\t\tif( error !== undef ){\n\t\t\t\t\t\t\t\tfn(error, result);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlist.push(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\treturn\tdefer;\n\t\t\t},\n\n\t\t\tqueue: function (fn){\n\t\t\t\tvar\n\t\t\t\t\t  _idx = 0\n\t\t\t\t\t, _length = 0\n\t\t\t\t\t, _fail = false\n\t\t\t\t\t, _end = false\n\t\t\t\t\t, queue = {\n\t\t\t\t\t\tinc: function (){\n\t\t\t\t\t\t\t_length++;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tnext: function (){\n\t\t\t\t\t\t\t_idx++;\n\t\t\t\t\t\t\tsetTimeout(queue.check, 0);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tcheck: function (){\n\t\t\t\t\t\t\t(_idx >= _length) && !_fail && queue.end();\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tisFail: function (){\n\t\t\t\t\t\t\treturn _fail;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tfail: function (){\n\t\t\t\t\t\t\t!_fail && fn(_fail = true);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tend: function (){\n\t\t\t\t\t\t\tif( !_end ){\n\t\t\t\t\t\t\t\t_end = true;\n\t\t\t\t\t\t\t\tfn();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t;\n\t\t\t\treturn queue;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * For each object\n\t\t\t *\n\t\t\t * @param\t{Object|Array}\tobj\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{*}\t\t\t\t[ctx]\n\t\t\t */\n\t\t\teach: _each,\n\n\n\t\t\t/**\n\t\t\t * Async for\n\t\t\t * @param {Array} array\n\t\t\t * @param {Function} callback\n\t\t\t */\n\t\t\tafor: function (array, callback){\n\t\t\t\tvar i = 0, n = array.length;\n\n\t\t\t\tif( _isArray(array) && n-- ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tcallback(n != i && _next, array[i], i++);\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Merge the contents of two or more objects together into the first object\n\t\t\t *\n\t\t\t * @param\t{Object}\tdst\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\textend: _extend,\n\n\n\t\t\t/**\n\t\t\t * Is file?\n\t\t\t * @param  {File}  file\n\t\t\t * @return {Boolean}\n\t\t\t */\n\t\t\tisFile: function (file){\n\t\t\t\treturn _toString.call(file) === '[object File]';\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is blob?\n\t\t\t * @param   {Blob}  blob\n\t\t\t * @returns {Boolean}\n\t\t\t */\n\t\t\tisBlob: function (blob) {\n\t\t\t\treturn this.isFile(blob) || (_toString.call(blob) === '[object Blob]');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is canvas element\n\t\t\t *\n\t\t\t * @param\t{HTMLElement}\tel\n\t\t\t * @return\t{Boolean}\n\t\t\t */\n\t\t\tisCanvas: function (el){\n\t\t\t\treturn\tel && _rcanvas.test(el.nodeName);\n\t\t\t},\n\n\n\t\t\tgetFilesFilter: function (filter){\n\t\t\t\tfilter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');\n\t\t\t\treturn\tfilter ? new RegExp('('+ filter.replace(/\\./g, '\\\\.').replace(/,/g, '|') +')$', 'i') : /./;\n\t\t\t},\n\n\n\n\t\t\t/**\n\t\t\t * Read as DataURL\n\t\t\t *\n\t\t\t * @param {File|Element} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsDataURL: function (file, fn){\n\t\t\t\tif( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', api.toDataURL(file));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_readAs(file, fn, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as Binary string\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsBinaryString: function (file, fn){\n\t\t\t\tif( _hasSupportReadAs('BinaryString') ){\n\t\t\t\t\t_readAs(file, fn, 'BinaryString');\n\t\t\t\t} else {\n\t\t\t\t\t// Hello IE10!\n\t\t\t\t\t_readAs(file, function (evt){\n\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// dataURL -> binaryString\n\t\t\t\t\t\t\t\tevt.result = api.toBinaryString(evt.result);\n\t\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\tevt.message = e.toString();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfn(evt);\n\t\t\t\t\t}, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as ArrayBuffer\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsArrayBuffer: function(file, fn){\n\t\t\t\t_readAs(file, fn, 'ArrayBuffer');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as text\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {String} encoding\n\t\t\t * @param {Function} [fn]\n\t\t\t */\n\t\t\treadAsText: function(file, encoding, fn){\n\t\t\t\tif( !fn ){\n\t\t\t\t\tfn\t= encoding;\n\t\t\t\t\tencoding = 'utf-8';\n\t\t\t\t}\n\n\t\t\t\t_readAs(file, fn, 'Text', encoding);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Convert image or canvas to DataURL\n\t\t\t *\n\t\t\t * @param   {Element}  el      Image or Canvas element\n\t\t\t * @param   {String}   [type]  mime-type\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoDataURL: function (el, type){\n\t\t\t\tif( typeof el == 'string' ){\n\t\t\t\t\treturn  el;\n\t\t\t\t}\n\t\t\t\telse if( el.toDataURL ){\n\t\t\t\t\treturn  el.toDataURL(type || 'image/png');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Canvert string, image or canvas to binary string\n\t\t\t *\n\t\t\t * @param   {String|Element} val\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoBinaryString: function (val){\n\t\t\t\treturn  window.atob(api.toDataURL(val).replace(_rdata, ''));\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read file or DataURL as ImageElement\n\t\t\t *\n\t\t\t * @param\t{File|String}\tfile\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{Boolean}\t\t[progress]\n\t\t\t */\n\t\t\treadAsImage: function (file, fn, progress){\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\tif( apiURL ){\n\t\t\t\t\t\t/** @namespace apiURL.createObjectURL */\n\t\t\t\t\t\tvar data = apiURL.createObjectURL(file);\n\t\t\t\t\t\tif( data === undef ){\n\t\t\t\t\t\t\t_emit(file, fn, 'error');\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tapi.readAsImage(data, fn, progress);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\tapi.readAsImage(evt.result, fn, progress);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( progress || evt.type == 'error' ){\n\t\t\t\t\t\t\t\t_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t}\n\t\t\t\telse if( _rimg.test(file.nodeName) ){\n\t\t\t\t\tif( file.complete ){\n\t\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar events = 'error abort load';\n\t\t\t\t\t\t_one(file, events, function _fn(evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' && apiURL ){\n\t\t\t\t\t\t\t\t/** @namespace apiURL.revokeObjectURL */\n\t\t\t\t\t\t\t\tapiURL.revokeObjectURL(file.src);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_off(file, events, _fn);\n\t\t\t\t\t\t\t_emit(file, fn, evt, file);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( file.iframe ){\n\t\t\t\t\t_emit(file, fn, { type: 'error' });\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Created image\n\t\t\t\t\tvar img = api.newImage(file.dataURL || file);\n\t\t\t\t\tapi.readAsImage(img, fn, progress);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Make file by name\n\t\t\t *\n\t\t\t * @param\t{String}\tname\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tcheckFileObj: function (name){\n\t\t\t\tvar file = {}, accept = api.accept;\n\n\t\t\t\tif( typeof name == 'object' ){\n\t\t\t\t\tfile = name;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfile.name = (name + '').split(/\\\\|\\//g).pop();\n\t\t\t\t}\n\n\t\t\t\tif( file.type == null ){\n\t\t\t\t\tfile.type = file.name.split('.').pop();\n\t\t\t\t}\n\n\t\t\t\t_each(accept, function (ext, type){\n\t\t\t\t\text = new RegExp(ext.replace(/\\s/g, '|'), 'i');\n\t\t\t\t\tif( ext.test(file.type) || api.ext2mime[file.type] ){\n\t\t\t\t\t\tfile.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn\tfile;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get drop files\n\t\t\t *\n\t\t\t * @param\t{Event}\tevt\n\t\t\t * @param\t{Function} callback\n\t\t\t */\n\t\t\tgetDropFiles: function (evt, callback){\n\t\t\t\tvar\n\t\t\t\t\t  files = []\n\t\t\t\t\t, dataTransfer = _getDataTransfer(evt)\n\t\t\t\t\t, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])\n\t\t\t\t\t, queue = api.queue(function (){ callback(files); })\n\t\t\t\t;\n\n\t\t\t\t_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){\n\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( entrySupport ){\n\t\t\t\t\t\t\t_readEntryAsFiles(item, function (err, entryFiles){\n\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\tapi.log('[err] getDropFiles:', err);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfiles.push.apply(files, entryFiles);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t_isRegularFile(item, function (yes){\n\t\t\t\t\t\t\t\tyes && files.push(item);\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcatch( err ){\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\tapi.log('[err] getDropFiles: ', err);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get file list\n\t\t\t *\n\t\t\t * @param\t{HTMLInputElement|Event}\tinput\n\t\t\t * @param\t{String|Function}\t[filter]\n\t\t\t * @param\t{Function}\t\t\t[callback]\n\t\t\t * @return\t{Array|Null}\n\t\t\t */\n\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\tvar files = [];\n\n\t\t\t\tif( callback ){\n\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tif( input.jquery ){\n\t\t\t\t\t// jQuery object\n\t\t\t\t\tinput.each(function (){\n\t\t\t\t\t\tfiles = files.concat(api.getFiles(this));\n\t\t\t\t\t});\n\t\t\t\t\tinput\t= files;\n\t\t\t\t\tfiles\t= [];\n\t\t\t\t}\n\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t}\n\n\t\t\t\tif( input.originalEvent ){\n\t\t\t\t\t// jQuery event\n\t\t\t\t\tinput = _fixEvent(input.originalEvent);\n\t\t\t\t}\n\t\t\t\telse if( input.srcElement ){\n\t\t\t\t\t// IE Event\n\t\t\t\t\tinput = _fixEvent(input);\n\t\t\t\t}\n\n\n\t\t\t\tif( input.dataTransfer ){\n\t\t\t\t\t// Drag'n'Drop\n\t\t\t\t\tinput = input.dataTransfer;\n\t\t\t\t}\n\t\t\t\telse if( input.target ){\n\t\t\t\t\t// Event\n\t\t\t\t\tinput = input.target;\n\t\t\t\t}\n\n\t\t\t\tif( input.files ){\n\t\t\t\t\t// Input[type=\"file\"]\n\t\t\t\t\tfiles = input.files;\n\n\t\t\t\t\tif( !html5 ){\n\t\t\t\t\t\t// Partial support for file api\n\t\t\t\t\t\tfiles[0].blob\t= input;\n\t\t\t\t\t\tfiles[0].iframe\t= true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( !html5 && isInputFile(input) ){\n\t\t\t\t\tif( api.trim(input.value) ){\n\t\t\t\t\t\tfiles = [api.checkFileObj(input.value)];\n\t\t\t\t\t\tfiles[0].blob   = input;\n\t\t\t\t\t\tfiles[0].iframe = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( _isArray(input) ){\n\t\t\t\t\tfiles\t= input;\n\t\t\t\t}\n\n\t\t\t\treturn\tapi.filter(files, function (file){ return !filter || filter.test(file.name); });\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get total file size\n\t\t\t * @param\t{Array}\tfiles\n\t\t\t * @return\t{Number}\n\t\t\t */\n\t\t\tgetTotalSize: function (files){\n\t\t\t\tvar size = 0, i = files && files.length;\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tsize += files[i].size;\n\t\t\t\t}\n\t\t\t\treturn\tsize;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get image information\n\t\t\t *\n\t\t\t * @param\t{File}\t\tfile\n\t\t\t * @param\t{Function}\tfn\n\t\t\t */\n\t\t\tgetInfo: function (file, fn){\n\t\t\t\tvar info = {}, readers = _infoReader.concat();\n\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tvar reader = readers.shift();\n\t\t\t\t\t\tif( reader ){\n\t\t\t\t\t\t\tif( reader.test(file.type) ){\n\t\t\t\t\t\t\t\treader(file, function (err, res){\n\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t_extend(info, res);\n\t\t\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tfn(false, info);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfn('not_support_info', info);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Add information reader\n\t\t\t *\n\t\t\t * @param {RegExp} mime\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\taddInfoReader: function (mime, fn){\n\t\t\t\tfn.test = function (type){ return mime.test(type); };\n\t\t\t\t_infoReader.push(fn);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter of array\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tinput\n\t\t\t * @param\t{Function}\tfn\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tfilter: function (input, fn){\n\t\t\t\tvar result = [], i = 0, n = input.length, val;\n\n\t\t\t\tfor( ; i < n; i++ ){\n\t\t\t\t\tif( i in input ){\n\t\t\t\t\t\tval = input[i];\n\t\t\t\t\t\tif( fn.call(val, val, i, input) ){\n\t\t\t\t\t\t\tresult.push(val);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn\tresult;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter files\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tfiles\n\t\t\t * @param\t{Function}\teachFn\n\t\t\t * @param\t{Function}\tresultFn\n\t\t\t */\n\t\t\tfilterFiles: function (files, eachFn, resultFn){\n\t\t\t\tif( files.length ){\n\t\t\t\t\t// HTML5 or Flash\n\t\t\t\t\tvar queue = files.concat(), file, result = [], deleted = [];\n\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tif( queue.length ){\n\t\t\t\t\t\t\tfile = queue.shift();\n\t\t\t\t\t\t\tapi.getInfo(file, function (err, info){\n\t\t\t\t\t\t\t\t(eachFn(file, err ? false : info) ? result : deleted).push(file);\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresultFn(result, deleted);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tresultFn([], files);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\tupload: function (options){\n\t\t\t\toptions = _extend({\n\t\t\t\t\t  jsonp: 'callback'\n\t\t\t\t\t, prepare: api.F\n\t\t\t\t\t, beforeupload: api.F\n\t\t\t\t\t, upload: api.F\n\t\t\t\t\t, fileupload: api.F\n\t\t\t\t\t, fileprogress: api.F\n\t\t\t\t\t, filecomplete: api.F\n\t\t\t\t\t, progress: api.F\n\t\t\t\t\t, complete: api.F\n\t\t\t\t\t, pause: api.F\n\t\t\t\t\t, imageOriginal: true\n\t\t\t\t\t, chunkSize: api.chunkSize\n\t\t\t\t\t, chunkUploadRetry: api.chunkUploadRetry\n\t\t\t\t\t, uploadRetry: api.uploadRetry\n\t\t\t\t}, options);\n\n\n\t\t\t\tif( options.imageAutoOrientation && !options.imageTransform ){\n\t\t\t\t\toptions.imageTransform = { rotate: 'auto' };\n\t\t\t\t}\n\n\n\t\t\t\tvar\n\t\t\t\t\t  proxyXHR = new api.XHR(options)\n\t\t\t\t\t, dataArray = this._getFilesDataArray(options.files)\n\t\t\t\t\t, _this = this\n\t\t\t\t\t, _total = 0\n\t\t\t\t\t, _loaded = 0\n\t\t\t\t\t, _nextFile\n\t\t\t\t\t, _complete = false\n\t\t\t\t;\n\n\n\t\t\t\t// calc total size\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\t_total += data.size;\n\t\t\t\t});\n\n\t\t\t\t// Array of files\n\t\t\t\tproxyXHR.files = [];\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t});\n\n\t\t\t\t// Set upload status props\n\t\t\t\tproxyXHR.total\t= _total;\n\t\t\t\tproxyXHR.loaded\t= 0;\n\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t// emit \"beforeupload\"  event\n\t\t\t\toptions.beforeupload(proxyXHR, options);\n\n\t\t\t\t// Upload by file\n\t\t\t\t_nextFile = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  data = dataArray.shift()\n\t\t\t\t\t\t, _file = data && data.file\n\t\t\t\t\t\t, _fileLoaded = false\n\t\t\t\t\t\t, _fileOptions = _simpleClone(options)\n\t\t\t\t\t;\n\n\t\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t\tif( _file && _file.name === api.expando ){\n\t\t\t\t\t\t_file = null;\n\t\t\t\t\t\tapi.log('[warn] FileAPI.upload() — called without files');\n\t\t\t\t\t}\n\n\t\t\t\t\tif( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){\n\t\t\t\t\t\t// Mark active job\n\t\t\t\t\t\t_complete = false;\n\n\t\t\t\t\t\t// Set current upload file\n\t\t\t\t\t\tproxyXHR.currentFile = _file;\n\n\t\t\t\t\t\t// Prepare file options\n\t\t\t\t\t\tif (_file && options.prepare(_file, _fileOptions) === false) {\n\t\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_fileOptions.file = _file;\n\n\t\t\t\t\t\t_this._getFormData(_fileOptions, data, function (form){\n\t\t\t\t\t\t\tif( !_loaded ){\n\t\t\t\t\t\t\t\t// emit \"upload\" event\n\t\t\t\t\t\t\t\toptions.upload(proxyXHR, options);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar xhr = new api.XHR(_extend({}, _fileOptions, {\n\n\t\t\t\t\t\t\t\tupload: _file ? function (){\n\t\t\t\t\t\t\t\t\t// emit \"fileupload\" event\n\t\t\t\t\t\t\t\t\toptions.fileupload(_file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tprogress: _file ? function (evt){\n\t\t\t\t\t\t\t\t\tif( !_fileLoaded ){\n\t\t\t\t\t\t\t\t\t\t// For ignore the double calls.\n\t\t\t\t\t\t\t\t\t\t_fileLoaded = (evt.loaded === evt.total);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"fileprogress\" event\n\t\t\t\t\t\t\t\t\t\toptions.fileprogress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  data.total = evt.total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: data.loaded = evt.loaded\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"progress\" event\n\t\t\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  _total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tcomplete: function (err){\n\t\t\t\t\t\t\t\t\t_each(_xhrPropsExport, function (name){\n\t\t\t\t\t\t\t\t\t\tproxyXHR[name] = xhr[name];\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tif( _file ){\n\t\t\t\t\t\t\t\t\t\tdata.total = (data.total || data.size);\n\t\t\t\t\t\t\t\t\t\tdata.loaded\t= data.total;\n\n\t\t\t\t\t\t\t\t\t\tif( !err ) {\n\t\t\t\t\t\t\t\t\t\t\t// emulate 100% \"progress\"\n\t\t\t\t\t\t\t\t\t\t\tthis.progress(data);\n\n\t\t\t\t\t\t\t\t\t\t\t// fixed throttle event\n\t\t\t\t\t\t\t\t\t\t\t_fileLoaded = true;\n\n\t\t\t\t\t\t\t\t\t\t\t// bytes loaded\n\t\t\t\t\t\t\t\t\t\t\t_loaded += data.size; // data.size != data.total, it's desirable fix this\n\t\t\t\t\t\t\t\t\t\t\tproxyXHR.loaded = _loaded;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// emit \"filecomplete\" event\n\t\t\t\t\t\t\t\t\t\toptions.filecomplete(err, xhr, _file, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// upload next file\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {_nextFile.call(_this);}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})); // xhr\n\n\n\t\t\t\t\t\t\t// ...\n\t\t\t\t\t\t\tproxyXHR.abort = function (current){\n\t\t\t\t\t\t\t\tif (!current) { dataArray.length = 0; }\n\t\t\t\t\t\t\t\tthis.current = current;\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Start upload\n\t\t\t\t\t\t\txhr.send(form);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;\n\t\t\t\t\t\toptions.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);\n\t\t\t\t\t\t// Mark done state\n\t\t\t\t\t\t_complete = true;\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Next tick\n\t\t\t\tsetTimeout(_nextFile, 0);\n\n\n\t\t\t\t// Append more files to the existing request\n\t\t\t\t// first - add them to the queue head/tail\n\t\t\t\tproxyXHR.append = function (files, first) {\n\t\t\t\t\tfiles = api._getFilesDataArray([].concat(files));\n\n\t\t\t\t\t_each(files, function (data) {\n\t\t\t\t\t\t_total += data.size;\n\t\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\tdataArray.unshift(data);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdataArray.push(data);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tproxyXHR.statusText = \"\";\n\n\t\t\t\t\tif( _complete ){\n\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Removes file from queue by file reference and returns it\n\t\t\t\tproxyXHR.remove = function (file) {\n\t\t\t\t    var i = dataArray.length, _file;\n\t\t\t\t    while( i-- ){\n\t\t\t\t\t\tif( dataArray[i].file == file ){\n\t\t\t\t\t\t\t_file = dataArray.splice(i, 1);\n\t\t\t\t\t\t\t_total -= _file.size;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn\t_file;\n\t\t\t\t};\n\n\t\t\t\treturn proxyXHR;\n\t\t\t},\n\n\n\t\t\t_getFilesDataArray: function (data){\n\t\t\t\tvar files = [], oFiles = {};\n\n\t\t\t\tif( isInputFile(data) ){\n\t\t\t\t\tvar tmp = api.getFiles(data);\n\t\t\t\t\toFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];\n\t\t\t\t}\n\t\t\t\telse if( _isArray(data) && isInputFile(data[0]) ){\n\t\t\t\t\t_each(data, function (input){\n\t\t\t\t\t\toFiles[input.name || 'file'] = api.getFiles(input);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\toFiles = data;\n\t\t\t\t}\n\n\t\t\t\t_each(oFiles, function add(file, name){\n\t\t\t\t\tif( _isArray(file) ){\n\t\t\t\t\t\t_each(file, function (file){\n\t\t\t\t\t\t\tadd(file, name);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( file && (file.name || file.image) ){\n\t\t\t\t\t\tfiles.push({\n\t\t\t\t\t\t\t  name: name\n\t\t\t\t\t\t\t, file: file\n\t\t\t\t\t\t\t, size: file.size\n\t\t\t\t\t\t\t, total: file.size\n\t\t\t\t\t\t\t, loaded: 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif( !files.length ){\n\t\t\t\t\t// Create fake `file` object\n\t\t\t\t\tfiles.push({ file: { name: api.expando } });\n\t\t\t\t}\n\n\t\t\t\treturn\tfiles;\n\t\t\t},\n\n\n\t\t\t_getFormData: function (options, data, fn){\n\t\t\t\tvar\n\t\t\t\t\t  file = data.file\n\t\t\t\t\t, name = data.name\n\t\t\t\t\t, filename = file.name\n\t\t\t\t\t, filetype = file.type\n\t\t\t\t\t, trans = api.support.transform && options.imageTransform\n\t\t\t\t\t, Form = new api.Form\n\t\t\t\t\t, queue = api.queue(function (){ fn(Form); })\n\t\t\t\t\t, isOrignTrans = trans && _isOriginTransform(trans)\n\t\t\t\t\t, postNameConcat = api.postNameConcat\n\t\t\t\t;\n\n\t\t\t\t// Append data\n\t\t\t\t_each(options.data, function add(val, name){\n\t\t\t\t\tif( typeof val == 'object' ){\n\t\t\t\t\t\t_each(val, function (v, i){\n\t\t\t\t\t\t\tadd(v, postNameConcat(name, i));\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tForm.append(name, val);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t(function _addFile(file/**Object*/){\n\t\t\t\t\tif( file.image ){ // This is a FileAPI.Image\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tfile.toData(function (err, image){\n\t\t\t\t\t\t\t// @todo: error\n\t\t\t\t\t\t\tfilename = filename || (new Date).getTime()+'.png';\n\n\t\t\t\t\t\t\t_addFile(image);\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tif( isOrignTrans ){\n\t\t\t\t\t\t\t// Convert to array for transform function\n\t\t\t\t\t\t\ttrans = [trans];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tapi.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){\n\t\t\t\t\t\t\tif( isOrignTrans && !err ){\n\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t// Canvas.toBlob or Flash not supported, use multipart\n\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tForm.append(name, images[0], filename,  trans[0].type || filetype);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar addOrigin = 0;\n\n\t\t\t\t\t\t\t\tif( !err ){\n\t\t\t\t\t\t\t\t\t_each(images, function (image, idx){\n\t\t\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif( !trans[idx].postName ){\n\t\t\t\t\t\t\t\t\t\t\taddOrigin = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tForm.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif( err || options.imageOriginal ){\n\t\t\t\t\t\t\t\t\tForm.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( filename !== api.expando ){\n\t\t\t\t\t\tForm.append(name, file, filename);\n\t\t\t\t\t}\n\t\t\t\t})(file);\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\treset: function (inp, notRemove){\n\t\t\t\tvar parent, clone;\n\n\t\t\t\tif( jQuery ){\n\t\t\t\t\tclone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tjQuery(inp).remove();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tparent  = inp.parentNode;\n\t\t\t\t\tclone   = parent.insertBefore(inp.cloneNode(true), inp);\n\t\t\t\t\tclone.value = '';\n\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tparent.removeChild(inp);\n\t\t\t\t\t}\n\n\t\t\t\t\t_each(_elEvents[api.uid(inp)], function (fns, type){\n\t\t\t\t\t\t_each(fns, function (fn){\n\t\t\t\t\t\t\t_off(inp, type, fn);\n\t\t\t\t\t\t\t_on(clone, type, fn);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn  clone;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Load remote file\n\t\t\t *\n\t\t\t * @param   {String}    url\n\t\t\t * @param   {Function}  fn\n\t\t\t * @return  {XMLHttpRequest}\n\t\t\t */\n\t\t\tload: function (url, fn){\n\t\t\t\tvar xhr = api.getXHR();\n\t\t\t\tif( xhr ){\n\t\t\t\t\txhr.open('GET', url, true);\n\n\t\t\t\t\tif( xhr.overrideMimeType ){\n\t\t\t\t        xhr.overrideMimeType('text/plain; charset=x-user-defined');\n\t\t\t\t\t}\n\n\t\t\t\t\t_on(xhr, 'progress', function (/**Event*/evt){\n\t\t\t\t\t\t/** @namespace evt.lengthComputable */\n\t\t\t\t\t\tif( evt.lengthComputable ){\n\t\t\t\t\t\t\tfn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\txhr.onreadystatechange = function(){\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\t\t\t\t\t\t\tif( xhr.status == 200 ){\n\t\t\t\t\t\t\t\turl = url.split('/');\n\t\t\t\t\t\t\t\t/** @namespace xhr.responseBody */\n\t\t\t\t\t\t\t\tvar file = {\n\t\t\t\t\t\t\t\t      name: url[url.length-1]\n\t\t\t\t\t\t\t\t\t, size: xhr.getResponseHeader('Content-Length')\n\t\t\t\t\t\t\t\t\t, type: xhr.getResponseHeader('Content-Type')\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfile.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);\n\t\t\t\t\t\t\t\tfn({ type: 'load', result: file }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tfn({ type: 'error' }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t    }\n\t\t\t\t\t};\n\t\t\t\t    xhr.send(null);\n\t\t\t\t} else {\n\t\t\t\t\tfn({ type: 'error' });\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tencode64: function (str){\n\t\t\t\tvar b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;\n\n\t\t\t\tif( typeof str !== 'string' ){\n\t\t\t\t\tstr\t= String(str);\n\t\t\t\t}\n\n\t\t\t\twhile( i < str.length ){\n\t\t\t\t\t//all three \"& 0xff\" added below are there to fix a known bug\n\t\t\t\t\t//with bytes returned by xhr.responseText\n\t\t\t\t\tvar\n\t\t\t\t\t\t  byte1 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte2 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte3 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, enc1 = byte1 >> 2\n\t\t\t\t\t\t, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)\n\t\t\t\t\t\t, enc3, enc4\n\t\t\t\t\t;\n\n\t\t\t\t\tif( isNaN(byte2) ){\n\t\t\t\t\t\tenc3 = enc4 = 64;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tenc3 = ((byte2 & 15) << 2) | (byte3 >> 6);\n\t\t\t\t\t\tenc4 = isNaN(byte3) ? 64 : byte3 & 63;\n\t\t\t\t\t}\n\n\t\t\t\t\toutStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);\n\t\t\t\t}\n\n\t\t\t\treturn  outStr;\n\t\t\t}\n\n\t\t} // api\n\t;\n\n\n\tfunction _emit(target, fn, name, res, ext){\n\t\tvar evt = {\n\t\t\t  type:\t\tname.type || name\n\t\t\t, target:\ttarget\n\t\t\t, result:\tres\n\t\t};\n\t\t_extend(evt, ext);\n\t\tfn(evt);\n\t}\n\n\n\tfunction _hasSupportReadAs(as){\n\t\treturn\tFileReader && !!FileReader.prototype['readAs'+as];\n\t}\n\n\n\tfunction _readAs(file, fn, as, encoding){\n\t\tif( api.isBlob(file) && _hasSupportReadAs(as) ){\n\t\t\tvar Reader = new FileReader;\n\n\t\t\t// Add event listener\n\t\t\t_on(Reader, _readerEvents, function _fn(evt){\n\t\t\t\tvar type = evt.type;\n\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t}\n\t\t\t\telse if( type == 'loadend' ){\n\t\t\t\t\t_off(Reader, _readerEvents, _fn);\n\t\t\t\t\tReader = null;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\ttry {\n\t\t\t\t// ReadAs ...\n\t\t\t\tif( encoding ){\n\t\t\t\t\tReader['readAs'+as](file, encoding);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tReader['readAs'+as](file);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (err){\n\t\t\t\t_emit(file, fn, 'error', undef, { error: err.toString() });\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });\n\t\t}\n\t}\n\n\n\tfunction _isRegularFile(file, callback){\n\t\t// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects\n\t\tif( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){\n\t\t\tif( FileReader ){\n\t\t\t\ttry {\n\t\t\t\t\tvar Reader = new FileReader();\n\n\t\t\t\t\t_one(Reader, _readerEvents, function (evt){\n\t\t\t\t\t\tvar isFile = evt.type != 'error';\n\t\t\t\t\t\tcallback(isFile);\n\t\t\t\t\t\tif( isFile ){\n\t\t\t\t\t\t\tReader.abort();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tReader.readAsDataURL(file);\n\t\t\t\t} catch( err ){\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcallback(null);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcallback(true);\n\t\t}\n\t}\n\n\n\tfunction _getAsEntry(item){\n\t\tvar entry;\n\t\tif( item.getAsEntry ){ entry = item.getAsEntry(); }\n\t\telse if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }\n\t\treturn\tentry;\n\t}\n\n\n\tfunction _readEntryAsFiles(entry, callback){\n\t\tif( !entry ){\n\t\t\t// error\n\t\t\tcallback('invalid entry');\n\t\t}\n\t\telse if( entry.isFile ){\n\t\t\t// Read as file\n\t\t\tentry.file(function(file){\n\t\t\t\t// success\n\t\t\t\tfile.fullPath = entry.fullPath;\n\t\t\t\tcallback(false, [file]);\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('FileError.code: '+err.code);\n\t\t\t});\n\t\t}\n\t\telse if( entry.isDirectory ){\n\t\t\tvar reader = entry.createReader(), result = [];\n\n\t\t\treader.readEntries(function(entries){\n\t\t\t\t// success\n\t\t\t\tapi.afor(entries, function (next, entry){\n\t\t\t\t\t_readEntryAsFiles(entry, function (err, files){\n\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\tapi.log(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresult = result.concat(files);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( next ){\n\t\t\t\t\t\t\tnext();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tcallback(false, result);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('directory_reader: ' + err);\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\t_readEntryAsFiles(_getAsEntry(entry), callback);\n\t\t}\n\t}\n\n\n\tfunction _simpleClone(obj){\n\t\tvar copy = {};\n\t\t_each(obj, function (val, key){\n\t\t\tif( val && (typeof val === 'object') && (val.nodeType === void 0) ){\n\t\t\t\tval = _extend({}, val);\n\t\t\t}\n\t\t\tcopy[key] = val;\n\t\t});\n\t\treturn\tcopy;\n\t}\n\n\n\tfunction isInputFile(el){\n\t\treturn\t_rinput.test(el && el.tagName);\n\t}\n\n\n\tfunction _getDataTransfer(evt){\n\t\treturn\t(evt.originalEvent || evt || '').dataTransfer || {};\n\t}\n\n\n\tfunction _isOriginTransform(trans){\n\t\tvar key;\n\t\tfor( key in trans ){\n\t\t\tif( trans.hasOwnProperty(key) ){\n\t\t\t\tif( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){\n\t\t\t\t\treturn\ttrue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn\tfalse;\n\t}\n\n\n\t// Add default image info reader\n\tapi.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){\n\t\tif( !file.__dimensions ){\n\t\t\tvar defer = file.__dimensions = api.defer();\n\n\t\t\tapi.readAsImage(file, function (evt){\n\t\t\t\tvar img = evt.target;\n\t\t\t\tdefer.resolve(evt.type == 'load' ? false : 'error', {\n\t\t\t\t\t  width:  img.width\n\t\t\t\t\t, height: img.height\n\t\t\t\t});\n                img.src = api.EMPTY_PNG;\n\t\t\t\timg = null;\n\t\t\t});\n\t\t}\n\n\t\tfile.__dimensions.then(callback);\n\t});\n\n\n\t/**\n\t * Drag'n'Drop special event\n\t *\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd = function (el, onHover, onDrop){\n\t\tvar _id, _type;\n\n\t\tif( !onDrop ){\n\t\t\tonDrop = onHover;\n\t\t\tonHover = api.F;\n\t\t}\n\n\t\tif( FileReader ){\n\t\t\t// Hover\n\t\t\t_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){\n\t\t\t\tvar\n\t\t\t\t\t  types = _getDataTransfer(evt).types\n\t\t\t\t\t, i = types && types.length\n\t\t\t\t\t, debounceTrigger = false\n\t\t\t\t;\n\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tif( ~types[i].indexOf('File') ){\n\t\t\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t\t\tif( _type !== evt.type ){\n\t\t\t\t\t\t\t_type = evt.type; // Store current type of event\n\n\t\t\t\t\t\t\tif( _type != 'dragleave' ){\n\t\t\t\t\t\t\t\tonHover.call(evt[currentTarget], true, evt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdebounceTrigger = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak; // exit from \"while\"\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( debounceTrigger ){\n\t\t\t\t\tclearTimeout(_id);\n\t\t\t\t\t_id = setTimeout(function (){\n\t\t\t\t\t\tonHover.call(evt[currentTarget], _type != 'dragleave', evt);\n\t\t\t\t\t}, 50);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\t// Drop\n\t\t\t_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){\n\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t_type = 0;\n\t\t\t\tonHover.call(evt[currentTarget], false, evt);\n\n\t\t\t\tapi.getDropFiles(evt, function (files){\n\t\t\t\t\tonDrop.call(evt[currentTarget], files, evt);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\tapi.log(\"Drag'n'Drop -- not supported\");\n\t\t}\n\t};\n\n\n\t/**\n\t * Remove drag'n'drop\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd.off = function (el, onHover, onDrop){\n\t\t_off(el, 'dragenter dragleave dragover', onHover.ff);\n\t\t_off(el, 'drop', onDrop.ff);\n\t};\n\n\n\t// Support jQuery\n\tif( jQuery && !jQuery.fn.dnd ){\n\t\tjQuery.fn.dnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\n\t\tjQuery.fn.offdnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd.off(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\t}\n\n\t// @export\n\twindow.FileAPI  = _extend(api, window.FileAPI);\n\n\n\t// Debug info\n\tapi.log('FileAPI: ' + api.version);\n\tapi.log('protocol: ' + window.location.protocol);\n\tapi.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);\n\n\n\t// @detect 'x-ua-compatible'\n\t_each(document.getElementsByTagName('meta'), function (meta){\n\t\tif( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){\n\t\t\tapi.log('meta.http-equiv: ' + meta.getAttribute('content'));\n\t\t}\n\t});\n\n\n\t// configuration\n\ttry {\n\t\tapi._supportConsoleLog = !!console.log;\n\t\tapi._supportConsoleLogApply = !!console.log.apply;\n\t} catch (err) {}\n\n\tif( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }\n\tif( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }\n\tif( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }\n})(window, void 0);\n\n/*global window, FileAPI, document */\n\n(function (api, document, undef) {\n\t'use strict';\n\n\tvar\n\t\tmin = Math.min,\n\t\tround = Math.round,\n\t\tgetCanvas = function () { return document.createElement('canvas'); },\n\t\tsupport = false,\n\t\texifOrientation = {\n\t\t\t  8:\t270\n\t\t\t, 3:\t180\n\t\t\t, 6:\t90\n\t\t\t, 7:\t270\n\t\t\t, 4:\t180\n\t\t\t, 5:\t90\n\t\t}\n\t;\n\n\ttry {\n\t\tsupport = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;\n\t}\n\tcatch (e){}\n\n\n\tfunction Image(file){\n\t\tif( file instanceof Image ){\n\t\t\tvar img = new Image(file.file);\n\t\t\tapi.extend(img.matrix, file.matrix);\n\t\t\treturn\timg;\n\t\t}\n\t\telse if( !(this instanceof Image) ){\n\t\t\treturn\tnew Image(file);\n\t\t}\n\n\t\tthis.file   = file;\n\t\tthis.size   = file.size || 100;\n\n\t\tthis.matrix\t= {\n\t\t\tsx: 0,\n\t\t\tsy: 0,\n\t\t\tsw: 0,\n\t\t\tsh: 0,\n\t\t\tdx: 0,\n\t\t\tdy: 0,\n\t\t\tdw: 0,\n\t\t\tdh: 0,\n\t\t\tresize: 0, // min, max OR preview\n\t\t\tdeg: 0,\n\t\t\tquality: 1, // jpeg quality\n\t\t\tfilter: 0\n\t\t};\n\t}\n\n\n\tImage.prototype = {\n\t\timage: true,\n\t\tconstructor: Image,\n\n\t\tset: function (attrs){\n\t\t\tapi.extend(this.matrix, attrs);\n\t\t\treturn\tthis;\n\t\t},\n\n\t\tcrop: function (x, y, w, h){\n\t\t\tif( w === undef ){\n\t\t\t\tw\t= x;\n\t\t\t\th\t= y;\n\t\t\t\tx = y = 0;\n\t\t\t}\n\t\t\treturn\tthis.set({ sx: x, sy: y, sw: w, sh: h || w });\n\t\t},\n\n\t\tresize: function (w, h, strategy){\n\t\t\tif( /min|max/.test(h) ){\n\t\t\t\tstrategy = h;\n\t\t\t\th = w;\n\t\t\t}\n\n\t\t\treturn\tthis.set({ dw: w, dh: h || w, resize: strategy });\n\t\t},\n\n\t\tpreview: function (w, h){\n\t\t\treturn\tthis.resize(w, h || w, 'preview');\n\t\t},\n\n\t\trotate: function (deg){\n\t\t\treturn\tthis.set({ deg: deg });\n\t\t},\n\n\t\tfilter: function (filter){\n\t\t\treturn\tthis.set({ filter: filter });\n\t\t},\n\n\t\toverlay: function (images){\n\t\t\treturn\tthis.set({ overlay: images });\n\t\t},\n\n\t\tclone: function (){\n\t\t\treturn\tnew Image(this);\n\t\t},\n\n\t\t_load: function (image, fn){\n\t\t\tvar self = this;\n\n\t\t\tif( /img|video/i.test(image.nodeName) ){\n\t\t\t\tfn.call(self, null, image);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.readAsImage(image, function (evt){\n\t\t\t\t\tfn.call(self, evt.type != 'load', evt.result);\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\t_apply: function (image, fn){\n\t\t\tvar\n\t\t\t\t  canvas = getCanvas()\n\t\t\t\t, m = this.getMatrix(image)\n\t\t\t\t, ctx = canvas.getContext('2d')\n\t\t\t\t, width = image.videoWidth || image.width\n\t\t\t\t, height = image.videoHeight || image.height\n\t\t\t\t, deg = m.deg\n\t\t\t\t, dw = m.dw\n\t\t\t\t, dh = m.dh\n\t\t\t\t, w = width\n\t\t\t\t, h = height\n\t\t\t\t, filter = m.filter\n\t\t\t\t, copy // canvas copy\n\t\t\t\t, buffer = image\n\t\t\t\t, overlay = m.overlay\n\t\t\t\t, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })\n\t\t\t\t, renderImageToCanvas = api.renderImageToCanvas\n\t\t\t;\n\n\t\t\t// Normalize angle\n\t\t\tdeg = deg - Math.floor(deg/360)*360;\n\n\t\t\t// For `renderImageToCanvas`\n\t\t\timage._type = this.file.type;\n\n\t\t\twhile(m.multipass && min(w/dw, h/dh) > 2 ){\n\t\t\t\tw = (w/2 + 0.5)|0;\n\t\t\t\th = (h/2 + 0.5)|0;\n\n\t\t\t\tcopy = getCanvas();\n\t\t\t\tcopy.width  = w;\n\t\t\t\tcopy.height = h;\n\n\t\t\t\tif( buffer !== image ){\n\t\t\t\t\trenderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t\trenderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);\n\t\t\t\t\tm.sx = m.sy = m.sw = m.sh = 0;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tcanvas.width  = (deg % 180) ? dh : dw;\n\t\t\tcanvas.height = (deg % 180) ? dw : dh;\n\n\t\t\tcanvas.type = m.type;\n\t\t\tcanvas.quality = m.quality;\n\n\t\t\tctx.rotate(deg * Math.PI / 180);\n\t\t\trenderImageToCanvas(ctx.canvas, buffer\n\t\t\t\t, m.sx, m.sy\n\t\t\t\t, m.sw || buffer.width\n\t\t\t\t, m.sh || buffer.height\n\t\t\t\t, (deg == 180 || deg == 270 ? -dw : 0)\n\t\t\t\t, (deg == 90 || deg == 180 ? -dh : 0)\n\t\t\t\t, dw, dh\n\t\t\t);\n\t\t\tdw = canvas.width;\n\t\t\tdh = canvas.height;\n\n\t\t\t// Apply overlay\n\t\t\toverlay && api.each([].concat(overlay), function (over){\n\t\t\t\tqueue.inc();\n\t\t\t\t// preload\n\t\t\t\tvar img = new window.Image, fn = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  x = over.x|0\n\t\t\t\t\t\t, y = over.y|0\n\t\t\t\t\t\t, w = over.w || img.width\n\t\t\t\t\t\t, h = over.h || img.height\n\t\t\t\t\t\t, rel = over.rel\n\t\t\t\t\t;\n\n\t\t\t\t\t// center  |  right  |  left\n\t\t\t\t\tx = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);\n\n\t\t\t\t\t// center  |  bottom  |  top\n\t\t\t\t\ty = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);\n\n\t\t\t\t\tapi.event.off(img, 'error load abort', fn);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tctx.globalAlpha = over.opacity || 1;\n\t\t\t\t\t\tctx.drawImage(img, x, y, w, h);\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er){}\n\n\t\t\t\t\tqueue.next();\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(img, 'error load abort', fn);\n\t\t\t\timg.src = over.src;\n\n\t\t\t\tif( img.complete ){\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif( filter ){\n\t\t\t\tqueue.inc();\n\t\t\t\tImage.applyFilter(canvas, filter, queue.next);\n\t\t\t}\n\n\t\t\tqueue.check();\n\t\t},\n\n\t\tgetMatrix: function (image){\n\t\t\tvar\n\t\t\t\t  m  = api.extend({}, this.matrix)\n\t\t\t\t, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width\n\t\t\t\t, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height\n\t\t\t\t, dw = m.dw = m.dw || sw\n\t\t\t\t, dh = m.dh = m.dh || sh\n\t\t\t\t, sf = sw/sh, df = dw/dh\n\t\t\t\t, strategy = m.resize\n\t\t\t;\n\n\t\t\tif( strategy == 'preview' ){\n\t\t\t\tif( dw != sw || dh != sh ){\n\t\t\t\t\t// Make preview\n\t\t\t\t\tvar w, h;\n\n\t\t\t\t\tif( df >= sf ){\n\t\t\t\t\t\tw\t= sw;\n\t\t\t\t\t\th\t= w / df;\n\t\t\t\t\t} else {\n\t\t\t\t\t\th\t= sh;\n\t\t\t\t\t\tw\t= h * df;\n\t\t\t\t\t}\n\n\t\t\t\t\tif( w != sw || h != sh ){\n\t\t\t\t\t\tm.sx\t= ~~((sw - w)/2);\n\t\t\t\t\t\tm.sy\t= ~~((sh - h)/2);\n\t\t\t\t\t\tsw\t\t= w;\n\t\t\t\t\t\tsh\t\t= h;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if( strategy ){\n\t\t\t\tif( !(sw > dw || sh > dh) ){\n\t\t\t\t\tdw = sw;\n\t\t\t\t\tdh = sh;\n\t\t\t\t}\n\t\t\t\telse if( strategy == 'min' ){\n\t\t\t\t\tdw = round(sf < df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf < df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdw = round(sf >= df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf >= df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm.sw = sw;\n\t\t\tm.sh = sh;\n\t\t\tm.dw = dw;\n\t\t\tm.dh = dh;\n\t\t\tm.multipass = api.multiPassResize;\n\t\t\treturn\tm;\n\t\t},\n\n\t\t_trans: function (fn){\n\t\t\tthis._load(this.file, function (err, image){\n\t\t\t\tif( err ){\n\t\t\t\t\tfn(err);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis._apply(image, fn);\n\t\t\t\t\t} catch (err){\n\t\t\t\t\t\tapi.log('[err] FileAPI.Image.fn._apply:', err);\n\t\t\t\t\t\tfn(err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\tget: function (fn){\n\t\t\tif( api.support.transform ){\n\t\t\t\tvar _this = this, matrix = _this.matrix;\n\n\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\tapi.getInfo(_this.file, function (err, info){\n\t\t\t\t\t\t// rotate by exif orientation\n\t\t\t\t\t\tmatrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t_this._trans(fn);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_this._trans(fn);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn('not_support_transform');\n\t\t\t}\n\n\t\t\treturn this;\n\t\t},\n\n\n\t\ttoData: function (fn){\n\t\t\treturn this.get(fn);\n\t\t}\n\n\t};\n\n\n\tImage.exifOrientation = exifOrientation;\n\n\n\tImage.transform = function (file, transform, autoOrientation, fn){\n\t\tfunction _transform(err, img){\n\t\t\t// img -- info object\n\t\t\tvar\n\t\t\t\t  images = {}\n\t\t\t\t, queue = api.queue(function (err){\n\t\t\t\t\tfn(err, images);\n\t\t\t\t})\n\t\t\t;\n\n\t\t\tif( !err ){\n\t\t\t\tapi.each(transform, function (params, name){\n\t\t\t\t\tif( !queue.isFail() ){\n\t\t\t\t\t\tvar ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';\n\n\t\t\t\t\t\tif( isFn ){\n\t\t\t\t\t\t\tparams(img, ImgTrans);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( params.width ){\n\t\t\t\t\t\t\tImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tif( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){\n\t\t\t\t\t\t\t\tImgTrans.resize(params.maxWidth, params.maxHeight, 'max');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.crop ){\n\t\t\t\t\t\t\tvar crop = params.crop;\n\t\t\t\t\t\t\tImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.rotate === undef && autoOrientation ){\n\t\t\t\t\t\t\tparams.rotate = 'auto';\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });\n\n\t\t\t\t\t\tif( !isFn ){\n\t\t\t\t\t\t\tImgTrans.set({\n\t\t\t\t\t\t\t\t  deg: params.rotate\n\t\t\t\t\t\t\t\t, overlay: params.overlay\n\t\t\t\t\t\t\t\t, filter: params.filter\n\t\t\t\t\t\t\t\t, quality: params.quality || 1\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\tImgTrans.toData(function (err, image){\n\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\tqueue.fail();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\timages[name] = image;\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tqueue.fail();\n\t\t\t}\n\t\t}\n\n\n\t\t// @todo: Оло-ло, нужно рефакторить это место\n\t\tif( file.width ){\n\t\t\t_transform(false, file);\n\t\t} else {\n\t\t\tapi.getInfo(file, _transform);\n\t\t}\n\t};\n\n\n\t// @const\n\tapi.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){\n\t\tapi.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){\n\t\t\tImage[x+'_'+y] = i*3 + j;\n\t\t\tImage[y+'_'+x] = i*3 + j;\n\t\t});\n\t});\n\n\n\t/**\n\t * Trabsform element to canvas\n\t *\n\t * @param    {Image|HTMLVideoElement}   el\n\t * @returns  {Canvas}\n\t */\n\tImage.toCanvas = function(el){\n\t\tvar canvas\t\t= document.createElement('canvas');\n\t\tcanvas.width\t= el.videoWidth || el.width;\n\t\tcanvas.height\t= el.videoHeight || el.height;\n\t\tcanvas.getContext('2d').drawImage(el, 0, 0);\n\t\treturn\tcanvas;\n\t};\n\n\n\t/**\n\t * Create image from DataURL\n\t * @param  {String}  dataURL\n\t * @param  {Object}  size\n\t * @param  {Function}  callback\n\t */\n\tImage.fromDataURL = function (dataURL, size, callback){\n\t\tvar img = api.newImage(dataURL);\n\t\tapi.extend(img, size);\n\t\tcallback(img);\n\t};\n\n\n\t/**\n\t * Apply filter (caman.js)\n\t *\n\t * @param  {Canvas|Image}   canvas\n\t * @param  {String|Function}  filter\n\t * @param  {Function}  doneFn\n\t */\n\tImage.applyFilter = function (canvas, filter, doneFn){\n\t\tif( typeof filter == 'function' ){\n\t\t\tfilter(canvas, doneFn);\n\t\t}\n\t\telse if( window.Caman ){\n\t\t\t// http://camanjs.com/guides/\n\t\t\twindow.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tthis[filter]();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tapi.each(filter, function (val, method){\n\t\t\t\t\t\tthis[method](val);\n\t\t\t\t\t}, this);\n\t\t\t\t}\n\t\t\t\tthis.render(doneFn);\n\t\t\t});\n\t\t}\n\t};\n\n\n\t/**\n\t * For load-image-ios.js\n\t */\n\tapi.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){\n\t\ttry {\n\t\t\treturn canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);\n\t\t} catch (ex) {\n\t\t\tapi.log('renderImageToCanvas failed');\n\t\t\tthrow ex;\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.support.canvas = api.support.transform = support;\n\tapi.Image = Image;\n})(FileAPI, document);\n\n/*\n * JavaScript Load Image iOS scaling fixes 1.0.3\n * https://github.com/blueimp/JavaScript-Load-Image\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * iOS image scaling fixes based on\n * https://github.com/stomita/ios-imagefile-megapixel\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, bitwise: true */\n/*global FileAPI, window, document */\n\n(function (factory) {\n\t'use strict';\n\tfactory(FileAPI);\n}(function (loadImage) {\n    'use strict';\n\n    // Only apply fixes on the iOS platform:\n    if (!window.navigator || !window.navigator.platform ||\n             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {\n        return;\n    }\n\n    var originalRenderMethod = loadImage.renderImageToCanvas;\n\n    // Detects subsampling in JPEG images:\n    loadImage.detectSubsampling = function (img) {\n        var canvas,\n            context;\n        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images\n            canvas = document.createElement('canvas');\n            canvas.width = canvas.height = 1;\n            context = canvas.getContext('2d');\n            context.drawImage(img, -img.width + 1, 0);\n            // subsampled image becomes half smaller in rendering size.\n            // check alpha channel value to confirm image is covering edge pixel or not.\n            // if alpha value is 0 image is not covering, hence subsampled.\n            return context.getImageData(0, 0, 1, 1).data[3] === 0;\n        }\n        return false;\n    };\n\n    // Detects vertical squash in JPEG images:\n    loadImage.detectVerticalSquash = function (img, subsampled) {\n        var naturalHeight = img.naturalHeight || img.height,\n            canvas = document.createElement('canvas'),\n            context = canvas.getContext('2d'),\n            data,\n            sy,\n            ey,\n            py,\n            alpha;\n        if (subsampled) {\n            naturalHeight /= 2;\n        }\n        canvas.width = 1;\n        canvas.height = naturalHeight;\n        context.drawImage(img, 0, 0);\n        data = context.getImageData(0, 0, 1, naturalHeight).data;\n        // search image edge pixel position in case it is squashed vertically:\n        sy = 0;\n        ey = naturalHeight;\n        py = naturalHeight;\n        while (py > sy) {\n            alpha = data[(py - 1) * 4 + 3];\n            if (alpha === 0) {\n                ey = py;\n            } else {\n                sy = py;\n            }\n            py = (ey + sy) >> 1;\n        }\n        return (py / naturalHeight) || 1;\n    };\n\n    // Renders image to canvas while working around iOS image scaling bugs:\n    // https://github.com/blueimp/JavaScript-Load-Image/issues/13\n    loadImage.renderImageToCanvas = function (\n        canvas,\n        img,\n        sourceX,\n        sourceY,\n        sourceWidth,\n        sourceHeight,\n        destX,\n        destY,\n        destWidth,\n        destHeight\n    ) {\n        if (img._type === 'image/jpeg') {\n            var context = canvas.getContext('2d'),\n                tmpCanvas = document.createElement('canvas'),\n                tileSize = 1024,\n                tmpContext = tmpCanvas.getContext('2d'),\n                subsampled,\n                vertSquashRatio,\n                tileX,\n                tileY;\n            tmpCanvas.width = tileSize;\n            tmpCanvas.height = tileSize;\n            context.save();\n            subsampled = loadImage.detectSubsampling(img);\n            if (subsampled) {\n                sourceX /= 2;\n                sourceY /= 2;\n                sourceWidth /= 2;\n                sourceHeight /= 2;\n            }\n            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);\n            if (subsampled || vertSquashRatio !== 1) {\n                sourceY *= vertSquashRatio;\n                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);\n                destHeight = Math.ceil(\n                    tileSize * destHeight / sourceHeight / vertSquashRatio\n                );\n                destY = 0;\n                tileY = 0;\n                while (tileY < sourceHeight) {\n                    destX = 0;\n                    tileX = 0;\n                    while (tileX < sourceWidth) {\n                        tmpContext.clearRect(0, 0, tileSize, tileSize);\n                        tmpContext.drawImage(\n                            img,\n                            sourceX,\n                            sourceY,\n                            sourceWidth,\n                            sourceHeight,\n                            -tileX,\n                            -tileY,\n                            sourceWidth,\n                            sourceHeight\n                        );\n                        context.drawImage(\n                            tmpCanvas,\n                            0,\n                            0,\n                            tileSize,\n                            tileSize,\n                            destX,\n                            destY,\n                            destWidth,\n                            destHeight\n                        );\n                        tileX += tileSize;\n                        destX += destWidth;\n                    }\n                    tileY += tileSize;\n                    destY += destHeight;\n                }\n                context.restore();\n                return canvas;\n            }\n        }\n        return originalRenderMethod(\n            canvas,\n            img,\n            sourceX,\n            sourceY,\n            sourceWidth,\n            sourceHeight,\n            destX,\n            destY,\n            destWidth,\n            destHeight\n        );\n    };\n\n}));\n\n/*global window, FileAPI */\n\n(function (api, window){\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, FormData = window.FormData\n\t\t, Form = function (){ this.items = []; }\n\t\t, encodeURIComponent = window.encodeURIComponent\n\t;\n\n\n\tForm.prototype = {\n\n\t\tappend: function (name, blob, file, type){\n\t\t\tthis.items.push({\n\t\t\t\t  name: name\n\t\t\t\t, blob: blob && blob.blob || (blob == void 0 ? '' : blob)\n\t\t\t\t, file: blob && (file || blob.name)\n\t\t\t\t, type:\tblob && (type || blob.type)\n\t\t\t});\n\t\t},\n\n\t\teach: function (fn){\n\t\t\tvar i = 0, n = this.items.length;\n\t\t\tfor( ; i < n; i++ ){\n\t\t\t\tfn.call(this, this.items[i]);\n\t\t\t}\n\t\t},\n\n\t\ttoData: function (fn, options){\n\t\t    // allow chunked transfer if we have only one file to send\n\t\t    // flag is used below and in XHR._send\n\t\t    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;\n\n\t\t\tif( !api.support.html5 ){\n\t\t\t\tapi.log('FileAPI.Form.toHtmlData');\n\t\t\t\tthis.toHtmlData(fn);\n\t\t\t}\n\t\t\telse if( !api.formData || this.multipart || !FormData ){\n\t\t\t\tapi.log('FileAPI.Form.toMultipartData');\n\t\t\t\tthis.toMultipartData(fn);\n\t\t\t}\n\t\t\telse if( options._chunked ){\n\t\t\t\tapi.log('FileAPI.Form.toPlainData');\n\t\t\t\tthis.toPlainData(fn);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.log('FileAPI.Form.toFormData');\n\t\t\t\tthis.toFormData(fn);\n\t\t\t}\n\t\t},\n\n\t\t_to: function (data, complete, next, arg){\n\t\t\tvar queue = api.queue(function (){\n\t\t\t\tcomplete(data);\n\t\t\t});\n\n\t\t\tthis.each(function (file){\n\t\t\t\tnext(file, data, queue, arg);\n\t\t\t});\n\n\t\t\tqueue.check();\n\t\t},\n\n\n\t\ttoHtmlData: function (fn){\n\t\t\tthis._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){\n\t\t\t\tvar blob = file.blob, hidden;\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tapi.reset(blob, true);\n\t\t\t\t\t// set new name\n\t\t\t\t\tblob.name = file.name;\n\t\t\t\t\tblob.disabled = false;\n\t\t\t\t\tdata.appendChild(blob);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thidden = document.createElement('input');\n\t\t\t\t\thidden.name  = file.name;\n\t\t\t\t\thidden.type  = 'hidden';\n\t\t\t\t\thidden.value = blob;\n\t\t\t\t\tdata.appendChild(hidden);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\ttoPlainData: function (fn){\n\t\t\tthis._to({}, fn, function (file, data, queue){\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.type = file.file;\n\t\t\t\t}\n\n\t\t\t\tif( file.blob.toBlob ){\n\t\t\t\t    // canvas\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.name = file.name;\n\t\t\t\t\t\tdata.file = blob;\n\t\t\t\t\t\tdata.size = blob.length;\n\t\t\t\t\t\tdata.type = file.type;\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t    // file\n\t\t\t\t\tdata.name = file.blob.name;\n\t\t\t\t\tdata.file = file.blob;\n\t\t\t\t\tdata.size = file.blob.size;\n\t\t\t\t\tdata.type = file.type;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t    // additional data\n\t\t\t\t    if( !data.params ){\n\t\t\t\t        data.params = [];\n\t\t\t\t    }\n\t\t\t\t    data.params.push(encodeURIComponent(file.name) +\"=\"+ encodeURIComponent(file.blob));\n\t\t\t\t}\n\n\t\t\t\tdata.start = -1;\n\t\t\t\tdata.end = data.file && data.file.FileAPIReadPosition || -1;\n\t\t\t\tdata.retry = 0;\n\t\t\t});\n\t\t},\n\n\t\ttoFormData: function (fn){\n\t\t\tthis._to(new FormData, fn, function (file, data, queue){\n\t\t\t\tif( file.blob && file.blob.toBlob ){\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.append(file.name, blob, file.file);\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t\tdata.append(file.name, file.blob, file.file);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdata.append(file.name, file.blob);\n\t\t\t\t}\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.append('_'+file.name, file.file);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\ttoMultipartData: function (fn){\n\t\t\tthis._to([], fn, function (file, data, queue, boundary){\n\t\t\t\tqueue.inc();\n\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\tdata.push(\n\t\t\t\t\t\t  '--_' + boundary + ('\\r\\nContent-Disposition: form-data; name=\"'+ file.name +'\"'+ (file.file ? '; filename=\"'+ encodeURIComponent(file.file) +'\"' : '')\n\t\t\t\t\t\t+ (file.file ? '\\r\\nContent-Type: '+ (file.type || 'application/octet-stream') : '')\n\t\t\t\t\t\t+ '\\r\\n'\n\t\t\t\t\t\t+ '\\r\\n'+ (file.file ? blob : encodeURIComponent(blob))\n\t\t\t\t\t\t+ '\\r\\n')\n\t\t\t\t\t);\n\t\t\t\t\tqueue.next();\n\t\t\t\t}, true);\n\t\t\t}, api.expando);\n\t\t}\n\t};\n\n\n\tfunction _convertFile(file, fn, useBinaryString){\n\t\tvar blob = file.blob, filename = file.file;\n\n\t\tif( filename ){\n\t\t\tif( !blob.toDataURL ){\n\t\t\t\t// The Blob is not an image.\n\t\t\t\tapi.readAsBinaryString(blob, function (evt){\n\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\tfn(file, evt.result);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar\n\t\t\t\t  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }\n\t\t\t\t, type = mime[file.type] ? file.type : 'image/png'\n\t\t\t\t, ext  = mime[type] || '.png'\n\t\t\t\t, quality = blob.quality || 1\n\t\t\t;\n\n\t\t\tif( !filename.match(new RegExp(ext+'$', 'i')) ){\n\t\t\t\t// Does not change the current extension, but add a new one.\n\t\t\t\tfilename += ext.replace('?', '');\n\t\t\t}\n\n\t\t\tfile.file = filename;\n\t\t\tfile.type = type;\n\n\t\t\tif( !useBinaryString && blob.toBlob ){\n\t\t\t\tblob.toBlob(function (blob){\n\t\t\t\t\tfn(file, blob);\n\t\t\t\t}, type, quality);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn(file, api.toBinaryString(blob.toDataURL(type, quality)));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfn(file, blob);\n\t\t}\n\t}\n\n\n\t// @export\n\tapi.Form = Form;\n})(FileAPI, window);\n\n/*global window, FileAPI, Uint8Array */\n\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\t  noop = function (){}\n\t\t, document = window.document\n\n\t\t, XHR = function (options){\n\t\t\tthis.uid = api.uid();\n\t\t\tthis.xhr = {\n\t\t\t\t  abort: noop\n\t\t\t\t, getResponseHeader: noop\n\t\t\t\t, getAllResponseHeaders: noop\n\t\t\t};\n\t\t\tthis.options = options;\n\t\t},\n\n\t\t_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }\n\t;\n\n\n\tXHR.prototype = {\n\t\tstatus: 0,\n\t\tstatusText: '',\n\t\tconstructor: XHR,\n\n\t\tgetResponseHeader: function (name){\n\t\t\treturn this.xhr.getResponseHeader(name);\n\t\t},\n\n\t\tgetAllResponseHeaders: function (){\n\t\t\treturn this.xhr.getAllResponseHeaders() || {};\n\t\t},\n\n\t\tend: function (status, statusText){\n\t\t\tvar _this = this, options = _this.options;\n\n\t\t\t_this.end\t\t=\n\t\t\t_this.abort\t\t= noop;\n\t\t\t_this.status\t= status;\n\n\t\t\tif( statusText ){\n\t\t\t\t_this.statusText = statusText;\n\t\t\t}\n\n\t\t\tapi.log('xhr.end:', status, statusText);\n\t\t\toptions.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);\n\n\t\t\tif( _this.xhr && _this.xhr.node ){\n\t\t\t\tsetTimeout(function (){\n\t\t\t\t\tvar node = _this.xhr.node;\n\t\t\t\t\ttry { node.parentNode.removeChild(node); } catch (e){}\n\t\t\t\t\ttry { delete window[_this.uid]; } catch (e){}\n\t\t\t\t\twindow[_this.uid] = _this.xhr.node = null;\n\t\t\t\t}, 9);\n\t\t\t}\n\t\t},\n\n\t\tabort: function (){\n\t\t\tthis.end(0, 'abort');\n\n\t\t\tif( this.xhr ){\n\t\t\t\tthis.xhr.aborted = true;\n\t\t\t\tthis.xhr.abort();\n\t\t\t}\n\t\t},\n\n\t\tsend: function (FormData){\n\t\t\tvar _this = this, options = this.options;\n\n\t\t\tFormData.toData(function (data){\n\t\t\t\t// Start uploading\n\t\t\t\toptions.upload(options, _this);\n\t\t\t\t_this._send.call(_this, options, data);\n\t\t\t}, options);\n\t\t},\n\n\t\t_send: function (options, data){\n\t\t\tvar _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + \"Load\", url = options.url;\n\n\t\t\tapi.log('XHR._send:', data);\n\n\t\t\tif( !options.cache ){\n\t\t\t\t// No cache\n\t\t\t\turl += (~url.indexOf('?') ? '&' : '?') + api.uid();\n\t\t\t}\n\n\t\t\tif( data.nodeName ){\n\t\t\t\tvar jsonp = options.jsonp;\n\n\t\t\t\t// prepare callback in GET\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)/i, '$1='+uid);\n\n\t\t\t\t// legacy\n\t\t\t\toptions.upload(options, _this);\n\n\t\t\t\tvar\n\t\t\t\t\tonPostMessage = function (evt){\n\t\t\t\t\t\tif( ~url.indexOf(evt.origin) ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar result = api.parseJSON(evt.data);\n\t\t\t\t\t\t\t\tif( result.id == uid ){\n\t\t\t\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\tcomplete(0, err.message);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// jsonp-callack\n\t\t\t\t\tcomplete = window[uid] = function (status, statusText, response){\n\t\t\t\t\t\t_this.readyState\t= 4;\n\t\t\t\t\t\t_this.responseText\t= response;\n\t\t\t\t\t\t_this.end(status, statusText);\n\n\t\t\t\t\t\tapi.event.off(window, 'message', onPostMessage);\n\t\t\t\t\t\twindow[uid] = xhr = transport = window[onloadFuncName] = null;\n\t\t\t\t\t}\n\t\t\t\t;\n\n\t\t\t\t_this.xhr.abort = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( transport.stop ){ transport.stop(); }\n\t\t\t\t\t\telse if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }\n\t\t\t\t\t\telse { transport.contentWindow.document.execCommand('Stop'); }\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er) {}\n\t\t\t\t\tcomplete(0, \"abort\");\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(window, 'message', onPostMessage);\n\n\t\t\t\twindow[onloadFuncName] = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  win = transport.contentWindow\n\t\t\t\t\t\t\t, doc = win.document\n\t\t\t\t\t\t\t, result = win.result || api.parseJSON(doc.body.innerHTML)\n\t\t\t\t\t\t;\n\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('[transport.onload]', e);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\txhr = document.createElement('div');\n\t\t\t\txhr.innerHTML = '<form target=\"'+ uid +'\" action=\"'+ url +'\" method=\"POST\" enctype=\"multipart/form-data\" style=\"position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;\">'\n\t\t\t\t\t\t\t+ '<iframe name=\"'+ uid +'\" src=\"javascript:false;\" onload=\"' + onloadFuncName + '()\"></iframe>'\n\t\t\t\t\t\t\t+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value=\"'+ uid +'\" name=\"'+jsonp+'\" type=\"hidden\"/>' : '')\n\t\t\t\t\t\t\t+ '</form>'\n\t\t\t\t;\n\n\t\t\t\t// get form-data & transport\n\t\t\t\tvar\n\t\t\t\t\t  form = xhr.getElementsByTagName('form')[0]\n\t\t\t\t\t, transport = xhr.getElementsByTagName('iframe')[0]\n\t\t\t\t;\n\n\t\t\t\tform.appendChild(data);\n\n\t\t\t\tapi.log(form.parentNode.innerHTML);\n\n\t\t\t\t// append to DOM\n\t\t\t\tdocument.body.appendChild(xhr);\n\n\t\t\t\t// keep a reference to node-transport\n\t\t\t\t_this.xhr.node = xhr;\n\n\t\t\t\t// send\n\t\t\t\t_this.readyState = 2; // loaded\n\t\t\t\tform.submit();\n\t\t\t\tform = null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Clean url\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)&?/i, '');\n\n\t\t\t\t// html5\n\t\t\t\tif (this.xhr && this.xhr.aborted) {\n\t\t\t\t\tapi.log(\"Error: already aborted\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\txhr = _this.xhr = api.getXHR();\n\n\t\t\t\tif (data.params) {\n\t\t\t\t\turl += (url.indexOf('?') < 0 ? \"?\" : \"&\") + data.params.join(\"&\");\n\t\t\t\t}\n\n\t\t\t\txhr.open('POST', url, true);\n\n\t\t\t\tif( api.withCredentials ){\n\t\t\t\t\txhr.withCredentials = \"true\";\n\t\t\t\t}\n\n\t\t\t\tif( !options.headers || !options.headers['X-Requested-With'] ){\n\t\t\t\t\txhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\t\t\t}\n\n\t\t\t\tapi.each(options.headers, function (val, key){\n\t\t\t\t\txhr.setRequestHeader(key, val);\n\t\t\t\t});\n\n\n\t\t\t\tif ( options._chunked ) {\n\t\t\t\t\t// chunked upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\tif (!data.retry) {\n\t\t\t\t\t\t\t\t// show progress only for correct chunk uploads\n\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t  type:\t\t\tevt.type\n\t\t\t\t\t\t\t\t\t, total:\t\tdata.size\n\t\t\t\t\t\t\t\t\t, loaded:\t\tdata.start + evt.loaded\n\t\t\t\t\t\t\t\t\t, totalSize:\tdata.size\n\t\t\t\t\t\t\t\t}, _this, options);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\tvar lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);\n\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}catch(_){}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status - 201 > 0) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\t// some kind of error\n\t\t\t\t\t\t\t\t// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action\n\t\t\t\t\t\t\t\t// up - server error\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {\n\t\t\t\t\t\t\t\t\t// let's try again the same chunk\n\t\t\t\t\t\t\t\t\t// only applicable for recoverable error codes 500 && 416\n\t\t\t\t\t\t\t\t\tvar delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(data.file, options);\n\n\t\t\t\t\t\t\t\t\t// smart restart if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdata.end = data.start - 1;\n\t\t\t\t\t\t\t\t\t\tif (416 == xhr.status) {\n\t\t\t\t\t\t\t\t\t\t\tdata.end = data.end - options.chunkSize;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// no mo retries\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// success\n\t\t\t\t\t\t\t\tdata.retry = 0;\n\n\t\t\t\t\t\t\t\tif (data.end == data.size - 1) {\n\t\t\t\t\t\t\t\t\t// finished\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// next chunk\n\n\t\t\t\t\t\t\t\t\t// shift position if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdata.file.FileAPIReadPosition = data.end;\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tdata.start = data.end + 1;\n\t\t\t\t\tdata.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);\n\n\t\t\t\t\t// Retrieve a slice of file\n\t\t\t\t\tvar\n\t\t\t\t\t\t  file = data.file\n\t\t\t\t\t\t, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)\n\t\t\t\t\t;\n\n\t\t\t\t\tif( data.size && !slice.size ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\t_this.end(-1);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Range\", \"bytes \" + data.start + \"-\" + data.end + \"/\" + data.size);\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Disposition\", 'attachment; filename=' + encodeURIComponent(data.name));\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Type\", data.type || \"application/octet-stream\");\n\n\t\t\t\t\t\txhr.send(slice);\n\t\t\t\t\t}\n\n\t\t\t\t\tfile = slice = null;\n\t\t\t\t} else {\n\t\t\t\t\t// single piece upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\t// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\toptions.progress(evt, _this, options);\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status > 201) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {\n\t\t\t\t\t\t\t\t\toptions.retry = (options.retry || 0) + 1;\n\t\t\t\t\t\t\t\t\tvar delay = api.networkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(options.file, options);\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif( api.isArray(data) ){\n\t\t\t\t\t\t// multipart\n\t\t\t\t\t\txhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);\n\t\t\t\t\t\tvar rawData = data.join('') +'--_'+ api.expando +'--';\n\n\t\t\t\t\t\t/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */\n\t\t\t\t\t\tif( xhr.sendAsBinary ){\n\t\t\t\t\t\t\txhr.sendAsBinary(rawData);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tvar bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });\n\t\t\t\t\t\t\txhr.send(new Uint8Array(bytes).buffer);\n\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// FormData\n\t\t\t\t\t\txhr.send(data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.XHR = XHR;\n})(window, FileAPI);\n\n/**\n * @class\tFileAPI.Camera\n * @author\tRubaXa\t<trash@rubaxa.org>\n * @support\tChrome 21+, FF 18+, Opera 12+\n */\n\n/*global window, FileAPI, jQuery */\n/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\tURL = window.URL || window.webkitURL,\n\n\t\tdocument = window.document,\n\t\tnavigator = window.navigator,\n\n\t\tgetMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,\n\n\t\thtml5 = !!getMedia\n\t;\n\n\n\t// Support \"media\"\n\tapi.support.media = html5;\n\n\n\tvar Camera = function (video){\n\t\tthis.video = video;\n\t};\n\n\n\tCamera.prototype = {\n\t\tisActive: function (){\n\t\t\treturn\t!!this._active;\n\t\t},\n\n\n\t\t/**\n\t\t * Start camera streaming\n\t\t * @param\t{Function}\tcallback\n\t\t */\n\t\tstart: function (callback){\n\t\t\tvar\n\t\t\t\t  _this = this\n\t\t\t\t, video = _this.video\n\t\t\t\t, _successId\n\t\t\t\t, _failId\n\t\t\t\t, _complete = function (err){\n\t\t\t\t\t_this._active = !err;\n\t\t\t\t\tclearTimeout(_failId);\n\t\t\t\t\tclearTimeout(_successId);\n//\t\t\t\t\tapi.event.off(video, 'loadedmetadata', _complete);\n\t\t\t\t\tcallback && callback(err, _this);\n\t\t\t\t}\n\t\t\t;\n\n\t\t\tgetMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){\n\t\t\t\t// Success\n\t\t\t\t_this.stream = stream;\n\n//\t\t\t\tapi.event.on(video, 'loadedmetadata', function (){\n//\t\t\t\t\t_complete(null);\n//\t\t\t\t});\n\n\t\t\t\t// Set camera stream\n\t\t\t\tvideo.src = URL.createObjectURL(stream);\n\n\t\t\t\t// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.\n\t\t\t\t// See crbug.com/110938.\n\t\t\t\t_successId = setInterval(function (){\n\t\t\t\t\tif( _detectVideoSignal(video) ){\n\t\t\t\t\t\t_complete(null);\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\n\t\t\t\t_failId = setTimeout(function (){\n\t\t\t\t\t_complete('timeout');\n\t\t\t\t}, 5000);\n\n\t\t\t\t// Go-go-go!\n\t\t\t\tvideo.play();\n\t\t\t}, _complete/*error*/);\n\t\t},\n\n\n\t\t/**\n\t\t * Stop camera streaming\n\t\t */\n\t\tstop: function (){\n\t\t\ttry {\n\t\t\t\tthis._active = false;\n\t\t\t\tthis.video.pause();\n\t\t\t\tthis.stream.stop();\n\t\t\t} catch( err ){ }\n\t\t},\n\n\n\t\t/**\n\t\t * Create screenshot\n\t\t * @return {FileAPI.Camera.Shot}\n\t\t */\n\t\tshot: function (){\n\t\t\treturn\tnew Shot(this.video);\n\t\t}\n\t};\n\n\n\t/**\n\t * Get camera element from container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @return\t{Camera}\n\t */\n\tCamera.get = function (el){\n\t\treturn\tnew Camera(el.firstChild);\n\t};\n\n\n\t/**\n\t * Publish camera element into container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Object}\t\toptions\n\t * @param\t{Function}\t\t[callback]\n\t */\n\tCamera.publish = function (el, options, callback){\n\t\tif( typeof options == 'function' ){\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\n\t\t// Dimensions of \"camera\"\n\t\toptions = api.extend({}, {\n\t\t\t  width:\t'100%'\n\t\t\t, height:\t'100%'\n\t\t\t, start:\ttrue\n\t\t}, options);\n\n\n\t\tif( el.jquery ){\n\t\t\t// Extract first element, from jQuery collection\n\t\t\tel = el[0];\n\t\t}\n\n\n\t\tvar doneFn = function (err){\n\t\t\tif( err ){\n\t\t\t\tcallback(err);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Get camera\n\t\t\t\tvar cam = Camera.get(el);\n\t\t\t\tif( options.start ){\n\t\t\t\t\tcam.start(callback);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(null, cam);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\n\t\tel.style.width\t= _px(options.width);\n\t\tel.style.height\t= _px(options.height);\n\n\n\t\tif( api.html5 && html5 ){\n\t\t\t// Create video element\n\t\t\tvar video = document.createElement('video');\n\n\t\t\t// Set dimensions\n\t\t\tvideo.style.width\t= _px(options.width);\n\t\t\tvideo.style.height\t= _px(options.height);\n\n\t\t\t// Clean container\n\t\t\tif( window.jQuery ){\n\t\t\t\tjQuery(el).empty();\n\t\t\t} else {\n\t\t\t\tel.innerHTML = '';\n\t\t\t}\n\n\t\t\t// Add \"camera\" to container\n\t\t\tel.appendChild(video);\n\n\t\t\t// end\n\t\t\tdoneFn();\n\t\t}\n\t\telse {\n\t\t\tCamera.fallback(el, options, doneFn);\n\t\t}\n\t};\n\n\n\tCamera.fallback = function (el, options, callback){\n\t\tcallback('not_support_camera');\n\t};\n\n\n\t/**\n\t * @class\tFileAPI.Camera.Shot\n\t */\n\tvar Shot = function (video){\n\t\tvar canvas\t= video.nodeName ? api.Image.toCanvas(video) : video;\n\t\tvar shot\t= api.Image(canvas);\n\t\tshot.type\t= 'image/png';\n\t\tshot.width\t= canvas.width;\n\t\tshot.height\t= canvas.height;\n\t\tshot.size\t= canvas.width * canvas.height * 4;\n\t\treturn\tshot;\n\t};\n\n\n\t/**\n\t * Add \"px\" postfix, if value is a number\n\t *\n\t * @private\n\t * @param\t{*}  val\n\t * @return\t{String}\n\t */\n\tfunction _px(val){\n\t\treturn\tval >= 0 ? val + 'px' : val;\n\t}\n\n\n\t/**\n\t * @private\n\t * @param\t{HTMLVideoElement} video\n\t * @return\t{Boolean}\n\t */\n\tfunction _detectVideoSignal(video){\n\t\tvar canvas = document.createElement('canvas'), ctx, res = false;\n\t\ttry {\n\t\t\tctx = canvas.getContext('2d');\n\t\t\tctx.drawImage(video, 0, 0, 1, 1);\n\t\t\tres = ctx.getImageData(0, 0, 1, 1).data[4] != 255;\n\t\t}\n\t\tcatch( e ){}\n\t\treturn\tres;\n\t}\n\n\n\t// @export\n\tCamera.Shot\t= Shot;\n\tapi.Camera\t= Camera;\n})(window, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, ActiveXObject, FileAPI */\n(function (window, jQuery, api) {\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, location = window.location\n\t\t, navigator = window.navigator\n\t\t, _each = api.each\n\t;\n\n\n\tapi.support.flash = (function (){\n\t\tvar mime = navigator.mimeTypes, has = false;\n\n\t\tif( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){\n\t\t\thas\t= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);\n\t\t}\n\t\telse {\n\t\t\ttry {\n\t\t\t\thas\t= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));\n\t\t\t}\n\t\t\tcatch(er){\n\t\t\t\tapi.log('Flash -- does not supported.');\n\t\t\t}\n\t\t}\n\n\t\tif( has && /^file:/i.test(location) ){\n\t\t\tapi.log('[warn] Flash does not work on `file:` protocol.');\n\t\t}\n\n\t\treturn\thas;\n\t})();\n\n\n\t   api.support.flash\n\t&& (0\n\t\t|| !api.html5 || !api.support.html5\n\t\t|| (api.cors && !api.support.cors)\n\t\t|| (api.media && !api.support.media)\n\t)\n\t&& (function (){\n\t\tvar\n\t\t\t  _attr  = api.uid()\n\t\t\t, _retry = 0\n\t\t\t, _files = {}\n\t\t\t, _rhttp = /^https?:/i\n\n\t\t\t, flash = {\n\t\t\t\t_fn: {},\n\n\n\t\t\t\t/**\n\t\t\t\t * Publish flash-object\n\t\t\t\t *\n\t\t\t\t * @param {HTMLElement} el\n\t\t\t\t * @param {String} id\n\t\t\t\t * @param {Object} [opts]\n\t\t\t\t */\n\t\t\t\tpublish: function (el, id, opts){\n\t\t\t\t\topts = opts || {};\n\t\t\t\t\tel.innerHTML = _makeFlashHTML({\n\t\t\t\t\t\tid: id\n\t\t\t\t\t\t, src: _getUrl(api.flashUrl, 'r=' + api.version)\n//\t\t\t\t\t\t, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')\n\t\t\t\t\t\t, wmode: opts.camera ? '' : 'transparent'\n\t\t\t\t\t\t, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')\n\t\t\t\t\t\t+ '&flashId='+ id\n\t\t\t\t\t\t+ '&storeKey='+ navigator.userAgent.match(/\\d/ig).join('') +'_'+ api.version\n\t\t\t\t\t\t+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))\n\t\t\t\t\t\t+ '&timeout='+api.flashAbortTimeout\n\t\t\t\t\t\t+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')\n\t\t\t\t\t\t+ '&debug='+(api.debug?\"1\":\"\")\n\t\t\t\t\t}, opts);\n\t\t\t\t},\n\n\n\t\t\t\t/**\n\t\t\t\t * Initialization & preload flash object\n\t\t\t\t */\n\t\t\t\tinit: function (){\n\t\t\t\t\tvar child = document.body && document.body.firstChild;\n\n\t\t\t\t\tif( child ){\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tif( child.nodeType == 1 ){\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.state: awaiting');\n\n\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\n\t\t\t\t\t\t\t\tdummy.id = '_' + _attr;\n\n\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t  top: 1\n\t\t\t\t\t\t\t\t\t, right: 1\n\t\t\t\t\t\t\t\t\t, width: 5\n\t\t\t\t\t\t\t\t\t, height: 5\n\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tchild.parentNode.insertBefore(dummy, child);\n\t\t\t\t\t\t\t\tflash.publish(dummy, _attr);\n\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile( child = child.nextSibling );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( _retry < 10 ){\n\t\t\t\t\t\tsetTimeout(flash.init, ++_retry*50);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tready: function (){\n\t\t\t\t\tapi.log('FlashAPI.state: ready');\n\n\t\t\t\t\tflash.ready = api.F;\n\t\t\t\t\tflash.isReady = true;\n\t\t\t\t\tflash.patch();\n\t\t\t\t\tflash.patchCamera && flash.patchCamera();\n\t\t\t\t\tapi.event.on(document, 'mouseover', flash.mouseover);\n\t\t\t\t\tapi.event.on(document, 'click', function (evt){\n\t\t\t\t\t\tif( flash.mouseover(evt) ){\n\t\t\t\t\t\t\tevt.preventDefault\n\t\t\t\t\t\t\t\t? evt.preventDefault()\n\t\t\t\t\t\t\t\t: (evt.returnValue = true)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\n\n\t\t\t\tgetEl: function (){\n\t\t\t\t\treturn\tdocument.getElementById('_'+_attr);\n\t\t\t\t},\n\n\n\t\t\t\tgetWrapper: function (node){\n\t\t\t\t\tdo {\n\t\t\t\t\t\tif( /js-fileapi-wrapper/.test(node.className) ){\n\t\t\t\t\t\t\treturn\tnode;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twhile( (node = node.parentNode) && (node !== document.body) );\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tdisableMouseover: false,\n\n\t\t\t\tmouseover: function (evt){\n\t\t\t\t\tif (!flash.disableMouseover) {\n\t\t\t\t\t\tvar target = api.event.fix(evt).target;\n\t\n\t\t\t\t\t\tif( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  state = target.getAttribute(_attr)\n\t\t\t\t\t\t\t\t, wrapper = flash.getWrapper(target)\n\t\t\t\t\t\t\t;\n\t\n\t\t\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\t\t\t// check state:\n\t\t\t\t\t\t\t\t//   i — published\n\t\t\t\t\t\t\t\t//   i — initialization\n\t\t\t\t\t\t\t\t//   r — ready\n\t\t\t\t\t\t\t\tif( state == 'i' || state == 'r' ){\n\t\t\t\t\t\t\t\t\t// publish fail\n\t\t\t\t\t\t\t\t\treturn\tfalse;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse if( state != 'p' ){\n\t\t\t\t\t\t\t\t\t// set \"init\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'i');\n\t\n\t\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\t\n\t\t\t\t\t\t\t\t\tif( !wrapper ){\n\t\t\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t\t  top:    0\n\t\t\t\t\t\t\t\t\t\t, left:   0\n\t\t\t\t\t\t\t\t\t\t, width:  target.offsetWidth\n\t\t\t\t\t\t\t\t\t\t, height: target.offsetHeight\n\t\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t\t\t\twrapper.appendChild(dummy);\n\t\t\t\t\t\t\t\t\tflash.publish(dummy, api.uid());\n\t\n\t\t\t\t\t\t\t\t\t// set \"publish\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'p');\n\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( wrapper ){\n\t\t\t\t\t\t\t\t// Use one flash element\n\t\t\t\t\t\t\t\tvar box = _getDimensions(wrapper);\n\t\t\t\t\t\t\t\t_css(flash.getEl(), box);\n\t\n\t\t\t\t\t\t\t\t// Set current input\n\t\t\t\t\t\t\t\tflash.curInp = target;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( !/object|embed/i.test(target.nodeName) ){\n\t\t\t\t\t\t\t_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tonEvent: function (evt){\n\t\t\t\t\tvar type = evt.type;\n\t\t\t\t\t\n\t\t\t\t\tif( type == 'ready' ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// set \"ready\" state\n\t\t\t\t\t\t\tflash.getInput(evt.flashId).setAttribute(_attr, 'r');\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tflash.ready();\n\t\t\t\t\t\tsetTimeout(function (){ flash.mouseenter(evt); }, 50);\n\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'ping' ){\n\t\t\t\t\t\tapi.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'log' ){\n\t\t\t\t\t\tapi.log('(flash -> js).log:', evt.target);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type in flash ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\tapi.log('FlashAPI.event.'+evt.type+':', evt);\n\t\t\t\t\t\t\tflash[type](evt);\n\t\t\t\t\t\t}, 1);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tmouseDown: function(evt) {\n\t\t\t\t\tflash.disableMouseover = true;\n\t\t\t\t},\n\t\t\t\tcancel: function(evt) {\n\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t},\n\t\t\t\tmouseenter: function (evt){\n\t\t\t\t\tvar node = flash.getInput(evt.flashId);\n\n\t\t\t\t\tif( node ){\n\t\t\t\t\t\t// Set multiple mode\n\t\t\t\t\t\tflash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);\n\n\n\t\t\t\t\t\t// Set files filter\n\t\t\t\t\t\tvar accept = [], exts = {};\n\n\t\t\t\t\t\t_each((node.getAttribute('accept') || '').split(/,\\s*/), function (mime){\n\t\t\t\t\t\t\tapi.accept[mime] && _each(api.accept[mime].split(' '), function (ext){\n\t\t\t\t\t\t\t\texts[ext] = 1;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t_each(exts, function (i, ext){\n\t\t\t\t\t\t\taccept.push( ext );\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tflash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tget: function (id){\n\t\t\t\t\treturn\tdocument[id] || window[id] || document.embeds[id];\n\t\t\t\t},\n\n\n\t\t\t\tgetInput: function (id){\n\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar node = flash.getWrapper(flash.get(id));\n\t\t\t\t\t\t\tif( node ){\n\t\t\t\t\t\t\t\treturn node.getElementsByTagName('input')[0];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\tapi.log('[err] Can not find \"input\" by flashId:', id, e);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn\tflash.curInp;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tselect: function (evt){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  inp = flash.getInput(evt.flashId)\n\t\t\t\t\t\t\t, uid = api.uid(inp)\n\t\t\t\t\t\t\t, files = evt.target.files\n\t\t\t\t\t\t\t, event\n\t\t\t\t\t\t;\n\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\tapi.checkFileObj(file);\n\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t_files[uid] = files;\n\t\n\t\t\t\t\t\tif( document.createEvent ){\n\t\t\t\t\t\t\tevent = document.createEvent('Event');\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tevent.initEvent('change', true, true);\n\t\t\t\t\t\t\tinp.dispatchEvent(event);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( jQuery ){\n\t\t\t\t\t\t\tjQuery(inp).trigger({ type: 'change', files: files });\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tevent = document.createEventObject();\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tinp.fireEvent('onchange', event);\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tinterval: null,\n\t\t\t\tcmd: function (id, name, data, last) {\n\t\t\t\t\tif (flash.uploadInProgress && flash.readInProgress) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tflash.cmd(id, name, data, last);\n\t\t\t\t\t\t}, 100);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.cmdFn(id, name, data, last);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tcmdFn: function(id, name, data, last) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tapi.log('(js -> flash).'+name+':', data);\n\t\t\t\t\t\treturn flash.get(id.flashId || id).cmd(name, data);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('(js -> flash).onError:', e);\n\t\t\t\t\t\tif( !last ){\n\t\t\t\t\t\t\t// try again\n\t\t\t\t\t\t\tsetTimeout(function (){ flash.cmd(id, name, data, true); }, 50);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tpatch: function (){\n\t\t\t\t\tapi.flashEngine = true;\n\n\t\t\t\t\t// FileAPI\n\t\t\t\t\t_inherit(api, {\n\t\t\t\t\t\treadAsDataURL: function (file, callback){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64');\n\t\t\t\t\t\t\t\tflash.readInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(file, 'readAsBase64', {\n\t\t\t\t\t\t\t\t\tid: file.id,\n\t\t\t\t\t\t\t\t\tcallback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tflash.readInProgress = false;\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64:', err);\n\n\t\t\t\t\t\t\t\t\t\tcallback({\n\t\t\t\t\t\t\t\t\t\t\t  type: err ? 'error' : 'load'\n\t\t\t\t\t\t\t\t\t\t\t, error: err\n\t\t\t\t\t\t\t\t\t\t\t, result: 'data:'+ file.type +';base64,'+ base64\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\treadAsText: function (file, encoding, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.log('[warn] FlashAPI.readAsText not supported `encoding` param');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback = encoding;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tevt.result = window.atob(evt.result.split(';base64,')[1]);\n\t\t\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\t\t\tevt.error = err.toString();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback(evt);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\n\t\t\t\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];\n\n\n\t\t\t\t\t\t\tif( !files ){\n\t\t\t\t\t\t\t\t// Файлов нету, вызываем родительский метод\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tif( filter ){\n\t\t\t\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t\t\t\t\tfiles\t= api.filter(files, function (file){ return filter.test(file.name); });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn\tfiles;\n\t\t\t\t\t\t},\n\n\n\t\t\t\t\t\tgetInfo: function (file, fn){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( file.isShot ){\n\t\t\t\t\t\t\t\tfn(null, file.info = {\n\t\t\t\t\t\t\t\t\twidth: file.width,\n\t\t\t\t\t\t\t\t\theight: file.height\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( !file.__info ){\n\t\t\t\t\t\t\t\t\tvar defer = file.__info = api.defer();\n\n//\t\t\t\t\t\t\t\t\tflash.cmd(file, 'getFileInfo', {\n//\t\t\t\t\t\t\t\t\t\t  id: file.id\n//\t\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, info){\n//\t\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n//\t\t\t\t\t\t\t\t\t\t\tdefer.resolve(err, file.info = info);\n//\t\t\t\t\t\t\t\t\t\t})\n//\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tdefer.resolve(null, file.info = null);\n\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfile.__info.then(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.Image\n\t\t\t\t\tapi.support.transform = true;\n\t\t\t\t\tapi.Image && _inherit(api.Image.prototype, {\n\t\t\t\t\t\tget: function (fn, scaleMode){\n\t\t\t\t\t\t\tthis.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit\n\t\t\t\t\t\t\treturn this.parent(fn);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_load: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._load:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar _this = this;\n\t\t\t\t\t\t\t\tapi.getInfo(file, function (err){\n\t\t\t\t\t\t\t\t\tfn.call(_this, err, file);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_apply: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar m = this.getMatrix(file.info), doneFn = fn;\n\n\t\t\t\t\t\t\t\tflash.cmd(file, 'imageTransform', {\n\t\t\t\t\t\t\t\t\t  id: file.id\n\t\t\t\t\t\t\t\t\t, matrix: m\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply.callback:', err);\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\tdoneFn(err);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){\n\t\t\t\t\t\t\t\t\t\t\t_makeFlashImage({\n\t\t\t\t\t\t\t\t\t\t\t\t  width:\t(m.deg % 180) ? m.dh : m.dw\n\t\t\t\t\t\t\t\t\t\t\t\t, height:\t(m.deg % 180) ? m.dw : m.dh\n\t\t\t\t\t\t\t\t\t\t\t\t, scale:\tm.scaleMode\n\t\t\t\t\t\t\t\t\t\t\t}, base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\tif( m.filter ){\n\t\t\t\t\t\t\t\t\t\t\t\tdoneFn = function (err, img){\n\t\t\t\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tapi.Image.applyFilter(img, m.filter, function (){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err, this.canvas);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tapi.newImage('data:'+ file.type +';base64,'+ base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  file = this.file\n\t\t\t\t\t\t\t\t, info = file.info\n\t\t\t\t\t\t\t\t, matrix = this.getMatrix(info)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image.toData');\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\t\t\t\t\tmatrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfn.call(this, !file.info, {\n\t\t\t\t\t\t\t\t\t  id:\t\tfile.id\n\t\t\t\t\t\t\t\t\t, flashId:\tfile.flashId\n\t\t\t\t\t\t\t\t\t, name:\t\tfile.name\n\t\t\t\t\t\t\t\t\t, type:\t\tfile.type\n\t\t\t\t\t\t\t\t\t, matrix:\tmatrix\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\tapi.Image && _inherit(api.Image, {\n\t\t\t\t\t\tfromDataURL: function (dataURL, size, callback){\n\t\t\t\t\t\t\tif( !api.support.dataURI || dataURL.length > 3e4 ){\n\t\t\t\t\t\t\t\t_makeFlashImage(\n\t\t\t\t\t\t\t\t\t  api.extend({ scale: 'exactFit' }, size)\n\t\t\t\t\t\t\t\t\t, dataURL.replace(/^data:[^,]+,/, '')\n\t\t\t\t\t\t\t\t\t, function (err, el){ callback(el); }\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tthis.parent(dataURL, size, callback);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// FileAPI.Form\n\t\t\t\t\t_inherit(api.Form.prototype, {\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar items = this.items, i = items.length;\n\n\t\t\t\t\t\t\tfor( ; i--; ){\n\t\t\t\t\t\t\t\tif( items[i].file && _isHtmlFile(items[i].blob) ){\n\t\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.log('FlashAPI.Form.toData');\n\t\t\t\t\t\t\tfn(items);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.XHR\n\t\t\t\t\t_inherit(api.XHR.prototype, {\n\t\t\t\t\t\t_send: function (options, formData){\n\t\t\t\t\t\t\tif(\n\t\t\t\t\t\t\t\t   formData.nodeName\n\t\t\t\t\t\t\t\t|| formData.append && api.support.html5\n\t\t\t\t\t\t\t\t|| api.isArray(formData) && (typeof formData[0] === 'string')\n\t\t\t\t\t\t\t){\n\t\t\t\t\t\t\t\t// HTML5, Multipart or IFrame\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  data = {}\n\t\t\t\t\t\t\t\t, files = {}\n\t\t\t\t\t\t\t\t, _this = this\n\t\t\t\t\t\t\t\t, flashId\n\t\t\t\t\t\t\t\t, fileId\n\t\t\t\t\t\t\t;\n\n\t\t\t\t\t\t\t_each(formData, function (item){\n\t\t\t\t\t\t\t\tif( item.file ){\n\t\t\t\t\t\t\t\t\tfiles[item.name] = item = _getFileDescr(item.blob);\n\t\t\t\t\t\t\t\t\tfileId  = item.id;\n\t\t\t\t\t\t\t\t\tflashId = item.flashId;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tdata[item.name] = item.blob;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tif( !fileId ){\n\t\t\t\t\t\t\t\tflashId = _attr;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif( !flashId ){\n\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI._send: flashId -- undefined');\n\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_this.xhr = {\n\t\t\t\t\t\t\t\theaders: {},\n\t\t\t\t\t\t\t\tabort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },\n\t\t\t\t\t\t\t\tgetResponseHeader: function (name){ return this.headers[name]; },\n\t\t\t\t\t\t\t\tgetAllResponseHeaders: function (){ return this.headers; }\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tvar queue = api.queue(function (){\n\t\t\t\t\t\t\t\tflash.uploadInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(flashId, 'upload', {\n\t\t\t\t\t\t\t\t\t  url: _getUrl(options.url.replace(/([a-z]+)=(\\?)&?/i, ''))\n\t\t\t\t\t\t\t\t\t, data: data\n\t\t\t\t\t\t\t\t\t, files: fileId ? files : null\n\t\t\t\t\t\t\t\t\t, headers: options.headers || {}\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function upload(evt){\n\t\t\t\t\t\t\t\t\t\tvar type = evt.type, result = evt.result;\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.upload.'+type);\n\n\t\t\t\t\t\t\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t\t\t\t\t\t\tevt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme\n\t\t\t\t\t\t\t\t\t\t\tevt.lengthComputable = true;\n\t\t\t\t\t\t\t\t\t\t\toptions.progress(evt);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'complete' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\n\t\t\t\t\t\t\t\t\t\t\tif( typeof result == 'string' ){\n\t\t\t\t\t\t\t\t\t\t\t\t_this.responseText\t= result.replace(/%22/g, \"\\\"\").replace(/%5c/g, \"\\\\\").replace(/%26/g, \"&\").replace(/%25/g, \"%\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 200);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'abort' || type == 'error' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 0, evt.message);\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\n\n\t\t\t\t\t\t\t// #2174: FileReference.load() call while FileReference.upload() or vice versa\n\t\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\t\t\tapi.getInfo(file, queue.next);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tqueue.check();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t;\n\n\n\t\tfunction _makeFlashHTML(opts){\n\t\t\treturn ('<object id=\"#id#\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\">'\n\t\t\t\t+ '<param name=\"movie\" value=\"#src#\" />'\n\t\t\t\t+ '<param name=\"flashvars\" value=\"#flashvars#\" />'\n\t\t\t\t+ '<param name=\"swliveconnect\" value=\"true\" />'\n\t\t\t\t+ '<param name=\"allowscriptaccess\" value=\"always\" />'\n\t\t\t\t+ '<param name=\"allownetworking\" value=\"all\" />'\n\t\t\t\t+ '<param name=\"menu\" value=\"false\" />'\n\t\t\t\t+ '<param name=\"wmode\" value=\"#wmode#\" />'\n\t\t\t\t+ '<embed flashvars=\"#flashvars#\" swliveconnect=\"true\" allownetworking=\"all\" allowscriptaccess=\"always\" name=\"#id#\" src=\"#src#\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\" menu=\"false\" wmode=\"transparent\" type=\"application/x-shockwave-flash\"></embed>'\n\t\t\t\t+ '</object>').replace(/#(\\w+)#/ig, function (a, name){ return opts[name]; })\n\t\t\t;\n\t\t}\n\n\n\t\tfunction _css(el, css){\n\t\t\tif( el && el.style ){\n\t\t\t\tvar key, val;\n\t\t\t\tfor( key in css ){\n\t\t\t\t\tval = css[key];\n\t\t\t\t\tif( typeof val == 'number' ){\n\t\t\t\t\t\tval += 'px';\n\t\t\t\t\t}\n\t\t\t\t\ttry { el.style[key] = val; } catch (e) {}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t}\n\t\t}\n\n\n\t\tfunction _inherit(obj, methods){\n\t\t\t_each(methods, function (fn, name){\n\t\t\t\tvar prev = obj[name];\n\t\t\t\tobj[name] = function (){\n\t\t\t\t\tthis.parent = prev;\n\t\t\t\t\treturn fn.apply(this, arguments);\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\n\t\tfunction _isHtmlFile(file){\n\t\t\treturn\tfile && !file.flashId;\n\t\t}\n\n\t\tfunction _wrap(fn){\n\t\t\tvar id = fn.wid = api.uid();\n\t\t\tflash._fn[id] = fn;\n\t\t\treturn\t'FileAPI.Flash._fn.'+id;\n\t\t}\n\n\n\t\tfunction _unwrap(fn){\n\t\t\ttry {\n\t\t\t\tflash._fn[fn.wid] = null;\n\t\t\t\tdelete\tflash._fn[fn.wid];\n\t\t\t}\n\t\t\tcatch(e){}\n\t\t}\n\n\n\t\tfunction _getUrl(url, params){\n\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\tif( /^\\.\\//.test(url) || '/' != url.charAt(0) ){\n\t\t\t\t\tvar path = location.pathname;\n\t\t\t\t\tpath = path.substr(0, path.lastIndexOf('/'));\n\t\t\t\t\turl = (path +'/'+ url).replace('/./', '/');\n\t\t\t\t}\n\n\t\t\t\tif( '//' != url.substr(0, 2) ){\n\t\t\t\t\turl = '//' + location.host + url;\n\t\t\t\t}\n\n\t\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\t\turl = location.protocol + url;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( params ){\n\t\t\t\turl += (/\\?/.test(url) ? '&' : '?') + params;\n\t\t\t}\n\n\t\t\treturn\turl;\n\t\t}\n\n\n\t\tfunction _makeFlashImage(opts, base64, fn){\n\t\t\tvar\n\t\t\t\t  key\n\t\t\t\t, flashId = api.uid()\n\t\t\t\t, el = document.createElement('div')\n\t\t\t\t, attempts = 10\n\t\t\t;\n\n\t\t\tfor( key in opts ){\n\t\t\t\tel.setAttribute(key, opts[key]);\n\t\t\t\tel[key] = opts[key];\n\t\t\t}\n\n\t\t\t_css(el, opts);\n\n\t\t\topts.width\t= '100%';\n\t\t\topts.height\t= '100%';\n\n\t\t\tel.innerHTML = _makeFlashHTML(api.extend({\n\t\t\t\t  id: flashId\n\t\t\t\t, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())\n\t\t\t\t, wmode: 'opaque'\n\t\t\t\t, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){\n\t\t\t\t\t_unwrap(_);\n\t\t\t\t\tif( --attempts > 0 ){\n\t\t\t\t\t\t_setImage();\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t})\n\t\t\t}, opts));\n\n\t\t\tfunction _setImage(){\n\t\t\t\ttry {\n\t\t\t\t\t// Get flash-object by id\n\t\t\t\t\tvar img = flash.get(flashId);\n\t\t\t\t\timg.setImage(base64);\n\t\t\t\t} catch (e){\n\t\t\t\t\tapi.log('[err] FlashAPI.Preview.setImage -- can not set \"base64\":', e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfn(false, el);\n\t\t\tel = null;\n\t\t}\n\n\n\t\tfunction _getFileDescr(file){\n\t\t\treturn\t{\n\t\t\t\t  id: file.id\n\t\t\t\t, name: file.name\n\t\t\t\t, matrix: file.matrix\n\t\t\t\t, flashId: file.flashId\n\t\t\t};\n\t\t}\n\n\n\t\tfunction _getDimensions(el){\n\t\t\tvar\n\t\t\t\t  box = el.getBoundingClientRect()\n\t\t\t\t, body = document.body\n\t\t\t\t, docEl = (el && el.ownerDocument).documentElement\n\t\t\t;\n\t\t\t\n\t\t\tfunction getOffset(obj) {\n\t\t\t    var left, top;\n\t\t\t    left = top = 0;\n\t\t\t    if (obj.offsetParent) {\n\t\t\t        do {\n\t\t\t            left += obj.offsetLeft;\n\t\t\t            top  += obj.offsetTop;\n\t\t\t        } while (obj = obj.offsetParent);\n\t\t\t    }\n\t\t\t    return {\n\t\t\t        left : left,\n\t\t\t        top : top\n\t\t\t    };\n\t\t\t};\n\t\t\t\n\t\t\treturn {\n\t\t\t\t  top:\t\tgetOffset(el).top\n\t\t\t\t, left:\t\tgetOffset(el).left\n\t\t\t\t, width:\tel.offsetWidth\n\t\t\t\t, height:\tel.offsetHeight\n\t\t\t};\n\t\t}\n\n\t\t// @export\n\t\tapi.Flash = flash;\n\n\n\t\t// Check dataURI support\n\t\tapi.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){\n\t\t\tapi.support.dataURI = !(img.width != 1 || img.height != 1);\n\t\t\tflash.init();\n\t\t});\n\t})();\n})(window, window.jQuery, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, FileAPI */\n(function (window, jQuery, api) {\n    \"use strict\";\n\n    var _each = api.each,\n        _cameraQueue = [];\n\n\n    if (api.support.flash && (api.media && !api.support.media)) {\n        (function () {\n\n            function _wrap(fn) {\n                var id = fn.wid = api.uid();\n                api.Flash._fn[id] = fn;\n                return 'FileAPI.Flash._fn.' + id;\n            }\n\n\n            function _unwrap(fn) {\n                try {\n                    api.Flash._fn[fn.wid] = null;\n                    delete api.Flash._fn[fn.wid];\n                } catch (e) {\n                }\n            }\n\n            var flash = api.Flash;\n            api.extend(api.Flash, {\n\n                patchCamera: function () {\n                    api.Camera.fallback = function (el, options, callback) {\n                        var camId = api.uid();\n                        api.log('FlashAPI.Camera.publish: ' + camId);\n                        flash.publish(el, camId, api.extend(options, {\n                            camera: true,\n                            onEvent: _wrap(function _(evt) {\n                                if (evt.type === 'camera') {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);\n                                        callback(evt.error);\n                                    } else {\n                                        api.log('FlashAPI.Camera.publish.success: ' + camId);\n                                        callback(null);\n                                    }\n                                }\n                            })\n                        }));\n                    };\n                    // Run\n                    _each(_cameraQueue, function (args) {\n                        api.Camera.fallback.apply(api.Camera, args);\n                    });\n                    _cameraQueue = [];\n\n\n                    // FileAPI.Camera:proto\n                    api.extend(api.Camera.prototype, {\n                        _id: function () {\n                            return this.video.id;\n                        },\n\n                        start: function (callback) {\n                            var _this = this;\n                            flash.cmd(this._id(), 'camera.on', {\n                                callback: _wrap(function _(evt) {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.camera.on.error: ' + evt.error);\n                                        callback(evt.error, _this);\n                                    } else {\n                                        api.log('FlashAPI.camera.on.success: ' + _this._id());\n                                        _this._active = true;\n                                        callback(null, _this);\n                                    }\n                                })\n                            });\n                        },\n\n                        stop: function () {\n                            this._active = false;\n                            flash.cmd(this._id(), 'camera.off');\n                        },\n\n                        shot: function () {\n                            api.log('FlashAPI.Camera.shot:', this._id());\n\n                            var shot = api.Flash.cmd(this._id(), 'shot', {});\n                            shot.type = 'image/png';\n                            shot.flashId = this._id();\n                            shot.isShot = true;\n\n                            return new api.Camera.Shot(shot);\n                        }\n                    });\n                }\n            });\n\n            api.Camera.fallback = function () {\n                _cameraQueue.push(arguments);\n            };\n\n        }());\n    }\n}(window, window.jQuery, FileAPI));\nif( typeof define === \"function\" && define.amd ){ define(\"FileAPI\", [], function (){ return FileAPI; }); }\n"
  },
  {
    "path": "demo/src/main/webapp/js/ng-file-upload-all.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * FileAPI Flash shim for old browsers not supporting FormData\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\n(function () {\n  /** @namespace FileAPI.noContentTimeout */\n\n  function patchXHR(fnName, newFn) {\n    window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);\n  }\n\n  function redefineProp(xhr, prop, fn) {\n    try {\n      Object.defineProperty(xhr, prop, {get: fn});\n    } catch (e) {/*ignore*/\n    }\n  }\n\n  if (!window.FileAPI) {\n    window.FileAPI = {};\n  }\n\n  if (!window.XMLHttpRequest) {\n    throw 'AJAX is not supported. XMLHttpRequest is not defined.';\n  }\n\n  FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;\n  if (FileAPI.shouldLoad) {\n    var initializeUploadListener = function (xhr) {\n      if (!xhr.__listeners) {\n        if (!xhr.upload) xhr.upload = {};\n        xhr.__listeners = [];\n        var origAddEventListener = xhr.upload.addEventListener;\n        xhr.upload.addEventListener = function (t, fn) {\n          xhr.__listeners[t] = fn;\n          if (origAddEventListener) origAddEventListener.apply(this, arguments);\n        };\n      }\n    };\n\n    patchXHR('open', function (orig) {\n      return function (m, url, b) {\n        initializeUploadListener(this);\n        this.__url = url;\n        try {\n          orig.apply(this, [m, url, b]);\n        } catch (e) {\n          if (e.message.indexOf('Access is denied') > -1) {\n            this.__origError = e;\n            orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);\n          }\n        }\n      };\n    });\n\n    patchXHR('getResponseHeader', function (orig) {\n      return function (h) {\n        return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));\n      };\n    });\n\n    patchXHR('getAllResponseHeaders', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('abort', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('setRequestHeader', function (orig) {\n      return function (header, value) {\n        if (header === '__setXHR_') {\n          initializeUploadListener(this);\n          var val = value(this);\n          // fix for angular < 1.2.0\n          if (val instanceof Function) {\n            val(this);\n          }\n        } else {\n          this.__requestHeaders = this.__requestHeaders || {};\n          this.__requestHeaders[header] = value;\n          orig.apply(this, arguments);\n        }\n      };\n    });\n\n    patchXHR('send', function (orig) {\n      return function () {\n        var xhr = this;\n        if (arguments[0] && arguments[0].__isFileAPIShim) {\n          var formData = arguments[0];\n          var config = {\n            url: xhr.__url,\n            jsonp: false, //removes the callback form param\n            cache: true, //removes the ?fileapiXXX in the url\n            complete: function (err, fileApiXHR) {\n              if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {\n                // this error seems to be fine the file is being uploaded properly.\n                err = null;\n              }\n              xhr.__completed = true;\n              if (!err && xhr.__listeners.load)\n                xhr.__listeners.load({\n                  type: 'load',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (!err && xhr.__listeners.loadend)\n                xhr.__listeners.loadend({\n                  type: 'loadend',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (err === 'abort' && xhr.__listeners.abort)\n                xhr.__listeners.abort({\n                  type: 'abort',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {\n                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;\n              });\n              if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {\n                return fileApiXHR.statusText;\n              });\n              redefineProp(xhr, 'readyState', function () {\n                return 4;\n              });\n              if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {\n                return fileApiXHR.response;\n              });\n              var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);\n              redefineProp(xhr, 'responseText', function () {\n                return resp;\n              });\n              redefineProp(xhr, 'response', function () {\n                return resp;\n              });\n              if (err) redefineProp(xhr, 'err', function () {\n                return err;\n              });\n              xhr.__fileApiXHR = fileApiXHR;\n              if (xhr.onreadystatechange) xhr.onreadystatechange();\n              if (xhr.onload) xhr.onload();\n            },\n            progress: function (e) {\n              e.target = xhr;\n              if (xhr.__listeners.progress) xhr.__listeners.progress(e);\n              xhr.__total = e.total;\n              xhr.__loaded = e.loaded;\n              if (e.total === e.loaded) {\n                // fix flash issue that doesn't call complete if there is no response text from the server\n                var _this = this;\n                setTimeout(function () {\n                  if (!xhr.__completed) {\n                    xhr.getAllResponseHeaders = function () {\n                    };\n                    _this.complete(null, {status: 204, statusText: 'No Content'});\n                  }\n                }, FileAPI.noContentTimeout || 10000);\n              }\n            },\n            headers: xhr.__requestHeaders\n          };\n          config.data = {};\n          config.files = {};\n          for (var i = 0; i < formData.data.length; i++) {\n            var item = formData.data[i];\n            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {\n              config.files[item.key] = item.val;\n            } else {\n              config.data[item.key] = item.val;\n            }\n          }\n\n          setTimeout(function () {\n            if (!FileAPI.hasFlash) {\n              throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n            }\n            xhr.__fileApiXHR = FileAPI.upload(config);\n          }, 1);\n        } else {\n          if (this.__origError) {\n            throw this.__origError;\n          }\n          orig.apply(xhr, arguments);\n        }\n      };\n    });\n    window.XMLHttpRequest.__isFileAPIShim = true;\n    window.FormData = FormData = function () {\n      return {\n        append: function (key, val, name) {\n          if (val.__isFileAPIBlobShim) {\n            val = val.data[0];\n          }\n          this.data.push({\n            key: key,\n            val: val,\n            name: name\n          });\n        },\n        data: [],\n        __isFileAPIShim: true\n      };\n    };\n\n    window.Blob = Blob = function (b) {\n      return {\n        data: b,\n        __isFileAPIBlobShim: true\n      };\n    };\n  }\n\n})();\n\n(function () {\n  /** @namespace FileAPI.forceLoad */\n  /** @namespace window.FileAPI.jsUrl */\n  /** @namespace window.FileAPI.jsPath */\n\n  function isInputTypeFile(elem) {\n    return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';\n  }\n\n  function hasFlash() {\n    try {\n      var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n      if (fo) return true;\n    } catch (e) {\n      if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;\n    }\n    return false;\n  }\n\n  function getOffset(obj) {\n    var left = 0, top = 0;\n\n    if (window.jQuery) {\n      return jQuery(obj).offset();\n    }\n\n    if (obj.offsetParent) {\n      do {\n        left += (obj.offsetLeft - obj.scrollLeft);\n        top += (obj.offsetTop - obj.scrollTop);\n        obj = obj.offsetParent;\n      } while (obj);\n    }\n    return {\n      left: left,\n      top: top\n    };\n  }\n\n  if (FileAPI.shouldLoad) {\n    FileAPI.hasFlash = hasFlash();\n\n    //load FileAPI\n    if (FileAPI.forceLoad) {\n      FileAPI.html5 = false;\n    }\n\n    if (!FileAPI.upload) {\n      var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;\n      if (window.FileAPI.jsUrl) {\n        jsUrl = window.FileAPI.jsUrl;\n      } else if (window.FileAPI.jsPath) {\n        basePath = window.FileAPI.jsPath;\n      } else {\n        for (i = 0; i < allScripts.length; i++) {\n          src = allScripts[i].src;\n          index = src.search(/\\/ng\\-file\\-upload[\\-a-zA-z0-9\\.]*\\.js/);\n          if (index > -1) {\n            basePath = src.substring(0, index + 1);\n            break;\n          }\n        }\n      }\n\n      if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;\n      script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');\n      document.getElementsByTagName('head')[0].appendChild(script);\n    }\n\n    FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {\n      if (!hasFlash()) {\n        throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n      }\n      var fixInputStyle = function () {\n        var label = fileElem.parent();\n        if (elem.attr('disabled')) {\n          if (label) label.removeClass('js-fileapi-wrapper');\n        } else {\n          if (!fileElem.attr('__ngf_flash_')) {\n            fileElem.unbind('change');\n            fileElem.unbind('click');\n            fileElem.bind('change', function (evt) {\n              fileApiChangeFn.apply(this, [evt]);\n              changeFn.apply(this, [evt]);\n            });\n            fileElem.attr('__ngf_flash_', 'true');\n          }\n          label.addClass('js-fileapi-wrapper');\n          if (!isInputTypeFile(elem)) {\n            label.css('position', 'absolute')\n              .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')\n              .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))\n              .css('overflow', 'hidden').css('z-index', '900000')\n              .css('visibility', 'visible');\n            fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('position', 'absolute').css('top', '0px').css('left', '0px');\n          }\n        }\n      };\n\n      elem.bind('mouseenter', fixInputStyle);\n\n      var fileApiChangeFn = function (evt) {\n        var files = FileAPI.getFiles(evt);\n        //just a double check for #233\n        for (var i = 0; i < files.length; i++) {\n          if (files[i].size === undefined) files[i].size = 0;\n          if (files[i].name === undefined) files[i].name = 'file';\n          if (files[i].type === undefined) files[i].type = 'undefined';\n        }\n        if (!evt.target) {\n          evt.target = {};\n        }\n        evt.target.files = files;\n        // if evt.target.files is not writable use helper field\n        if (evt.target.files !== files) {\n          evt.__files_ = files;\n        }\n        (evt.__files_ || evt.target.files).item = function (i) {\n          return (evt.__files_ || evt.target.files)[i] || null;\n        };\n      };\n    };\n\n    FileAPI.disableFileInput = function (elem, disable) {\n      if (disable) {\n        elem.removeClass('js-fileapi-wrapper');\n      } else {\n        elem.addClass('js-fileapi-wrapper');\n      }\n    };\n  }\n})();\n\nif (!window.FileReader) {\n  window.FileReader = function () {\n    var _this = this, loadStarted = false;\n    this.listeners = {};\n    this.addEventListener = function (type, fn) {\n      _this.listeners[type] = _this.listeners[type] || [];\n      _this.listeners[type].push(fn);\n    };\n    this.removeEventListener = function (type, fn) {\n      if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);\n    };\n    this.dispatchEvent = function (evt) {\n      var list = _this.listeners[evt.type];\n      if (list) {\n        for (var i = 0; i < list.length; i++) {\n          list[i].call(_this, evt);\n        }\n      }\n    };\n    this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;\n\n    var constructEvent = function (type, evt) {\n      var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};\n      if (evt.result != null) e.target.result = evt.result;\n      return e;\n    };\n    var listener = function (evt) {\n      if (!loadStarted) {\n        loadStarted = true;\n        if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));\n      }\n      var e;\n      if (evt.type === 'load') {\n        if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));\n        e = constructEvent('load', evt);\n        if (_this.onload) _this.onload(e);\n        _this.dispatchEvent(e);\n      } else if (evt.type === 'progress') {\n        e = constructEvent('progress', evt);\n        if (_this.onprogress) _this.onprogress(e);\n        _this.dispatchEvent(e);\n      } else {\n        e = constructEvent('error', evt);\n        if (_this.onerror) _this.onerror(e);\n        _this.dispatchEvent(e);\n      }\n    };\n    this.readAsDataURL = function (file) {\n      FileAPI.readAsDataURL(file, listener);\n    };\n    this.readAsText = function (file) {\n      FileAPI.readAsText(file, listener);\n    };\n  };\n}\n\n/**!\n * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\nif (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {\n  window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {\n    return function (header, value) {\n      if (header === '__setXHR_') {\n        var val = value(this);\n        // fix for angular < 1.2.0\n        if (val instanceof Function) {\n          val(this);\n        }\n      } else {\n        orig.apply(this, arguments);\n      }\n    };\n  })(window.XMLHttpRequest.prototype.setRequestHeader);\n}\n\nvar ngFileUpload = angular.module('ngFileUpload', []);\n\nngFileUpload.version = '12.2.13';\n\nngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {\n  var upload = this;\n  upload.promisesCount = 0;\n\n  this.isResumeSupported = function () {\n    return window.Blob && window.Blob.prototype.slice;\n  };\n\n  var resumeSupported = this.isResumeSupported();\n\n  function sendHttp(config) {\n    config.method = config.method || 'POST';\n    config.headers = config.headers || {};\n\n    var deferred = config._deferred = config._deferred || $q.defer();\n    var promise = deferred.promise;\n\n    function notifyProgress(e) {\n      if (deferred.notify) {\n        deferred.notify(e);\n      }\n      if (promise.progressFunc) {\n        $timeout(function () {\n          promise.progressFunc(e);\n        });\n      }\n    }\n\n    function getNotifyEvent(n) {\n      if (config._start != null && resumeSupported) {\n        return {\n          loaded: n.loaded + config._start,\n          total: (config._file && config._file.size) || n.total,\n          type: n.type, config: config,\n          lengthComputable: true, target: n.target\n        };\n      } else {\n        return n;\n      }\n    }\n\n    if (!config.disableProgress) {\n      config.headers.__setXHR_ = function () {\n        return function (xhr) {\n          if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;\n          config.__XHR = xhr;\n          if (config.xhrFn) config.xhrFn(xhr);\n          xhr.upload.addEventListener('progress', function (e) {\n            e.config = config;\n            notifyProgress(getNotifyEvent(e));\n          }, false);\n          //fix for firefox not firing upload progress end, also IE8-9\n          xhr.upload.addEventListener('load', function (e) {\n            if (e.lengthComputable) {\n              e.config = config;\n              notifyProgress(getNotifyEvent(e));\n            }\n          }, false);\n        };\n      };\n    }\n\n    function uploadWithAngular() {\n      $http(config).then(function (r) {\n          if (resumeSupported && config._chunkSize && !config._finished && config._file) {\n            var fileSize = config._file && config._file.size || 0;\n            notifyProgress({\n                loaded: Math.min(config._end, fileSize),\n                total: fileSize,\n                config: config,\n                type: 'progress'\n              }\n            );\n            upload.upload(config, true);\n          } else {\n            if (config._finished) delete config._finished;\n            deferred.resolve(r);\n          }\n        }, function (e) {\n          deferred.reject(e);\n        }, function (n) {\n          deferred.notify(n);\n        }\n      );\n    }\n\n    if (!resumeSupported) {\n      uploadWithAngular();\n    } else if (config._chunkSize && config._end && !config._finished) {\n      config._start = config._end;\n      config._end += config._chunkSize;\n      uploadWithAngular();\n    } else if (config.resumeSizeUrl) {\n      $http.get(config.resumeSizeUrl).then(function (resp) {\n        if (config.resumeSizeResponseReader) {\n          config._start = config.resumeSizeResponseReader(resp.data);\n        } else {\n          config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());\n        }\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else if (config.resumeSize) {\n      config.resumeSize().then(function (size) {\n        config._start = size;\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else {\n      if (config._chunkSize) {\n        config._start = 0;\n        config._end = config._start + config._chunkSize;\n      }\n      uploadWithAngular();\n    }\n\n\n    promise.success = function (fn) {\n      promise.then(function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.error = function (fn) {\n      promise.then(null, function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.progress = function (fn) {\n      promise.progressFunc = fn;\n      promise.then(null, null, function (n) {\n        fn(n);\n      });\n      return promise;\n    };\n    promise.abort = promise.pause = function () {\n      if (config.__XHR) {\n        $timeout(function () {\n          config.__XHR.abort();\n        });\n      }\n      return promise;\n    };\n    promise.xhr = function (fn) {\n      config.xhrFn = (function (origXhrFn) {\n        return function () {\n          if (origXhrFn) origXhrFn.apply(promise, arguments);\n          fn.apply(promise, arguments);\n        };\n      })(config.xhrFn);\n      return promise;\n    };\n\n    upload.promisesCount++;\n    if (promise['finally'] && promise['finally'] instanceof Function) {\n      promise['finally'](function () {\n        upload.promisesCount--;\n      });\n    }\n    return promise;\n  }\n\n  this.isUploadInProgress = function () {\n    return upload.promisesCount > 0;\n  };\n\n  this.rename = function (file, name) {\n    file.ngfName = name;\n    return file;\n  };\n\n  this.jsonBlob = function (val) {\n    if (val != null && !angular.isString(val)) {\n      val = JSON.stringify(val);\n    }\n    var blob = new window.Blob([val], {type: 'application/json'});\n    blob._ngfBlob = true;\n    return blob;\n  };\n\n  this.json = function (val) {\n    return angular.toJson(val);\n  };\n\n  function copy(obj) {\n    var clone = {};\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        clone[key] = obj[key];\n      }\n    }\n    return clone;\n  }\n\n  this.isFile = function (file) {\n    return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));\n  };\n\n  this.upload = function (config, internal) {\n    function toResumeFile(file, formData) {\n      if (file._ngfBlob) return file;\n      config._file = config._file || file;\n      if (config._start != null && resumeSupported) {\n        if (config._end && config._end >= file.size) {\n          config._finished = true;\n          config._end = file.size;\n        }\n        var slice = file.slice(config._start, config._end || file.size);\n        slice.name = file.name;\n        slice.ngfName = file.ngfName;\n        if (config._chunkSize) {\n          formData.append('_chunkSize', config._chunkSize);\n          formData.append('_currentChunkSize', config._end - config._start);\n          formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));\n          formData.append('_totalSize', config._file.size);\n        }\n        return slice;\n      }\n      return file;\n    }\n\n    function addFieldToFormData(formData, val, key) {\n      if (val !== undefined) {\n        if (angular.isDate(val)) {\n          val = val.toISOString();\n        }\n        if (angular.isString(val)) {\n          formData.append(key, val);\n        } else if (upload.isFile(val)) {\n          var file = toResumeFile(val, formData);\n          var split = key.split(',');\n          if (split[1]) {\n            file.ngfName = split[1].replace(/^\\s+|\\s+$/g, '');\n            key = split[0];\n          }\n          config._fileKey = config._fileKey || key;\n          formData.append(key, file, file.ngfName || file.name);\n        } else {\n          if (angular.isObject(val)) {\n            if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;\n\n            val.$$ngfCircularDetection = true;\n            try {\n              for (var k in val) {\n                if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {\n                  var objectKey = config.objectKey == null ? '[i]' : config.objectKey;\n                  if (val.length && parseInt(k) > -1) {\n                    objectKey = config.arrayKey == null ? objectKey : config.arrayKey;\n                  }\n                  addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));\n                }\n              }\n            } finally {\n              delete val.$$ngfCircularDetection;\n            }\n          } else {\n            formData.append(key, val);\n          }\n        }\n      }\n    }\n\n    function digestConfig() {\n      config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n      config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n      config.headers = config.headers || {};\n      config.headers['Content-Type'] = undefined;\n      config.transformRequest = config.transformRequest ?\n        (angular.isArray(config.transformRequest) ?\n          config.transformRequest : [config.transformRequest]) : [];\n      config.transformRequest.push(function (data) {\n        var formData = new window.FormData(), key;\n        data = data || config.fields || {};\n        if (config.file) {\n          data.file = config.file;\n        }\n        for (key in data) {\n          if (data.hasOwnProperty(key)) {\n            var val = data[key];\n            if (config.formDataAppender) {\n              config.formDataAppender(formData, key, val);\n            } else {\n              addFieldToFormData(formData, val, key);\n            }\n          }\n        }\n\n        return formData;\n      });\n    }\n\n    if (!internal) config = copy(config);\n    if (!config._isDigested) {\n      config._isDigested = true;\n      digestConfig();\n    }\n\n    return sendHttp(config);\n  };\n\n  this.http = function (config) {\n    config = copy(config);\n    config.transformRequest = config.transformRequest || function (data) {\n        if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {\n          return data;\n        }\n        return $http.defaults.transformRequest[0].apply(this, arguments);\n      };\n    config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n    config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n    return sendHttp(config);\n  };\n\n  this.translateScalars = function (str) {\n    if (angular.isString(str)) {\n      if (str.search(/kb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1024);\n      } else if (str.search(/mb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1048576);\n      } else if (str.search(/gb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1073741824);\n      } else if (str.search(/b/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/s/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/m/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 60);\n      } else if (str.search(/h/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 3600);\n      }\n    }\n    return str;\n  };\n\n  this.urlToBlob = function(url) {\n    var defer = $q.defer();\n    $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {\n      var arrayBufferView = new Uint8Array(resp.data);\n      var type = resp.headers('content-type') || 'image/WebP';\n      var blob = new window.Blob([arrayBufferView], {type: type});\n      var matches = url.match(/.*\\/(.+?)(\\?.*)?$/);\n      if (matches.length > 1) {\n        blob.name = matches[1];\n      }\n      defer.resolve(blob);\n    }, function (e) {\n      defer.reject(e);\n    });\n    return defer.promise;\n  };\n\n  this.setDefaults = function (defaults) {\n    this.defaults = defaults || {};\n  };\n\n  this.defaults = {};\n  this.version = ngFileUpload.version;\n}\n\n]);\n\nngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {\n  var upload = UploadExif;\n  upload.getAttrWithDefaults = function (attr, name) {\n    if (attr[name] != null) return attr[name];\n    var def = upload.defaults[name];\n    return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));\n  };\n\n  upload.attrGetter = function (name, attr, scope, params) {\n    var attrVal = this.getAttrWithDefaults(attr, name);\n    if (scope) {\n      try {\n        if (params) {\n          return $parse(attrVal)(scope, params);\n        } else {\n          return $parse(attrVal)(scope);\n        }\n      } catch (e) {\n        // hangle string value without single qoute\n        if (name.search(/min|max|pattern/i)) {\n          return attrVal;\n        } else {\n          throw e;\n        }\n      }\n    } else {\n      return attrVal;\n    }\n  };\n\n  upload.shouldUpdateOn = function (type, attr, scope) {\n    var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);\n    if (modelOptions && modelOptions.updateOn) {\n      return modelOptions.updateOn.split(' ').indexOf(type) > -1;\n    }\n    return true;\n  };\n\n  upload.emptyPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.resolve.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.rejectPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.reject.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.happyPromise = function (promise, data) {\n    var d = $q.defer();\n    promise.then(function (result) {\n      d.resolve(result);\n    }, function (error) {\n      $timeout(function () {\n        throw error;\n      });\n      d.resolve(data);\n    });\n    return d.promise;\n  };\n\n  function applyExifRotations(files, attr, scope) {\n    var promises = [upload.emptyPromise()];\n    angular.forEach(files, function (f, i) {\n      if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {\n        promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {\n          files.splice(i, 1, fixedFile);\n        }));\n      }\n    });\n    return $q.all(promises);\n  }\n\n  function resizeFile(files, attr, scope, ngModel) {\n    var resizeVal = upload.attrGetter('ngfResize', attr, scope);\n    if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();\n    if (resizeVal instanceof Function) {\n      var defer = $q.defer();\n      return resizeVal(files).then(function (p) {\n        resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {\n          defer.resolve(r);\n        }, function (e) {\n          defer.reject(e);\n        });\n      }, function (e) {\n        defer.reject(e);\n      });\n    } else {\n      return resizeWithParams(resizeVal, files, attr, scope, ngModel);\n    }\n  }\n\n  function resizeWithParams(params, files, attr, scope, ngModel) {\n    var promises = [upload.emptyPromise()];\n\n    function handleFile(f, i) {\n      if (f.type.indexOf('image') === 0) {\n        if (params.pattern && !upload.validatePattern(f, params.pattern)) return;\n        params.resizeIf = function (width, height) {\n          return upload.attrGetter('ngfResizeIf', attr, scope,\n            {$width: width, $height: height, $file: f});\n        };\n        var promise = upload.resize(f, params);\n        promises.push(promise);\n        promise.then(function (resizedFile) {\n          files.splice(i, 1, resizedFile);\n        }, function (e) {\n          f.$error = 'resize';\n          (f.$errorMessages = (f.$errorMessages || {})).resize = true;\n          f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);\n          ngModel.$ngfValidations.push({name: 'resize', valid: false});\n          upload.applyModelValidation(ngModel, files);\n        });\n      }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n      handleFile(files[i], i);\n    }\n    return $q.all(promises);\n  }\n\n  upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {\n    function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {\n      attr.$$ngfPrevValidFiles = files;\n      attr.$$ngfPrevInvalidFiles = invalidFiles;\n      var file = files && files.length ? files[0] : null;\n      var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;\n\n      if (ngModel) {\n        upload.applyModelValidation(ngModel, files);\n        ngModel.$setViewValue(isSingleModel ? file : files);\n      }\n\n      if (fileChange) {\n        $parse(fileChange)(scope, {\n          $files: files,\n          $file: file,\n          $newFiles: newFiles,\n          $duplicateFiles: dupFiles,\n          $invalidFiles: invalidFiles,\n          $invalidFile: invalidFile,\n          $event: evt\n        });\n      }\n\n      var invalidModel = upload.attrGetter('ngfModelInvalid', attr);\n      if (invalidModel) {\n        $timeout(function () {\n          $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);\n        });\n      }\n      $timeout(function () {\n        // scope apply changes\n      });\n    }\n\n    var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,\n      invalids = [], valids = [];\n\n    function removeDuplicates() {\n      function equals(f1, f2) {\n        return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&\n          f1.type === f2.type;\n      }\n\n      function isInPrevFiles(f) {\n        var j;\n        for (j = 0; j < prevValidFiles.length; j++) {\n          if (equals(f, prevValidFiles[j])) {\n            return true;\n          }\n        }\n        for (j = 0; j < prevInvalidFiles.length; j++) {\n          if (equals(f, prevInvalidFiles[j])) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n      if (files) {\n        allNewFiles = [];\n        dupFiles = [];\n        for (var i = 0; i < files.length; i++) {\n          if (isInPrevFiles(files[i])) {\n            dupFiles.push(files[i]);\n          } else {\n            allNewFiles.push(files[i]);\n          }\n        }\n      }\n    }\n\n    function toArray(v) {\n      return angular.isArray(v) ? v : [v];\n    }\n\n    function resizeAndUpdate() {\n      function updateModel() {\n        $timeout(function () {\n          update(keep ? prevValidFiles.concat(valids) : valids,\n            keep ? prevInvalidFiles.concat(invalids) : invalids,\n            files, dupFiles, isSingleModel);\n        }, options && options.debounce ? options.debounce.change || options.debounce : 0);\n      }\n\n      var resizingFiles = validateAfterResize ? allNewFiles : valids;\n      resizeFile(resizingFiles, attr, scope, ngModel).then(function () {\n        if (validateAfterResize) {\n          upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n            .then(function (validationResult) {\n              valids = validationResult.validsFiles;\n              invalids = validationResult.invalidsFiles;\n              updateModel();\n            });\n        } else {\n          updateModel();\n        }\n      }, function () {\n        for (var i = 0; i < resizingFiles.length; i++) {\n          var f = resizingFiles[i];\n          if (f.$error === 'resize') {\n            var index = valids.indexOf(f);\n            if (index > -1) {\n              valids.splice(index, 1);\n              invalids.push(f);\n            }\n            updateModel();\n          }\n        }\n      });\n    }\n\n    prevValidFiles = attr.$$ngfPrevValidFiles || [];\n    prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];\n    if (ngModel && ngModel.$modelValue) {\n      prevValidFiles = toArray(ngModel.$modelValue);\n    }\n\n    var keep = upload.attrGetter('ngfKeep', attr, scope);\n    allNewFiles = (files || []).slice(0);\n    if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {\n      removeDuplicates(attr, scope);\n    }\n\n    var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);\n\n    if (keep && !allNewFiles.length) return;\n\n    upload.attrGetter('ngfBeforeModelChange', attr, scope, {\n      $files: files,\n      $file: files && files.length ? files[0] : null,\n      $newFiles: allNewFiles,\n      $duplicateFiles: dupFiles,\n      $event: evt\n    });\n\n    var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);\n\n    var options = upload.attrGetter('ngfModelOptions', attr, scope);\n    upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n      .then(function (validationResult) {\n      if (noDelay) {\n        update(allNewFiles, [], files, dupFiles, isSingleModel);\n      } else {\n        if ((!options || !options.allowInvalid) && !validateAfterResize) {\n          valids = validationResult.validFiles;\n          invalids = validationResult.invalidFiles;\n        } else {\n          valids = allNewFiles;\n        }\n        if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {\n          applyExifRotations(valids, attr, scope).then(function () {\n            resizeAndUpdate();\n          });\n        } else {\n          resizeAndUpdate();\n        }\n      }\n    });\n  };\n\n  return upload;\n}]);\n\nngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {\n  var generatedElems = [];\n\n  function isDelayedClickSupported(ua) {\n    // fix for android native browser < 4.4 and safari windows\n    var m = ua.match(/Android[^\\d]*(\\d+)\\.(\\d+)/);\n    if (m && m.length > 2) {\n      var v = Upload.defaults.androidFixMinorVersion || 4;\n      return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);\n    }\n\n    // safari on windows\n    return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);\n  }\n\n  function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {\n    /** @namespace attr.ngfSelect */\n    /** @namespace attr.ngfChange */\n    /** @namespace attr.ngModel */\n    /** @namespace attr.ngfModelOptions */\n    /** @namespace attr.ngfMultiple */\n    /** @namespace attr.ngfCapture */\n    /** @namespace attr.ngfValidate */\n    /** @namespace attr.ngfKeep */\n    var attrGetter = function (name, scope) {\n      return upload.attrGetter(name, attr, scope);\n    };\n\n    function isInputTypeFile() {\n      return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';\n    }\n\n    function fileChangeAttr() {\n      return attrGetter('ngfChange') || attrGetter('ngfSelect');\n    }\n\n    function changeFn(evt) {\n      if (upload.shouldUpdateOn('change', attr, scope)) {\n        var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];\n        /* Handle duplicate call in  IE11 */\n        if (!fileList) return;\n        for (var i = 0; i < fileList.length; i++) {\n          files.push(fileList[i]);\n        }\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(),\n          files.length ? files : null, evt);\n      }\n    }\n\n    upload.registerModelChangeValidator(ngModel, attr, scope);\n\n    var unwatches = [];\n    if (attrGetter('ngfMultiple')) {\n      unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {\n        fileElem.attr('multiple', attrGetter('ngfMultiple', scope));\n      }));\n    }\n    if (attrGetter('ngfCapture')) {\n      unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {\n        fileElem.attr('capture', attrGetter('ngfCapture', scope));\n      }));\n    }\n    if (attrGetter('ngfAccept')) {\n      unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {\n        fileElem.attr('accept', attrGetter('ngfAccept', scope));\n      }));\n    }\n    unwatches.push(attr.$observe('accept', function () {\n      fileElem.attr('accept', attrGetter('accept'));\n    }));\n    function bindAttrToFileInput(fileElem, label) {\n      function updateId(val) {\n        fileElem.attr('id', 'ngf-' + val);\n        label.attr('id', 'ngf-label-' + val);\n      }\n\n      for (var i = 0; i < elem[0].attributes.length; i++) {\n        var attribute = elem[0].attributes[i];\n        if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {\n          if (attribute.name === 'id') {\n            updateId(attribute.value);\n            unwatches.push(attr.$observe('id', updateId));\n          } else {\n            fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||\n            attribute.name === 'multiple')) ? attribute.name : attribute.value);\n          }\n        }\n      }\n    }\n\n    function createFileInput() {\n      if (isInputTypeFile()) {\n        return elem;\n      }\n\n      var fileElem = angular.element('<input type=\"file\">');\n\n      var label = angular.element('<label>upload</label>');\n      label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')\n        .css('width', '0px').css('height', '0px').css('border', 'none')\n        .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');\n      bindAttrToFileInput(fileElem, label);\n\n      generatedElems.push({el: elem, ref: label});\n\n      document.body.appendChild(label.append(fileElem)[0]);\n\n      return fileElem;\n    }\n\n    function clickHandler(evt) {\n      if (elem.attr('disabled')) return false;\n      if (attrGetter('ngfSelectDisabled', scope)) return;\n\n      var r = detectSwipe(evt);\n      // prevent the click if it is a swipe\n      if (r != null) return r;\n\n      resetModel(evt);\n\n      // fix for md when the element is removed from the DOM and added back #460\n      try {\n        if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {\n          generatedElems.push({el: elem, ref: fileElem.parent()});\n          document.body.appendChild(fileElem.parent()[0]);\n          fileElem.bind('change', changeFn);\n        }\n      } catch (e) {/*ignore*/\n      }\n\n      if (isDelayedClickSupported(navigator.userAgent)) {\n        setTimeout(function () {\n          fileElem[0].click();\n        }, 0);\n      } else {\n        fileElem[0].click();\n      }\n\n      return false;\n    }\n\n\n    var initialTouchStartY = 0;\n    var initialTouchStartX = 0;\n\n    function detectSwipe(evt) {\n      var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);\n      if (touches) {\n        if (evt.type === 'touchstart') {\n          initialTouchStartX = touches[0].clientX;\n          initialTouchStartY = touches[0].clientY;\n          return true; // don't block event default\n        } else {\n          // prevent scroll from triggering event\n          if (evt.type === 'touchend') {\n            var currentX = touches[0].clientX;\n            var currentY = touches[0].clientY;\n            if ((Math.abs(currentX - initialTouchStartX) > 20) ||\n              (Math.abs(currentY - initialTouchStartY) > 20)) {\n              evt.stopPropagation();\n              evt.preventDefault();\n              return false;\n            }\n          }\n          return true;\n        }\n      }\n    }\n\n    var fileElem = elem;\n\n    function resetModel(evt) {\n      if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {\n        fileElem.val(null);\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);\n      }\n    }\n\n    if (!isInputTypeFile()) {\n      fileElem = createFileInput();\n    }\n    fileElem.bind('change', changeFn);\n\n    if (!isInputTypeFile()) {\n      elem.bind('click touchstart touchend', clickHandler);\n    } else {\n      elem.bind('click', resetModel);\n    }\n\n    function ie10SameFileSelectFix(evt) {\n      if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {\n        if (!fileElem[0].parentNode) {\n          fileElem = null;\n          return;\n        }\n        evt.preventDefault();\n        evt.stopPropagation();\n        fileElem.unbind('click');\n        var clone = fileElem.clone();\n        fileElem.replaceWith(clone);\n        fileElem = clone;\n        fileElem.attr('__ngf_ie10_Fix_', 'true');\n        fileElem.bind('change', changeFn);\n        fileElem.bind('click', ie10SameFileSelectFix);\n        fileElem[0].click();\n        return false;\n      } else {\n        fileElem.removeAttr('__ngf_ie10_Fix_');\n      }\n    }\n\n    if (navigator.appVersion.indexOf('MSIE 10') !== -1) {\n      fileElem.bind('click', ie10SameFileSelectFix);\n    }\n\n    if (ngModel) ngModel.$formatters.push(function (val) {\n      if (val == null || val.length === 0) {\n        if (fileElem.val()) {\n          fileElem.val(null);\n        }\n      }\n      return val;\n    });\n\n    scope.$on('$destroy', function () {\n      if (!isInputTypeFile()) fileElem.parent().remove();\n      angular.forEach(unwatches, function (unwatch) {\n        unwatch();\n      });\n    });\n\n    $timeout(function () {\n      for (var i = 0; i < generatedElems.length; i++) {\n        var g = generatedElems[i];\n        if (!document.body.contains(g.el[0])) {\n          generatedElems.splice(i, 1);\n          g.ref.remove();\n        }\n      }\n    });\n\n    if (window.FileAPI && window.FileAPI.ngfFixIE) {\n      window.FileAPI.ngfFixIE(elem, fileElem, changeFn);\n    }\n  }\n\n  return {\n    restrict: 'AEC',\n    require: '?ngModel',\n    link: function (scope, elem, attr, ngModel) {\n      linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);\n    }\n  };\n}]);\n\n(function () {\n\n  ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {\n    var upload = UploadBase;\n    upload.base64DataUrl = function (file) {\n      if (angular.isArray(file)) {\n        var d = $q.defer(), count = 0;\n        angular.forEach(file, function (f) {\n          upload.dataUrl(f, true)['finally'](function () {\n            count++;\n            if (count === file.length) {\n              var urls = [];\n              angular.forEach(file, function (ff) {\n                urls.push(ff.$ngfDataUrl);\n              });\n              d.resolve(urls, file);\n            }\n          });\n        });\n        return d.promise;\n      } else {\n        return upload.dataUrl(file, true);\n      }\n    };\n    upload.dataUrl = function (file, disallowObjectUrl) {\n      if (!file) return upload.emptyPromise(file, file);\n      if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {\n        return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);\n      }\n      var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;\n      if (p) return p;\n\n      var deferred = $q.defer();\n      $timeout(function () {\n        if (window.FileReader && file &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {\n          //prefer URL.createObjectURL for handling refrences to files of all sizes\n          //since it doesn´t build a large string in memory\n          var URL = window.URL || window.webkitURL;\n          if (URL && URL.createObjectURL && !disallowObjectUrl) {\n            var url;\n            try {\n              url = URL.createObjectURL(file);\n            } catch (e) {\n              $timeout(function () {\n                file.$ngfBlobUrl = '';\n                deferred.reject();\n              });\n              return;\n            }\n            $timeout(function () {\n              file.$ngfBlobUrl = url;\n              if (url) {\n                deferred.resolve(url, file);\n                upload.blobUrls = upload.blobUrls || [];\n                upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;\n                upload.blobUrls.push({url: url, size: file.size});\n                upload.blobUrlsTotalSize += file.size || 0;\n                var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;\n                var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;\n                while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {\n                  var obj = upload.blobUrls.splice(0, 1)[0];\n                  URL.revokeObjectURL(obj.url);\n                  upload.blobUrlsTotalSize -= obj.size;\n                }\n              }\n            });\n          } else {\n            var fileReader = new FileReader();\n            fileReader.onload = function (e) {\n              $timeout(function () {\n                file.$ngfDataUrl = e.target.result;\n                deferred.resolve(e.target.result, file);\n                $timeout(function () {\n                  delete file.$ngfDataUrl;\n                }, 1000);\n              });\n            };\n            fileReader.onerror = function () {\n              $timeout(function () {\n                file.$ngfDataUrl = '';\n                deferred.reject();\n              });\n            };\n            fileReader.readAsDataURL(file);\n          }\n        } else {\n          $timeout(function () {\n            file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';\n            deferred.reject();\n          });\n        }\n      });\n\n      if (disallowObjectUrl) {\n        p = file.$$ngfDataUrlPromise = deferred.promise;\n      } else {\n        p = file.$$ngfBlobUrlPromise = deferred.promise;\n      }\n      p['finally'](function () {\n        delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];\n      });\n      return p;\n    };\n    return upload;\n  }]);\n\n  function getTagType(el) {\n    if (el.tagName.toLowerCase() === 'img') return 'image';\n    if (el.tagName.toLowerCase() === 'audio') return 'audio';\n    if (el.tagName.toLowerCase() === 'video') return 'video';\n    return /./;\n  }\n\n  function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {\n    function constructDataUrl(file) {\n      var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);\n      Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {\n        $timeout(function () {\n          var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;\n          if (isBackground) {\n            elem.css('background-image', 'url(\\'' + (src || '') + '\\')');\n          } else {\n            elem.attr('src', src);\n          }\n          if (src) {\n            elem.removeClass('ng-hide');\n          } else {\n            elem.addClass('ng-hide');\n          }\n        });\n      });\n    }\n\n    $timeout(function () {\n      var unwatch = scope.$watch(attr[directiveName], function (file) {\n        var size = resizeParams;\n        if (directiveName === 'ngfThumbnail') {\n          if (!size) {\n            size = {\n              width: elem[0].naturalWidth || elem[0].clientWidth,\n              height: elem[0].naturalHeight || elem[0].clientHeight\n            };\n          }\n          if (size.width === 0 && window.getComputedStyle) {\n            var style = getComputedStyle(elem[0]);\n            if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {\n              size = {\n                width: parseInt(style.width.slice(0, -2)),\n                height: parseInt(style.height.slice(0, -2))\n              };\n            }\n          }\n        }\n\n        if (angular.isString(file)) {\n          elem.removeClass('ng-hide');\n          if (isBackground) {\n            return elem.css('background-image', 'url(\\'' + file + '\\')');\n          } else {\n            return elem.attr('src', file);\n          }\n        }\n        if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&\n          (!isBackground || file.type.indexOf('image') === 0)) {\n          if (size && Upload.isResizeSupported()) {\n            size.resizeIf = function (width, height) {\n              return Upload.attrGetter('ngfResizeIf', attr, scope,\n                {$width: width, $height: height, $file: file});\n            };\n            Upload.resize(file, size).then(\n              function (f) {\n                constructDataUrl(f);\n              }, function (e) {\n                throw e;\n              }\n            );\n          } else {\n            constructDataUrl(file);\n          }\n        } else {\n          elem.addClass('ng-hide');\n        }\n      });\n\n      scope.$on('$destroy', function () {\n        unwatch();\n      });\n    });\n  }\n\n\n  /** @namespace attr.ngfSrc */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',\n          Upload.attrGetter('ngfResize', attr, scope), false);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfBackground */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',\n          Upload.attrGetter('ngfResize', attr, scope), true);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfThumbnail */\n  /** @namespace attr.ngfAsBackground */\n  /** @namespace attr.ngfSize */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        var size = Upload.attrGetter('ngfSize', attr, scope);\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,\n          Upload.attrGetter('ngfAsBackground', attr, scope));\n      }\n    };\n  }]);\n\n  ngFileUpload.config(['$compileProvider', function ($compileProvider) {\n    if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n    if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n  }]);\n\n  ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {\n    return function (file, disallowObjectUrl, trustedUrl) {\n      if (angular.isString(file)) {\n        return $sce.trustAsResourceUrl(file);\n      }\n      var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);\n      if (file && !src) {\n        if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {\n          file.$ngfDataUrlFilterInProgress = true;\n          UploadDataUrl.dataUrl(file, disallowObjectUrl);\n        }\n        return '';\n      }\n      if (file) delete file.$ngfDataUrlFilterInProgress;\n      return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';\n    };\n  }]);\n\n})();\n\nngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {\n  var upload = UploadDataUrl;\n\n  function globStringToRegex(str) {\n    var regexp = '', excludes = [];\n    if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {\n      regexp = str.substring(1, str.length - 1);\n    } else {\n      var split = str.split(',');\n      if (split.length > 1) {\n        for (var i = 0; i < split.length; i++) {\n          var r = globStringToRegex(split[i]);\n          if (r.regexp) {\n            regexp += '(' + r.regexp + ')';\n            if (i < split.length - 1) {\n              regexp += '|';\n            }\n          } else {\n            excludes = excludes.concat(r.excludes);\n          }\n        }\n      } else {\n        if (str.indexOf('!') === 0) {\n          excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');\n        } else {\n          if (str.indexOf('.') === 0) {\n            str = '*' + str;\n          }\n          regexp = '^' + str.replace(new RegExp('[.\\\\\\\\+*?\\\\[\\\\^\\\\]$(){}=!<>|:\\\\-]', 'g'), '\\\\$&') + '$';\n          regexp = regexp.replace(/\\\\\\*/g, '.*').replace(/\\\\\\?/g, '.');\n        }\n      }\n    }\n    return {regexp: regexp, excludes: excludes};\n  }\n\n  upload.validatePattern = function (file, val) {\n    if (!val) {\n      return true;\n    }\n    var pattern = globStringToRegex(val), valid = true;\n    if (pattern.regexp && pattern.regexp.length) {\n      var regexp = new RegExp(pattern.regexp, 'i');\n      valid = (file.type != null && regexp.test(file.type)) ||\n        (file.name != null && regexp.test(file.name));\n    }\n    var len = pattern.excludes.length;\n    while (len--) {\n      var exclude = new RegExp(pattern.excludes[len], 'i');\n      valid = valid && (file.type == null || exclude.test(file.type)) &&\n        (file.name == null || exclude.test(file.name));\n    }\n    return valid;\n  };\n\n  upload.ratioToFloat = function (val) {\n    var r = val.toString(), xIndex = r.search(/[x:]/i);\n    if (xIndex > -1) {\n      r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));\n    } else {\n      r = parseFloat(r);\n    }\n    return r;\n  };\n\n  upload.registerModelChangeValidator = function (ngModel, attr, scope) {\n    if (ngModel) {\n      ngModel.$formatters.push(function (files) {\n        if (ngModel.$dirty) {\n          var filesArray = files;\n          if (files && !angular.isArray(files)) {\n            filesArray = [files];\n          }\n          upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {\n            upload.applyModelValidation(ngModel, filesArray);\n          });\n        }\n        return files;\n      });\n    }\n  };\n\n  function markModelAsDirty(ngModel, files) {\n    if (files != null && !ngModel.$dirty) {\n      if (ngModel.$setDirty) {\n        ngModel.$setDirty();\n      } else {\n        ngModel.$dirty = true;\n      }\n    }\n  }\n\n  upload.applyModelValidation = function (ngModel, files) {\n    markModelAsDirty(ngModel, files);\n    angular.forEach(ngModel.$ngfValidations, function (validation) {\n      ngModel.$setValidity(validation.name, validation.valid);\n    });\n  };\n\n  upload.getValidationAttr = function (attr, scope, name, validationName, file) {\n    var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);\n    var val = upload.attrGetter(dName, attr, scope, {$file: file});\n    if (val == null) {\n      val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});\n      if (val) {\n        var split = (validationName || name).split('.');\n        val = val[split[0]];\n        if (split.length > 1) {\n          val = val && val[split[1]];\n        }\n      }\n    }\n    return val;\n  };\n\n  upload.validate = function (files, prevLength, ngModel, attr, scope) {\n    ngModel = ngModel || {};\n    ngModel.$ngfValidations = ngModel.$ngfValidations || [];\n\n    angular.forEach(ngModel.$ngfValidations, function (v) {\n      v.valid = true;\n    });\n\n    var attrGetter = function (name, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');\n    var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);\n\n    if (files == null || files.length === 0) {\n      return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});\n    }\n\n    files = files.length === undefined ? [files] : files.slice(0);\n    var invalidFiles = [];\n\n    function validateSync(name, validationName, fn) {\n      if (files) {\n        var i = files.length, valid = null;\n        while (i--) {\n          var file = files[i];\n          if (file) {\n            var val = upload.getValidationAttr(attr, scope, name, validationName, file);\n            if (val != null) {\n              if (!fn(file, val, i)) {\n                if (ignoredErrors.indexOf(name) === -1) {\n                  file.$error = name;\n                  (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n                  file.$errorParam = val;\n                  if (invalidFiles.indexOf(file) === -1) {\n                    invalidFiles.push(file);\n                  }\n                  if (!runAllValidation) {\n                    files.splice(i, 1);\n                  }\n                  valid = false;\n                } else {\n                  files.splice(i, 1);\n                }\n              }\n            }\n          }\n        }\n        if (valid !== null) {\n          ngModel.$ngfValidations.push({name: name, valid: valid});\n        }\n      }\n    }\n\n    validateSync('pattern', null, upload.validatePattern);\n    validateSync('minSize', 'size.min', function (file, val) {\n      return file.size + 0.1 >= upload.translateScalars(val);\n    });\n    validateSync('maxSize', 'size.max', function (file, val) {\n      return file.size - 0.1 <= upload.translateScalars(val);\n    });\n    var totalSize = 0;\n    validateSync('maxTotalSize', null, function (file, val) {\n      totalSize += file.size;\n      if (totalSize > upload.translateScalars(val)) {\n        files.splice(0, files.length);\n        return false;\n      }\n      return true;\n    });\n\n    validateSync('validateFn', null, function (file, r) {\n      return r === true || r === null || r === '';\n    });\n\n    if (!files.length) {\n      return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});\n    }\n\n    function validateAsync(name, validationName, type, asyncFn, fn) {\n      function resolveResult(defer, file, val) {\n        function resolveInternal(fn) {\n          if (fn()) {\n            if (ignoredErrors.indexOf(name) === -1) {\n              file.$error = name;\n              (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n              file.$errorParam = val;\n              if (invalidFiles.indexOf(file) === -1) {\n                invalidFiles.push(file);\n              }\n              if (!runAllValidation) {\n                var i = files.indexOf(file);\n                if (i > -1) files.splice(i, 1);\n              }\n              defer.resolve(false);\n            } else {\n              var j = files.indexOf(file);\n              if (j > -1) files.splice(j, 1);\n              defer.resolve(true);\n            }\n          } else {\n            defer.resolve(true);\n          }\n        }\n\n        if (val != null) {\n          asyncFn(file, val).then(function (d) {\n            resolveInternal(function () {\n              return !fn(d, val);\n            });\n          }, function () {\n            resolveInternal(function () {\n              return attrGetter('ngfValidateForce', {$file: file});\n            });\n          });\n        } else {\n          defer.resolve(true);\n        }\n      }\n\n      var promises = [upload.emptyPromise(true)];\n      if (files) {\n        files = files.length === undefined ? [files] : files;\n        angular.forEach(files, function (file) {\n          var defer = $q.defer();\n          promises.push(defer.promise);\n          if (type && (file.type == null || file.type.search(type) !== 0)) {\n            defer.resolve(true);\n            return;\n          }\n          if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {\n            upload.imageDimensions(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {\n            upload.mediaDuration(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDuration', {$file: file, $duration: d}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else {\n            resolveResult(defer, file,\n              upload.getValidationAttr(attr, scope, name, validationName, file));\n          }\n        });\n      }\n      var deffer = $q.defer();\n      $q.all(promises).then(function (values) {\n        var isValid = true;\n        for (var i = 0; i < values.length; i++) {\n          if (!values[i]) {\n            isValid = false;\n            break;\n          }\n        }\n        ngModel.$ngfValidations.push({name: name, valid: isValid});\n        deffer.resolve(isValid);\n      });\n      return deffer.promise;\n    }\n\n    var deffer = $q.defer();\n    var promises = [];\n\n    promises.push(validateAsync('maxHeight', 'height.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height <= val;\n      }));\n    promises.push(validateAsync('minHeight', 'height.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height >= val;\n      }));\n    promises.push(validateAsync('maxWidth', 'width.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width <= val;\n      }));\n    promises.push(validateAsync('minWidth', 'width.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width >= val;\n      }));\n    promises.push(validateAsync('dimensions', null, /image/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n    promises.push(validateAsync('ratio', null, /image/,\n      this.imageDimensions, function (d, val) {\n        var split = val.toString().split(','), valid = false;\n        for (var i = 0; i < split.length; i++) {\n          if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {\n            valid = true;\n          }\n        }\n        return valid;\n      }));\n    promises.push(validateAsync('maxRatio', 'ratio.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;\n      }));\n    promises.push(validateAsync('minRatio', 'ratio.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;\n      }));\n    promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d <= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d >= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('duration', null, /audio|video/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n\n    promises.push(validateAsync('validateAsyncFn', null, null,\n      function (file, val) {\n        return val;\n      }, function (r) {\n        return r === true || r === null || r === '';\n      }));\n\n    $q.all(promises).then(function () {\n\n      if (runAllValidation) {\n        for (var i = 0; i < files.length; i++) {\n          var file = files[i];\n          if (file.$error) {\n            files.splice(i--, 1);\n          }\n        }\n      }\n\n      runAllValidation = false;\n      validateSync('maxFiles', null, function (file, val, i) {\n        return prevLength + i < val;\n      });\n\n      deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});\n    });\n    return deffer.promise;\n  };\n\n  upload.imageDimensions = function (file) {\n    if (file.$ngfWidth && file.$ngfHeight) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});\n      });\n      return d.promise;\n    }\n    if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('image') !== 0) {\n        deferred.reject('not image');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var img = angular.element('<img>').attr('src', dataUrl)\n          .css('visibility', 'hidden').css('position', 'fixed')\n          .css('max-width', 'none !important').css('max-height', 'none !important');\n\n        function success() {\n          var width = img[0].naturalWidth || img[0].clientWidth;\n          var height = img[0].naturalHeight || img[0].clientHeight;\n          img.remove();\n          file.$ngfWidth = width;\n          file.$ngfHeight = height;\n          deferred.resolve({width: width, height: height});\n        }\n\n        function error() {\n          img.remove();\n          deferred.reject('load error');\n        }\n\n        img.on('load', success);\n        img.on('error', error);\n\n        var secondsCounter = 0;\n        function checkLoadErrorInCaseOfNoCallback() {\n          $timeout(function () {\n            if (img[0].parentNode) {\n              if (img[0].clientWidth) {\n                success();\n              } else if (secondsCounter++ > 10) {\n                error();\n              } else {\n                checkLoadErrorInCaseOfNoCallback();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadErrorInCaseOfNoCallback();\n\n        angular.element(document.getElementsByTagName('body')[0]).append(img);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDimensionPromise = deferred.promise;\n    file.$ngfDimensionPromise['finally'](function () {\n      delete file.$ngfDimensionPromise;\n    });\n    return file.$ngfDimensionPromise;\n  };\n\n  upload.mediaDuration = function (file) {\n    if (file.$ngfDuration) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve(file.$ngfDuration);\n      });\n      return d.promise;\n    }\n    if (file.$ngfDurationPromise) return file.$ngfDurationPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {\n        deferred.reject('not media');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')\n          .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');\n\n        function success() {\n          var duration = el[0].duration;\n          file.$ngfDuration = duration;\n          el.remove();\n          deferred.resolve(duration);\n        }\n\n        function error() {\n          el.remove();\n          deferred.reject('load error');\n        }\n\n        el.on('loadedmetadata', success);\n        el.on('error', error);\n        var count = 0;\n\n        function checkLoadError() {\n          $timeout(function () {\n            if (el[0].parentNode) {\n              if (el[0].duration) {\n                success();\n              } else if (count > 10) {\n                error();\n              } else {\n                checkLoadError();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadError();\n\n        angular.element(document.body).append(el);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDurationPromise = deferred.promise;\n    file.$ngfDurationPromise['finally'](function () {\n      delete file.$ngfDurationPromise;\n    });\n    return file.$ngfDurationPromise;\n  };\n  return upload;\n}\n]);\n\nngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {\n  var upload = UploadValidate;\n\n  /**\n   * Conserve aspect ratio of the original region. Useful when shrinking/enlarging\n   * images to fit into a certain area.\n   * Source:  http://stackoverflow.com/a/14731922\n   *\n   * @param {Number} srcWidth Source area width\n   * @param {Number} srcHeight Source area height\n   * @param {Number} maxWidth Nestable area maximum available width\n   * @param {Number} maxHeight Nestable area maximum available height\n   * @return {Object} { width, height }\n   */\n  var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {\n    var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :\n      Math.min(maxWidth / srcWidth, maxHeight / srcHeight);\n    return {\n      width: srcWidth * ratio, height: srcHeight * ratio,\n      marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight\n    };\n  };\n\n  // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89\n  var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {\n    var deferred = $q.defer();\n    var canvasElement = document.createElement('canvas');\n    var imageElement = document.createElement('img');\n    imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');\n    document.body.appendChild(imageElement);\n\n    imageElement.onload = function () {\n      var imgWidth = imageElement.width, imgHeight = imageElement.height;\n      imageElement.parentNode.removeChild(imageElement);\n      if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {\n        deferred.reject('resizeIf');\n        return;\n      }\n      try {\n        if (ratio) {\n          var ratioFloat = upload.ratioToFloat(ratio);\n          var imgRatio = imgWidth / imgHeight;\n          if (imgRatio < ratioFloat) {\n            width = imgWidth;\n            height = width / ratioFloat;\n          } else {\n            height = imgHeight;\n            width = height * ratioFloat;\n          }\n        }\n        if (!width) {\n          width = imgWidth;\n        }\n        if (!height) {\n          height = imgHeight;\n        }\n        var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);\n        canvasElement.width = Math.min(dimensions.width, width);\n        canvasElement.height = Math.min(dimensions.height, height);\n        var context = canvasElement.getContext('2d');\n        context.drawImage(imageElement,\n          Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),\n          dimensions.width, dimensions.height);\n        deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));\n      } catch (e) {\n        deferred.reject(e);\n      }\n    };\n    imageElement.onerror = function () {\n      imageElement.parentNode.removeChild(imageElement);\n      deferred.reject();\n    };\n    imageElement.src = imagen;\n    return deferred.promise;\n  };\n\n  upload.dataUrltoBlob = function (dataurl, name, origSize) {\n    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],\n      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n);\n    }\n    var blob = new window.Blob([u8arr], {type: mime});\n    blob.name = name;\n    blob.$ngfOrigSize = origSize;\n    return blob;\n  };\n\n  upload.isResizeSupported = function () {\n    var elem = document.createElement('canvas');\n    return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;\n  };\n\n  if (upload.isResizeSupported()) {\n    // add name getter to the blob constructor prototype\n    Object.defineProperty(window.Blob.prototype, 'name', {\n      get: function () {\n        return this.$ngfName;\n      },\n      set: function (v) {\n        this.$ngfName = v;\n      },\n      configurable: true\n    });\n  }\n\n  upload.resize = function (file, options) {\n    if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);\n\n    var deferred = $q.defer();\n    upload.dataUrl(file, true).then(function (url) {\n      resize(url, options.width, options.height, options.quality, options.type || file.type,\n        options.ratio, options.centerCrop, options.resizeIf)\n        .then(function (dataUrl) {\n          if (file.type === 'image/jpeg' && options.restoreExif !== false) {\n            try {\n              dataUrl = upload.restoreExif(url, dataUrl);\n            } catch (e) {\n              setTimeout(function () {throw e;}, 1);\n            }\n          }\n          try {\n            var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);\n            deferred.resolve(blob);\n          } catch (e) {\n            deferred.reject(e);\n          }\n        }, function (r) {\n          if (r === 'resizeIf') {\n            deferred.resolve(file);\n          }\n          deferred.reject(r);\n        });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  return upload;\n}]);\n\n(function () {\n  ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',\n    function ($parse, $timeout, $window, Upload, $http, $q) {\n      return {\n        restrict: 'AEC',\n        require: '?ngModel',\n        link: function (scope, elem, attr, ngModel) {\n          linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);\n        }\n      };\n    }]);\n\n  ngFileUpload.directive('ngfNoFileDrop', function () {\n    return function (scope, elem) {\n      if (dropAvailable()) elem.css('display', 'none');\n    };\n  });\n\n  ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {\n    return function (scope, elem, attr) {\n      if (dropAvailable()) {\n        var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));\n        $timeout(function () {\n          model(scope);\n          if (model.assign) {\n            model.assign(scope, true);\n          }\n        });\n      }\n    };\n  }]);\n\n  function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {\n    var available = dropAvailable();\n\n    var attrGetter = function (name, scope, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    if (attrGetter('dropAvailable')) {\n      $timeout(function () {\n        if (scope[attrGetter('dropAvailable')]) {\n          scope[attrGetter('dropAvailable')].value = available;\n        } else {\n          scope[attrGetter('dropAvailable')] = available;\n        }\n      });\n    }\n    if (!available) {\n      if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {\n        elem.css('display', 'none');\n      }\n      return;\n    }\n\n    function isDisabled() {\n      return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);\n    }\n\n    if (attrGetter('ngfSelect') == null) {\n      upload.registerModelChangeValidator(ngModel, attr, scope);\n    }\n\n    var leaveTimeout = null;\n    var stopPropagation = $parse(attrGetter('ngfStopPropagation'));\n    var dragOverDelay = 1;\n    var actualDragOverClass;\n\n    elem[0].addEventListener('dragover', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      // handling dragover events from the Chrome download bar\n      if (navigator.userAgent.indexOf('Chrome') > -1) {\n        var b = evt.dataTransfer.effectAllowed;\n        evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';\n      }\n      $timeout.cancel(leaveTimeout);\n      if (!actualDragOverClass) {\n        actualDragOverClass = 'C';\n        calculateDragOverClass(scope, attr, evt, function (clazz) {\n          actualDragOverClass = clazz;\n          elem.addClass(actualDragOverClass);\n          attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});\n        });\n      }\n    }, false);\n    elem[0].addEventListener('dragenter', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n    }, false);\n    elem[0].addEventListener('dragleave', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      leaveTimeout = $timeout(function () {\n        if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n        actualDragOverClass = null;\n        attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});\n      }, dragOverDelay || 100);\n    }, false);\n    elem[0].addEventListener('drop', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n      actualDragOverClass = null;\n      extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');\n    }, false);\n    elem[0].addEventListener('paste', function (evt) {\n      if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n        attrGetter('ngfEnableFirefoxPaste', scope)) {\n        evt.preventDefault();\n      }\n      if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;\n      extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');\n    }, false);\n\n    if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n      attrGetter('ngfEnableFirefoxPaste', scope)) {\n      elem.attr('contenteditable', true);\n      elem.on('keypress', function (e) {\n        if (!e.metaKey && !e.ctrlKey) {\n          e.preventDefault();\n        }\n      });\n    }\n\n    function extractFilesAndUpdateModel(source, evt, updateOnType) {\n      if (!source) return;\n      // html needs to be calculated on the same process otherwise the data will be wiped\n      // after promise resolve or setTimeout.\n      var html;\n      try {\n        html = source && source.getData && source.getData('text/html');\n      } catch (e) {/* Fix IE11 that throw error calling getData */\n      }\n      extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,\n        attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {\n        if (files.length) {\n          updateModel(files, evt);\n        } else {\n          extractFilesFromHtml(updateOnType, html).then(function (files) {\n            updateModel(files, evt);\n          });\n        }\n      });\n    }\n\n    function updateModel(files, evt) {\n      upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);\n    }\n\n    function extractFilesFromHtml(updateOn, html) {\n      if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);\n      var urls = [];\n      html.replace(/<(img src|img [^>]* src) *=\\\"([^\\\"]*)\\\"/gi, function (m, n, src) {\n        urls.push(src);\n      });\n      var promises = [], files = [];\n      if (urls.length) {\n        angular.forEach(urls, function (url) {\n          promises.push(upload.urlToBlob(url).then(function (blob) {\n            files.push(blob);\n          }));\n        });\n        var defer = $q.defer();\n        $q.all(promises).then(function () {\n          defer.resolve(files);\n        }, function (e) {\n          defer.reject(e);\n        });\n        return defer.promise;\n      }\n      return upload.emptyPromise();\n    }\n\n    function calculateDragOverClass(scope, attr, evt, callback) {\n      var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';\n      if (angular.isString(obj)) {\n        dClass = obj;\n      } else if (obj) {\n        if (obj.delay) dragOverDelay = obj.delay;\n        if (obj.accept || obj.reject) {\n          var items = evt.dataTransfer.items;\n          if (items == null || !items.length) {\n            dClass = obj.accept;\n          } else {\n            var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});\n            var len = items.length;\n            while (len--) {\n              if (!upload.validatePattern(items[len], pattern)) {\n                dClass = obj.reject;\n                break;\n              } else {\n                dClass = obj.accept;\n              }\n            }\n          }\n        }\n      }\n      callback(dClass);\n    }\n\n    function extractFiles(items, fileList, allowDir, multiple) {\n      var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');\n      if (maxFiles == null) {\n        maxFiles = Number.MAX_VALUE;\n      }\n      var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');\n      if (maxTotalSize == null) {\n        maxTotalSize = Number.MAX_VALUE;\n      }\n      var includeDir = attrGetter('ngfIncludeDir', scope);\n      var files = [], totalSize = 0;\n\n      function traverseFileTree(entry, path) {\n        var defer = $q.defer();\n        if (entry != null) {\n          if (entry.isDirectory) {\n            var promises = [upload.emptyPromise()];\n            if (includeDir) {\n              var file = {type: 'directory'};\n              file.name = file.path = (path || '') + entry.name;\n              files.push(file);\n            }\n            var dirReader = entry.createReader();\n            var entries = [];\n            var readEntries = function () {\n              dirReader.readEntries(function (results) {\n                try {\n                  if (!results.length) {\n                    angular.forEach(entries.slice(0), function (e) {\n                      if (files.length <= maxFiles && totalSize <= maxTotalSize) {\n                        promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));\n                      }\n                    });\n                    $q.all(promises).then(function () {\n                      defer.resolve();\n                    }, function (e) {\n                      defer.reject(e);\n                    });\n                  } else {\n                    entries = entries.concat(Array.prototype.slice.call(results || [], 0));\n                    readEntries();\n                  }\n                } catch (e) {\n                  defer.reject(e);\n                }\n              }, function (e) {\n                defer.reject(e);\n              });\n            };\n            readEntries();\n          } else {\n            entry.file(function (file) {\n              try {\n                file.path = (path ? path : '') + file.name;\n                if (includeDir) {\n                  file = upload.rename(file, file.path);\n                }\n                files.push(file);\n                totalSize += file.size;\n                defer.resolve();\n              } catch (e) {\n                defer.reject(e);\n              }\n            }, function (e) {\n              defer.reject(e);\n            });\n          }\n        }\n        return defer.promise;\n      }\n\n      var promises = [upload.emptyPromise()];\n\n      if (items && items.length > 0 && $window.location.protocol !== 'file:') {\n        for (var i = 0; i < items.length; i++) {\n          if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {\n            var entry = items[i].webkitGetAsEntry();\n            if (entry.isDirectory && !allowDir) {\n              continue;\n            }\n            if (entry != null) {\n              promises.push(traverseFileTree(entry));\n            }\n          } else {\n            var f = items[i].getAsFile();\n            if (f != null) {\n              files.push(f);\n              totalSize += f.size;\n            }\n          }\n          if (files.length > maxFiles || totalSize > maxTotalSize ||\n            (!multiple && files.length > 0)) break;\n        }\n      } else {\n        if (fileList != null) {\n          for (var j = 0; j < fileList.length; j++) {\n            var file = fileList.item(j);\n            if (file.type || file.size > 0) {\n              files.push(file);\n              totalSize += file.size;\n            }\n            if (files.length > maxFiles || totalSize > maxTotalSize ||\n              (!multiple && files.length > 0)) break;\n          }\n        }\n      }\n\n      var defer = $q.defer();\n      $q.all(promises).then(function () {\n        if (!multiple && !includeDir && files.length) {\n          var i = 0;\n          while (files[i] && files[i].type === 'directory') i++;\n          defer.resolve([files[i]]);\n        } else {\n          defer.resolve(files);\n        }\n      }, function (e) {\n        defer.reject(e);\n      });\n\n      return defer.promise;\n    }\n  }\n\n  function dropAvailable() {\n    var div = document.createElement('div');\n    return ('draggable' in div) && ('ondrop' in div) && !/Edge\\/12./i.test(navigator.userAgent);\n  }\n\n})();\n\n// customized version of https://github.com/exif-js/exif-js\nngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {\n  var upload = UploadResize;\n\n  upload.isExifSupported = function () {\n    return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();\n  };\n\n  function applyTransform(ctx, orientation, width, height) {\n    switch (orientation) {\n      case 2:\n        return ctx.transform(-1, 0, 0, 1, width, 0);\n      case 3:\n        return ctx.transform(-1, 0, 0, -1, width, height);\n      case 4:\n        return ctx.transform(1, 0, 0, -1, 0, height);\n      case 5:\n        return ctx.transform(0, 1, 1, 0, 0, 0);\n      case 6:\n        return ctx.transform(0, 1, -1, 0, height, 0);\n      case 7:\n        return ctx.transform(0, -1, -1, 0, height, width);\n      case 8:\n        return ctx.transform(0, -1, 1, 0, 0, width);\n    }\n  }\n\n  upload.readOrientation = function (file) {\n    var defer = $q.defer();\n    var reader = new FileReader();\n    var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;\n    reader.readAsArrayBuffer(slicedFile);\n    reader.onerror = function (e) {\n      return defer.reject(e);\n    };\n    reader.onload = function (e) {\n      var result = {orientation: 1};\n      var view = new DataView(this.result);\n      if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);\n\n      var length = view.byteLength,\n        offset = 2;\n      while (offset < length) {\n        var marker = view.getUint16(offset, false);\n        offset += 2;\n        if (marker === 0xFFE1) {\n          if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);\n\n          var little = view.getUint16(offset += 6, false) === 0x4949;\n          offset += view.getUint32(offset + 4, little);\n          var tags = view.getUint16(offset, little);\n          offset += 2;\n          for (var i = 0; i < tags; i++)\n            if (view.getUint16(offset + (i * 12), little) === 0x0112) {\n              var orientation = view.getUint16(offset + (i * 12) + 8, little);\n              if (orientation >= 2 && orientation <= 8) {\n                view.setUint16(offset + (i * 12) + 8, 1, little);\n                result.fixedArrayBuffer = e.target.result;\n              }\n              result.orientation = orientation;\n              return defer.resolve(result);\n            }\n        } else if ((marker & 0xFF00) !== 0xFF00) break;\n        else offset += view.getUint16(offset, false);\n      }\n      return defer.resolve(result);\n    };\n    return defer.promise;\n  };\n\n  function arrayBufferToBase64(buffer) {\n    var binary = '';\n    var bytes = new Uint8Array(buffer);\n    var len = bytes.byteLength;\n    for (var i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return window.btoa(binary);\n  }\n\n  upload.applyExifRotation = function (file) {\n    if (file.type.indexOf('image/jpeg') !== 0) {\n      return upload.emptyPromise(file);\n    }\n\n    var deferred = $q.defer();\n    upload.readOrientation(file).then(function (result) {\n      if (result.orientation < 2 || result.orientation > 8) {\n        return deferred.resolve(file);\n      }\n      upload.dataUrl(file, true).then(function (url) {\n        var canvas = document.createElement('canvas');\n        var img = document.createElement('img');\n\n        img.onload = function () {\n          try {\n            canvas.width = result.orientation > 4 ? img.height : img.width;\n            canvas.height = result.orientation > 4 ? img.width : img.height;\n            var ctx = canvas.getContext('2d');\n            applyTransform(ctx, result.orientation, img.width, img.height);\n            ctx.drawImage(img, 0, 0);\n            var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);\n            dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);\n            var blob = upload.dataUrltoBlob(dataUrl, file.name);\n            deferred.resolve(blob);\n          } catch (e) {\n            return deferred.reject(e);\n          }\n        };\n        img.onerror = function () {\n          deferred.reject();\n        };\n        img.src = url;\n      }, function (e) {\n        deferred.reject(e);\n      });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  upload.restoreExif = function (orig, resized) {\n    var ExifRestorer = {};\n\n    ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +\n      'QRSTUVWXYZabcdef' +\n      'ghijklmnopqrstuv' +\n      'wxyz0123456789+/' +\n      '=';\n\n    ExifRestorer.encode64 = function (input) {\n      var output = '',\n        chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0;\n\n      do {\n        chr1 = input[i++];\n        chr2 = input[i++];\n        chr3 = input[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n          enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n          enc4 = 64;\n        }\n\n        output = output +\n          this.KEY_STR.charAt(enc1) +\n          this.KEY_STR.charAt(enc2) +\n          this.KEY_STR.charAt(enc3) +\n          this.KEY_STR.charAt(enc4);\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n      } while (i < input.length);\n\n      return output;\n    };\n\n    ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {\n      if (origFileBase64.match('data:image/jpeg;base64,')) {\n        origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');\n      }\n\n      var rawImage = this.decode64(origFileBase64);\n      var segments = this.slice2Segments(rawImage);\n\n      var image = this.exifManipulation(resizedFileBase64, segments);\n\n      return 'data:image/jpeg;base64,' + this.encode64(image);\n    };\n\n\n    ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {\n      var exifArray = this.getExifArray(segments),\n        newImageArray = this.insertExif(resizedFileBase64, exifArray);\n      return new Uint8Array(newImageArray);\n    };\n\n\n    ExifRestorer.getExifArray = function (segments) {\n      var seg;\n      for (var x = 0; x < segments.length; x++) {\n        seg = segments[x];\n        if (seg[0] === 255 & seg[1] === 225) //(ff e1)\n        {\n          return seg;\n        }\n      }\n      return [];\n    };\n\n\n    ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {\n      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),\n        buf = this.decode64(imageData),\n        separatePoint = buf.indexOf(255, 3),\n        mae = buf.slice(0, separatePoint),\n        ato = buf.slice(separatePoint),\n        array = mae;\n\n      array = array.concat(exifArray);\n      array = array.concat(ato);\n      return array;\n    };\n\n\n    ExifRestorer.slice2Segments = function (rawImageArray) {\n      var head = 0,\n        segments = [];\n\n      while (1) {\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {\n          break;\n        }\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {\n          head += 2;\n        }\n        else {\n          var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n            endPoint = head + length + 2,\n            seg = rawImageArray.slice(head, endPoint);\n          segments.push(seg);\n          head = endPoint;\n        }\n        if (head > rawImageArray.length) {\n          break;\n        }\n      }\n\n      return segments;\n    };\n\n\n    ExifRestorer.decode64 = function (input) {\n      var chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0,\n        buf = [];\n\n      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n      var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n      if (base64test.exec(input)) {\n        console.log('There were invalid base64 characters in the input text.\\n' +\n          'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and \"=\"\\n' +\n          'Expect errors in decoding.');\n      }\n      input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, '');\n\n      do {\n        enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        buf.push(chr1);\n\n        if (enc3 !== 64) {\n          buf.push(chr2);\n        }\n        if (enc4 !== 64) {\n          buf.push(chr3);\n        }\n\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n\n      } while (i < input.length);\n\n      return buf;\n    };\n\n    return ExifRestorer.restore(orig, resized);  //<= EXIF\n  };\n\n  return upload;\n}]);\n\n"
  },
  {
    "path": "demo/src/main/webapp/js/ng-file-upload-shim.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * FileAPI Flash shim for old browsers not supporting FormData\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\n(function () {\n  /** @namespace FileAPI.noContentTimeout */\n\n  function patchXHR(fnName, newFn) {\n    window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);\n  }\n\n  function redefineProp(xhr, prop, fn) {\n    try {\n      Object.defineProperty(xhr, prop, {get: fn});\n    } catch (e) {/*ignore*/\n    }\n  }\n\n  if (!window.FileAPI) {\n    window.FileAPI = {};\n  }\n\n  if (!window.XMLHttpRequest) {\n    throw 'AJAX is not supported. XMLHttpRequest is not defined.';\n  }\n\n  FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;\n  if (FileAPI.shouldLoad) {\n    var initializeUploadListener = function (xhr) {\n      if (!xhr.__listeners) {\n        if (!xhr.upload) xhr.upload = {};\n        xhr.__listeners = [];\n        var origAddEventListener = xhr.upload.addEventListener;\n        xhr.upload.addEventListener = function (t, fn) {\n          xhr.__listeners[t] = fn;\n          if (origAddEventListener) origAddEventListener.apply(this, arguments);\n        };\n      }\n    };\n\n    patchXHR('open', function (orig) {\n      return function (m, url, b) {\n        initializeUploadListener(this);\n        this.__url = url;\n        try {\n          orig.apply(this, [m, url, b]);\n        } catch (e) {\n          if (e.message.indexOf('Access is denied') > -1) {\n            this.__origError = e;\n            orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);\n          }\n        }\n      };\n    });\n\n    patchXHR('getResponseHeader', function (orig) {\n      return function (h) {\n        return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));\n      };\n    });\n\n    patchXHR('getAllResponseHeaders', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('abort', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('setRequestHeader', function (orig) {\n      return function (header, value) {\n        if (header === '__setXHR_') {\n          initializeUploadListener(this);\n          var val = value(this);\n          // fix for angular < 1.2.0\n          if (val instanceof Function) {\n            val(this);\n          }\n        } else {\n          this.__requestHeaders = this.__requestHeaders || {};\n          this.__requestHeaders[header] = value;\n          orig.apply(this, arguments);\n        }\n      };\n    });\n\n    patchXHR('send', function (orig) {\n      return function () {\n        var xhr = this;\n        if (arguments[0] && arguments[0].__isFileAPIShim) {\n          var formData = arguments[0];\n          var config = {\n            url: xhr.__url,\n            jsonp: false, //removes the callback form param\n            cache: true, //removes the ?fileapiXXX in the url\n            complete: function (err, fileApiXHR) {\n              if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {\n                // this error seems to be fine the file is being uploaded properly.\n                err = null;\n              }\n              xhr.__completed = true;\n              if (!err && xhr.__listeners.load)\n                xhr.__listeners.load({\n                  type: 'load',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (!err && xhr.__listeners.loadend)\n                xhr.__listeners.loadend({\n                  type: 'loadend',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (err === 'abort' && xhr.__listeners.abort)\n                xhr.__listeners.abort({\n                  type: 'abort',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {\n                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;\n              });\n              if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {\n                return fileApiXHR.statusText;\n              });\n              redefineProp(xhr, 'readyState', function () {\n                return 4;\n              });\n              if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {\n                return fileApiXHR.response;\n              });\n              var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);\n              redefineProp(xhr, 'responseText', function () {\n                return resp;\n              });\n              redefineProp(xhr, 'response', function () {\n                return resp;\n              });\n              if (err) redefineProp(xhr, 'err', function () {\n                return err;\n              });\n              xhr.__fileApiXHR = fileApiXHR;\n              if (xhr.onreadystatechange) xhr.onreadystatechange();\n              if (xhr.onload) xhr.onload();\n            },\n            progress: function (e) {\n              e.target = xhr;\n              if (xhr.__listeners.progress) xhr.__listeners.progress(e);\n              xhr.__total = e.total;\n              xhr.__loaded = e.loaded;\n              if (e.total === e.loaded) {\n                // fix flash issue that doesn't call complete if there is no response text from the server\n                var _this = this;\n                setTimeout(function () {\n                  if (!xhr.__completed) {\n                    xhr.getAllResponseHeaders = function () {\n                    };\n                    _this.complete(null, {status: 204, statusText: 'No Content'});\n                  }\n                }, FileAPI.noContentTimeout || 10000);\n              }\n            },\n            headers: xhr.__requestHeaders\n          };\n          config.data = {};\n          config.files = {};\n          for (var i = 0; i < formData.data.length; i++) {\n            var item = formData.data[i];\n            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {\n              config.files[item.key] = item.val;\n            } else {\n              config.data[item.key] = item.val;\n            }\n          }\n\n          setTimeout(function () {\n            if (!FileAPI.hasFlash) {\n              throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n            }\n            xhr.__fileApiXHR = FileAPI.upload(config);\n          }, 1);\n        } else {\n          if (this.__origError) {\n            throw this.__origError;\n          }\n          orig.apply(xhr, arguments);\n        }\n      };\n    });\n    window.XMLHttpRequest.__isFileAPIShim = true;\n    window.FormData = FormData = function () {\n      return {\n        append: function (key, val, name) {\n          if (val.__isFileAPIBlobShim) {\n            val = val.data[0];\n          }\n          this.data.push({\n            key: key,\n            val: val,\n            name: name\n          });\n        },\n        data: [],\n        __isFileAPIShim: true\n      };\n    };\n\n    window.Blob = Blob = function (b) {\n      return {\n        data: b,\n        __isFileAPIBlobShim: true\n      };\n    };\n  }\n\n})();\n\n(function () {\n  /** @namespace FileAPI.forceLoad */\n  /** @namespace window.FileAPI.jsUrl */\n  /** @namespace window.FileAPI.jsPath */\n\n  function isInputTypeFile(elem) {\n    return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';\n  }\n\n  function hasFlash() {\n    try {\n      var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n      if (fo) return true;\n    } catch (e) {\n      if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;\n    }\n    return false;\n  }\n\n  function getOffset(obj) {\n    var left = 0, top = 0;\n\n    if (window.jQuery) {\n      return jQuery(obj).offset();\n    }\n\n    if (obj.offsetParent) {\n      do {\n        left += (obj.offsetLeft - obj.scrollLeft);\n        top += (obj.offsetTop - obj.scrollTop);\n        obj = obj.offsetParent;\n      } while (obj);\n    }\n    return {\n      left: left,\n      top: top\n    };\n  }\n\n  if (FileAPI.shouldLoad) {\n    FileAPI.hasFlash = hasFlash();\n\n    //load FileAPI\n    if (FileAPI.forceLoad) {\n      FileAPI.html5 = false;\n    }\n\n    if (!FileAPI.upload) {\n      var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;\n      if (window.FileAPI.jsUrl) {\n        jsUrl = window.FileAPI.jsUrl;\n      } else if (window.FileAPI.jsPath) {\n        basePath = window.FileAPI.jsPath;\n      } else {\n        for (i = 0; i < allScripts.length; i++) {\n          src = allScripts[i].src;\n          index = src.search(/\\/ng\\-file\\-upload[\\-a-zA-z0-9\\.]*\\.js/);\n          if (index > -1) {\n            basePath = src.substring(0, index + 1);\n            break;\n          }\n        }\n      }\n\n      if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;\n      script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');\n      document.getElementsByTagName('head')[0].appendChild(script);\n    }\n\n    FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {\n      if (!hasFlash()) {\n        throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n      }\n      var fixInputStyle = function () {\n        var label = fileElem.parent();\n        if (elem.attr('disabled')) {\n          if (label) label.removeClass('js-fileapi-wrapper');\n        } else {\n          if (!fileElem.attr('__ngf_flash_')) {\n            fileElem.unbind('change');\n            fileElem.unbind('click');\n            fileElem.bind('change', function (evt) {\n              fileApiChangeFn.apply(this, [evt]);\n              changeFn.apply(this, [evt]);\n            });\n            fileElem.attr('__ngf_flash_', 'true');\n          }\n          label.addClass('js-fileapi-wrapper');\n          if (!isInputTypeFile(elem)) {\n            label.css('position', 'absolute')\n              .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')\n              .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))\n              .css('overflow', 'hidden').css('z-index', '900000')\n              .css('visibility', 'visible');\n            fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('position', 'absolute').css('top', '0px').css('left', '0px');\n          }\n        }\n      };\n\n      elem.bind('mouseenter', fixInputStyle);\n\n      var fileApiChangeFn = function (evt) {\n        var files = FileAPI.getFiles(evt);\n        //just a double check for #233\n        for (var i = 0; i < files.length; i++) {\n          if (files[i].size === undefined) files[i].size = 0;\n          if (files[i].name === undefined) files[i].name = 'file';\n          if (files[i].type === undefined) files[i].type = 'undefined';\n        }\n        if (!evt.target) {\n          evt.target = {};\n        }\n        evt.target.files = files;\n        // if evt.target.files is not writable use helper field\n        if (evt.target.files !== files) {\n          evt.__files_ = files;\n        }\n        (evt.__files_ || evt.target.files).item = function (i) {\n          return (evt.__files_ || evt.target.files)[i] || null;\n        };\n      };\n    };\n\n    FileAPI.disableFileInput = function (elem, disable) {\n      if (disable) {\n        elem.removeClass('js-fileapi-wrapper');\n      } else {\n        elem.addClass('js-fileapi-wrapper');\n      }\n    };\n  }\n})();\n\nif (!window.FileReader) {\n  window.FileReader = function () {\n    var _this = this, loadStarted = false;\n    this.listeners = {};\n    this.addEventListener = function (type, fn) {\n      _this.listeners[type] = _this.listeners[type] || [];\n      _this.listeners[type].push(fn);\n    };\n    this.removeEventListener = function (type, fn) {\n      if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);\n    };\n    this.dispatchEvent = function (evt) {\n      var list = _this.listeners[evt.type];\n      if (list) {\n        for (var i = 0; i < list.length; i++) {\n          list[i].call(_this, evt);\n        }\n      }\n    };\n    this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;\n\n    var constructEvent = function (type, evt) {\n      var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};\n      if (evt.result != null) e.target.result = evt.result;\n      return e;\n    };\n    var listener = function (evt) {\n      if (!loadStarted) {\n        loadStarted = true;\n        if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));\n      }\n      var e;\n      if (evt.type === 'load') {\n        if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));\n        e = constructEvent('load', evt);\n        if (_this.onload) _this.onload(e);\n        _this.dispatchEvent(e);\n      } else if (evt.type === 'progress') {\n        e = constructEvent('progress', evt);\n        if (_this.onprogress) _this.onprogress(e);\n        _this.dispatchEvent(e);\n      } else {\n        e = constructEvent('error', evt);\n        if (_this.onerror) _this.onerror(e);\n        _this.dispatchEvent(e);\n      }\n    };\n    this.readAsDataURL = function (file) {\n      FileAPI.readAsDataURL(file, listener);\n    };\n    this.readAsText = function (file) {\n      FileAPI.readAsText(file, listener);\n    };\n  };\n}\n"
  },
  {
    "path": "demo/src/main/webapp/js/ng-file-upload.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\nif (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {\n  window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {\n    return function (header, value) {\n      if (header === '__setXHR_') {\n        var val = value(this);\n        // fix for angular < 1.2.0\n        if (val instanceof Function) {\n          val(this);\n        }\n      } else {\n        orig.apply(this, arguments);\n      }\n    };\n  })(window.XMLHttpRequest.prototype.setRequestHeader);\n}\n\nvar ngFileUpload = angular.module('ngFileUpload', []);\n\nngFileUpload.version = '12.2.13';\n\nngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {\n  var upload = this;\n  upload.promisesCount = 0;\n\n  this.isResumeSupported = function () {\n    return window.Blob && window.Blob.prototype.slice;\n  };\n\n  var resumeSupported = this.isResumeSupported();\n\n  function sendHttp(config) {\n    config.method = config.method || 'POST';\n    config.headers = config.headers || {};\n\n    var deferred = config._deferred = config._deferred || $q.defer();\n    var promise = deferred.promise;\n\n    function notifyProgress(e) {\n      if (deferred.notify) {\n        deferred.notify(e);\n      }\n      if (promise.progressFunc) {\n        $timeout(function () {\n          promise.progressFunc(e);\n        });\n      }\n    }\n\n    function getNotifyEvent(n) {\n      if (config._start != null && resumeSupported) {\n        return {\n          loaded: n.loaded + config._start,\n          total: (config._file && config._file.size) || n.total,\n          type: n.type, config: config,\n          lengthComputable: true, target: n.target\n        };\n      } else {\n        return n;\n      }\n    }\n\n    if (!config.disableProgress) {\n      config.headers.__setXHR_ = function () {\n        return function (xhr) {\n          if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;\n          config.__XHR = xhr;\n          if (config.xhrFn) config.xhrFn(xhr);\n          xhr.upload.addEventListener('progress', function (e) {\n            e.config = config;\n            notifyProgress(getNotifyEvent(e));\n          }, false);\n          //fix for firefox not firing upload progress end, also IE8-9\n          xhr.upload.addEventListener('load', function (e) {\n            if (e.lengthComputable) {\n              e.config = config;\n              notifyProgress(getNotifyEvent(e));\n            }\n          }, false);\n        };\n      };\n    }\n\n    function uploadWithAngular() {\n      $http(config).then(function (r) {\n          if (resumeSupported && config._chunkSize && !config._finished && config._file) {\n            var fileSize = config._file && config._file.size || 0;\n            notifyProgress({\n                loaded: Math.min(config._end, fileSize),\n                total: fileSize,\n                config: config,\n                type: 'progress'\n              }\n            );\n            upload.upload(config, true);\n          } else {\n            if (config._finished) delete config._finished;\n            deferred.resolve(r);\n          }\n        }, function (e) {\n          deferred.reject(e);\n        }, function (n) {\n          deferred.notify(n);\n        }\n      );\n    }\n\n    if (!resumeSupported) {\n      uploadWithAngular();\n    } else if (config._chunkSize && config._end && !config._finished) {\n      config._start = config._end;\n      config._end += config._chunkSize;\n      uploadWithAngular();\n    } else if (config.resumeSizeUrl) {\n      $http.get(config.resumeSizeUrl).then(function (resp) {\n        if (config.resumeSizeResponseReader) {\n          config._start = config.resumeSizeResponseReader(resp.data);\n        } else {\n          config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());\n        }\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else if (config.resumeSize) {\n      config.resumeSize().then(function (size) {\n        config._start = size;\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else {\n      if (config._chunkSize) {\n        config._start = 0;\n        config._end = config._start + config._chunkSize;\n      }\n      uploadWithAngular();\n    }\n\n\n    promise.success = function (fn) {\n      promise.then(function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.error = function (fn) {\n      promise.then(null, function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.progress = function (fn) {\n      promise.progressFunc = fn;\n      promise.then(null, null, function (n) {\n        fn(n);\n      });\n      return promise;\n    };\n    promise.abort = promise.pause = function () {\n      if (config.__XHR) {\n        $timeout(function () {\n          config.__XHR.abort();\n        });\n      }\n      return promise;\n    };\n    promise.xhr = function (fn) {\n      config.xhrFn = (function (origXhrFn) {\n        return function () {\n          if (origXhrFn) origXhrFn.apply(promise, arguments);\n          fn.apply(promise, arguments);\n        };\n      })(config.xhrFn);\n      return promise;\n    };\n\n    upload.promisesCount++;\n    if (promise['finally'] && promise['finally'] instanceof Function) {\n      promise['finally'](function () {\n        upload.promisesCount--;\n      });\n    }\n    return promise;\n  }\n\n  this.isUploadInProgress = function () {\n    return upload.promisesCount > 0;\n  };\n\n  this.rename = function (file, name) {\n    file.ngfName = name;\n    return file;\n  };\n\n  this.jsonBlob = function (val) {\n    if (val != null && !angular.isString(val)) {\n      val = JSON.stringify(val);\n    }\n    var blob = new window.Blob([val], {type: 'application/json'});\n    blob._ngfBlob = true;\n    return blob;\n  };\n\n  this.json = function (val) {\n    return angular.toJson(val);\n  };\n\n  function copy(obj) {\n    var clone = {};\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        clone[key] = obj[key];\n      }\n    }\n    return clone;\n  }\n\n  this.isFile = function (file) {\n    return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));\n  };\n\n  this.upload = function (config, internal) {\n    function toResumeFile(file, formData) {\n      if (file._ngfBlob) return file;\n      config._file = config._file || file;\n      if (config._start != null && resumeSupported) {\n        if (config._end && config._end >= file.size) {\n          config._finished = true;\n          config._end = file.size;\n        }\n        var slice = file.slice(config._start, config._end || file.size);\n        slice.name = file.name;\n        slice.ngfName = file.ngfName;\n        if (config._chunkSize) {\n          formData.append('_chunkSize', config._chunkSize);\n          formData.append('_currentChunkSize', config._end - config._start);\n          formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));\n          formData.append('_totalSize', config._file.size);\n        }\n        return slice;\n      }\n      return file;\n    }\n\n    function addFieldToFormData(formData, val, key) {\n      if (val !== undefined) {\n        if (angular.isDate(val)) {\n          val = val.toISOString();\n        }\n        if (angular.isString(val)) {\n          formData.append(key, val);\n        } else if (upload.isFile(val)) {\n          var file = toResumeFile(val, formData);\n          var split = key.split(',');\n          if (split[1]) {\n            file.ngfName = split[1].replace(/^\\s+|\\s+$/g, '');\n            key = split[0];\n          }\n          config._fileKey = config._fileKey || key;\n          formData.append(key, file, file.ngfName || file.name);\n        } else {\n          if (angular.isObject(val)) {\n            if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;\n\n            val.$$ngfCircularDetection = true;\n            try {\n              for (var k in val) {\n                if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {\n                  var objectKey = config.objectKey == null ? '[i]' : config.objectKey;\n                  if (val.length && parseInt(k) > -1) {\n                    objectKey = config.arrayKey == null ? objectKey : config.arrayKey;\n                  }\n                  addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));\n                }\n              }\n            } finally {\n              delete val.$$ngfCircularDetection;\n            }\n          } else {\n            formData.append(key, val);\n          }\n        }\n      }\n    }\n\n    function digestConfig() {\n      config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n      config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n      config.headers = config.headers || {};\n      config.headers['Content-Type'] = undefined;\n      config.transformRequest = config.transformRequest ?\n        (angular.isArray(config.transformRequest) ?\n          config.transformRequest : [config.transformRequest]) : [];\n      config.transformRequest.push(function (data) {\n        var formData = new window.FormData(), key;\n        data = data || config.fields || {};\n        if (config.file) {\n          data.file = config.file;\n        }\n        for (key in data) {\n          if (data.hasOwnProperty(key)) {\n            var val = data[key];\n            if (config.formDataAppender) {\n              config.formDataAppender(formData, key, val);\n            } else {\n              addFieldToFormData(formData, val, key);\n            }\n          }\n        }\n\n        return formData;\n      });\n    }\n\n    if (!internal) config = copy(config);\n    if (!config._isDigested) {\n      config._isDigested = true;\n      digestConfig();\n    }\n\n    return sendHttp(config);\n  };\n\n  this.http = function (config) {\n    config = copy(config);\n    config.transformRequest = config.transformRequest || function (data) {\n        if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {\n          return data;\n        }\n        return $http.defaults.transformRequest[0].apply(this, arguments);\n      };\n    config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n    config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n    return sendHttp(config);\n  };\n\n  this.translateScalars = function (str) {\n    if (angular.isString(str)) {\n      if (str.search(/kb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1024);\n      } else if (str.search(/mb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1048576);\n      } else if (str.search(/gb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1073741824);\n      } else if (str.search(/b/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/s/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/m/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 60);\n      } else if (str.search(/h/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 3600);\n      }\n    }\n    return str;\n  };\n\n  this.urlToBlob = function(url) {\n    var defer = $q.defer();\n    $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {\n      var arrayBufferView = new Uint8Array(resp.data);\n      var type = resp.headers('content-type') || 'image/WebP';\n      var blob = new window.Blob([arrayBufferView], {type: type});\n      var matches = url.match(/.*\\/(.+?)(\\?.*)?$/);\n      if (matches.length > 1) {\n        blob.name = matches[1];\n      }\n      defer.resolve(blob);\n    }, function (e) {\n      defer.reject(e);\n    });\n    return defer.promise;\n  };\n\n  this.setDefaults = function (defaults) {\n    this.defaults = defaults || {};\n  };\n\n  this.defaults = {};\n  this.version = ngFileUpload.version;\n}\n\n]);\n\nngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {\n  var upload = UploadExif;\n  upload.getAttrWithDefaults = function (attr, name) {\n    if (attr[name] != null) return attr[name];\n    var def = upload.defaults[name];\n    return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));\n  };\n\n  upload.attrGetter = function (name, attr, scope, params) {\n    var attrVal = this.getAttrWithDefaults(attr, name);\n    if (scope) {\n      try {\n        if (params) {\n          return $parse(attrVal)(scope, params);\n        } else {\n          return $parse(attrVal)(scope);\n        }\n      } catch (e) {\n        // hangle string value without single qoute\n        if (name.search(/min|max|pattern/i)) {\n          return attrVal;\n        } else {\n          throw e;\n        }\n      }\n    } else {\n      return attrVal;\n    }\n  };\n\n  upload.shouldUpdateOn = function (type, attr, scope) {\n    var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);\n    if (modelOptions && modelOptions.updateOn) {\n      return modelOptions.updateOn.split(' ').indexOf(type) > -1;\n    }\n    return true;\n  };\n\n  upload.emptyPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.resolve.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.rejectPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.reject.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.happyPromise = function (promise, data) {\n    var d = $q.defer();\n    promise.then(function (result) {\n      d.resolve(result);\n    }, function (error) {\n      $timeout(function () {\n        throw error;\n      });\n      d.resolve(data);\n    });\n    return d.promise;\n  };\n\n  function applyExifRotations(files, attr, scope) {\n    var promises = [upload.emptyPromise()];\n    angular.forEach(files, function (f, i) {\n      if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {\n        promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {\n          files.splice(i, 1, fixedFile);\n        }));\n      }\n    });\n    return $q.all(promises);\n  }\n\n  function resizeFile(files, attr, scope, ngModel) {\n    var resizeVal = upload.attrGetter('ngfResize', attr, scope);\n    if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();\n    if (resizeVal instanceof Function) {\n      var defer = $q.defer();\n      return resizeVal(files).then(function (p) {\n        resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {\n          defer.resolve(r);\n        }, function (e) {\n          defer.reject(e);\n        });\n      }, function (e) {\n        defer.reject(e);\n      });\n    } else {\n      return resizeWithParams(resizeVal, files, attr, scope, ngModel);\n    }\n  }\n\n  function resizeWithParams(params, files, attr, scope, ngModel) {\n    var promises = [upload.emptyPromise()];\n\n    function handleFile(f, i) {\n      if (f.type.indexOf('image') === 0) {\n        if (params.pattern && !upload.validatePattern(f, params.pattern)) return;\n        params.resizeIf = function (width, height) {\n          return upload.attrGetter('ngfResizeIf', attr, scope,\n            {$width: width, $height: height, $file: f});\n        };\n        var promise = upload.resize(f, params);\n        promises.push(promise);\n        promise.then(function (resizedFile) {\n          files.splice(i, 1, resizedFile);\n        }, function (e) {\n          f.$error = 'resize';\n          (f.$errorMessages = (f.$errorMessages || {})).resize = true;\n          f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);\n          ngModel.$ngfValidations.push({name: 'resize', valid: false});\n          upload.applyModelValidation(ngModel, files);\n        });\n      }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n      handleFile(files[i], i);\n    }\n    return $q.all(promises);\n  }\n\n  upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {\n    function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {\n      attr.$$ngfPrevValidFiles = files;\n      attr.$$ngfPrevInvalidFiles = invalidFiles;\n      var file = files && files.length ? files[0] : null;\n      var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;\n\n      if (ngModel) {\n        upload.applyModelValidation(ngModel, files);\n        ngModel.$setViewValue(isSingleModel ? file : files);\n      }\n\n      if (fileChange) {\n        $parse(fileChange)(scope, {\n          $files: files,\n          $file: file,\n          $newFiles: newFiles,\n          $duplicateFiles: dupFiles,\n          $invalidFiles: invalidFiles,\n          $invalidFile: invalidFile,\n          $event: evt\n        });\n      }\n\n      var invalidModel = upload.attrGetter('ngfModelInvalid', attr);\n      if (invalidModel) {\n        $timeout(function () {\n          $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);\n        });\n      }\n      $timeout(function () {\n        // scope apply changes\n      });\n    }\n\n    var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,\n      invalids = [], valids = [];\n\n    function removeDuplicates() {\n      function equals(f1, f2) {\n        return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&\n          f1.type === f2.type;\n      }\n\n      function isInPrevFiles(f) {\n        var j;\n        for (j = 0; j < prevValidFiles.length; j++) {\n          if (equals(f, prevValidFiles[j])) {\n            return true;\n          }\n        }\n        for (j = 0; j < prevInvalidFiles.length; j++) {\n          if (equals(f, prevInvalidFiles[j])) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n      if (files) {\n        allNewFiles = [];\n        dupFiles = [];\n        for (var i = 0; i < files.length; i++) {\n          if (isInPrevFiles(files[i])) {\n            dupFiles.push(files[i]);\n          } else {\n            allNewFiles.push(files[i]);\n          }\n        }\n      }\n    }\n\n    function toArray(v) {\n      return angular.isArray(v) ? v : [v];\n    }\n\n    function resizeAndUpdate() {\n      function updateModel() {\n        $timeout(function () {\n          update(keep ? prevValidFiles.concat(valids) : valids,\n            keep ? prevInvalidFiles.concat(invalids) : invalids,\n            files, dupFiles, isSingleModel);\n        }, options && options.debounce ? options.debounce.change || options.debounce : 0);\n      }\n\n      var resizingFiles = validateAfterResize ? allNewFiles : valids;\n      resizeFile(resizingFiles, attr, scope, ngModel).then(function () {\n        if (validateAfterResize) {\n          upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n            .then(function (validationResult) {\n              valids = validationResult.validsFiles;\n              invalids = validationResult.invalidsFiles;\n              updateModel();\n            });\n        } else {\n          updateModel();\n        }\n      }, function () {\n        for (var i = 0; i < resizingFiles.length; i++) {\n          var f = resizingFiles[i];\n          if (f.$error === 'resize') {\n            var index = valids.indexOf(f);\n            if (index > -1) {\n              valids.splice(index, 1);\n              invalids.push(f);\n            }\n            updateModel();\n          }\n        }\n      });\n    }\n\n    prevValidFiles = attr.$$ngfPrevValidFiles || [];\n    prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];\n    if (ngModel && ngModel.$modelValue) {\n      prevValidFiles = toArray(ngModel.$modelValue);\n    }\n\n    var keep = upload.attrGetter('ngfKeep', attr, scope);\n    allNewFiles = (files || []).slice(0);\n    if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {\n      removeDuplicates(attr, scope);\n    }\n\n    var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);\n\n    if (keep && !allNewFiles.length) return;\n\n    upload.attrGetter('ngfBeforeModelChange', attr, scope, {\n      $files: files,\n      $file: files && files.length ? files[0] : null,\n      $newFiles: allNewFiles,\n      $duplicateFiles: dupFiles,\n      $event: evt\n    });\n\n    var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);\n\n    var options = upload.attrGetter('ngfModelOptions', attr, scope);\n    upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n      .then(function (validationResult) {\n      if (noDelay) {\n        update(allNewFiles, [], files, dupFiles, isSingleModel);\n      } else {\n        if ((!options || !options.allowInvalid) && !validateAfterResize) {\n          valids = validationResult.validFiles;\n          invalids = validationResult.invalidFiles;\n        } else {\n          valids = allNewFiles;\n        }\n        if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {\n          applyExifRotations(valids, attr, scope).then(function () {\n            resizeAndUpdate();\n          });\n        } else {\n          resizeAndUpdate();\n        }\n      }\n    });\n  };\n\n  return upload;\n}]);\n\nngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {\n  var generatedElems = [];\n\n  function isDelayedClickSupported(ua) {\n    // fix for android native browser < 4.4 and safari windows\n    var m = ua.match(/Android[^\\d]*(\\d+)\\.(\\d+)/);\n    if (m && m.length > 2) {\n      var v = Upload.defaults.androidFixMinorVersion || 4;\n      return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);\n    }\n\n    // safari on windows\n    return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);\n  }\n\n  function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {\n    /** @namespace attr.ngfSelect */\n    /** @namespace attr.ngfChange */\n    /** @namespace attr.ngModel */\n    /** @namespace attr.ngfModelOptions */\n    /** @namespace attr.ngfMultiple */\n    /** @namespace attr.ngfCapture */\n    /** @namespace attr.ngfValidate */\n    /** @namespace attr.ngfKeep */\n    var attrGetter = function (name, scope) {\n      return upload.attrGetter(name, attr, scope);\n    };\n\n    function isInputTypeFile() {\n      return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';\n    }\n\n    function fileChangeAttr() {\n      return attrGetter('ngfChange') || attrGetter('ngfSelect');\n    }\n\n    function changeFn(evt) {\n      if (upload.shouldUpdateOn('change', attr, scope)) {\n        var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];\n        /* Handle duplicate call in  IE11 */\n        if (!fileList) return;\n        for (var i = 0; i < fileList.length; i++) {\n          files.push(fileList[i]);\n        }\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(),\n          files.length ? files : null, evt);\n      }\n    }\n\n    upload.registerModelChangeValidator(ngModel, attr, scope);\n\n    var unwatches = [];\n    if (attrGetter('ngfMultiple')) {\n      unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {\n        fileElem.attr('multiple', attrGetter('ngfMultiple', scope));\n      }));\n    }\n    if (attrGetter('ngfCapture')) {\n      unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {\n        fileElem.attr('capture', attrGetter('ngfCapture', scope));\n      }));\n    }\n    if (attrGetter('ngfAccept')) {\n      unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {\n        fileElem.attr('accept', attrGetter('ngfAccept', scope));\n      }));\n    }\n    unwatches.push(attr.$observe('accept', function () {\n      fileElem.attr('accept', attrGetter('accept'));\n    }));\n    function bindAttrToFileInput(fileElem, label) {\n      function updateId(val) {\n        fileElem.attr('id', 'ngf-' + val);\n        label.attr('id', 'ngf-label-' + val);\n      }\n\n      for (var i = 0; i < elem[0].attributes.length; i++) {\n        var attribute = elem[0].attributes[i];\n        if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {\n          if (attribute.name === 'id') {\n            updateId(attribute.value);\n            unwatches.push(attr.$observe('id', updateId));\n          } else {\n            fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||\n            attribute.name === 'multiple')) ? attribute.name : attribute.value);\n          }\n        }\n      }\n    }\n\n    function createFileInput() {\n      if (isInputTypeFile()) {\n        return elem;\n      }\n\n      var fileElem = angular.element('<input type=\"file\">');\n\n      var label = angular.element('<label>upload</label>');\n      label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')\n        .css('width', '0px').css('height', '0px').css('border', 'none')\n        .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');\n      bindAttrToFileInput(fileElem, label);\n\n      generatedElems.push({el: elem, ref: label});\n\n      document.body.appendChild(label.append(fileElem)[0]);\n\n      return fileElem;\n    }\n\n    function clickHandler(evt) {\n      if (elem.attr('disabled')) return false;\n      if (attrGetter('ngfSelectDisabled', scope)) return;\n\n      var r = detectSwipe(evt);\n      // prevent the click if it is a swipe\n      if (r != null) return r;\n\n      resetModel(evt);\n\n      // fix for md when the element is removed from the DOM and added back #460\n      try {\n        if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {\n          generatedElems.push({el: elem, ref: fileElem.parent()});\n          document.body.appendChild(fileElem.parent()[0]);\n          fileElem.bind('change', changeFn);\n        }\n      } catch (e) {/*ignore*/\n      }\n\n      if (isDelayedClickSupported(navigator.userAgent)) {\n        setTimeout(function () {\n          fileElem[0].click();\n        }, 0);\n      } else {\n        fileElem[0].click();\n      }\n\n      return false;\n    }\n\n\n    var initialTouchStartY = 0;\n    var initialTouchStartX = 0;\n\n    function detectSwipe(evt) {\n      var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);\n      if (touches) {\n        if (evt.type === 'touchstart') {\n          initialTouchStartX = touches[0].clientX;\n          initialTouchStartY = touches[0].clientY;\n          return true; // don't block event default\n        } else {\n          // prevent scroll from triggering event\n          if (evt.type === 'touchend') {\n            var currentX = touches[0].clientX;\n            var currentY = touches[0].clientY;\n            if ((Math.abs(currentX - initialTouchStartX) > 20) ||\n              (Math.abs(currentY - initialTouchStartY) > 20)) {\n              evt.stopPropagation();\n              evt.preventDefault();\n              return false;\n            }\n          }\n          return true;\n        }\n      }\n    }\n\n    var fileElem = elem;\n\n    function resetModel(evt) {\n      if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {\n        fileElem.val(null);\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);\n      }\n    }\n\n    if (!isInputTypeFile()) {\n      fileElem = createFileInput();\n    }\n    fileElem.bind('change', changeFn);\n\n    if (!isInputTypeFile()) {\n      elem.bind('click touchstart touchend', clickHandler);\n    } else {\n      elem.bind('click', resetModel);\n    }\n\n    function ie10SameFileSelectFix(evt) {\n      if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {\n        if (!fileElem[0].parentNode) {\n          fileElem = null;\n          return;\n        }\n        evt.preventDefault();\n        evt.stopPropagation();\n        fileElem.unbind('click');\n        var clone = fileElem.clone();\n        fileElem.replaceWith(clone);\n        fileElem = clone;\n        fileElem.attr('__ngf_ie10_Fix_', 'true');\n        fileElem.bind('change', changeFn);\n        fileElem.bind('click', ie10SameFileSelectFix);\n        fileElem[0].click();\n        return false;\n      } else {\n        fileElem.removeAttr('__ngf_ie10_Fix_');\n      }\n    }\n\n    if (navigator.appVersion.indexOf('MSIE 10') !== -1) {\n      fileElem.bind('click', ie10SameFileSelectFix);\n    }\n\n    if (ngModel) ngModel.$formatters.push(function (val) {\n      if (val == null || val.length === 0) {\n        if (fileElem.val()) {\n          fileElem.val(null);\n        }\n      }\n      return val;\n    });\n\n    scope.$on('$destroy', function () {\n      if (!isInputTypeFile()) fileElem.parent().remove();\n      angular.forEach(unwatches, function (unwatch) {\n        unwatch();\n      });\n    });\n\n    $timeout(function () {\n      for (var i = 0; i < generatedElems.length; i++) {\n        var g = generatedElems[i];\n        if (!document.body.contains(g.el[0])) {\n          generatedElems.splice(i, 1);\n          g.ref.remove();\n        }\n      }\n    });\n\n    if (window.FileAPI && window.FileAPI.ngfFixIE) {\n      window.FileAPI.ngfFixIE(elem, fileElem, changeFn);\n    }\n  }\n\n  return {\n    restrict: 'AEC',\n    require: '?ngModel',\n    link: function (scope, elem, attr, ngModel) {\n      linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);\n    }\n  };\n}]);\n\n(function () {\n\n  ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {\n    var upload = UploadBase;\n    upload.base64DataUrl = function (file) {\n      if (angular.isArray(file)) {\n        var d = $q.defer(), count = 0;\n        angular.forEach(file, function (f) {\n          upload.dataUrl(f, true)['finally'](function () {\n            count++;\n            if (count === file.length) {\n              var urls = [];\n              angular.forEach(file, function (ff) {\n                urls.push(ff.$ngfDataUrl);\n              });\n              d.resolve(urls, file);\n            }\n          });\n        });\n        return d.promise;\n      } else {\n        return upload.dataUrl(file, true);\n      }\n    };\n    upload.dataUrl = function (file, disallowObjectUrl) {\n      if (!file) return upload.emptyPromise(file, file);\n      if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {\n        return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);\n      }\n      var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;\n      if (p) return p;\n\n      var deferred = $q.defer();\n      $timeout(function () {\n        if (window.FileReader && file &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {\n          //prefer URL.createObjectURL for handling refrences to files of all sizes\n          //since it doesn´t build a large string in memory\n          var URL = window.URL || window.webkitURL;\n          if (URL && URL.createObjectURL && !disallowObjectUrl) {\n            var url;\n            try {\n              url = URL.createObjectURL(file);\n            } catch (e) {\n              $timeout(function () {\n                file.$ngfBlobUrl = '';\n                deferred.reject();\n              });\n              return;\n            }\n            $timeout(function () {\n              file.$ngfBlobUrl = url;\n              if (url) {\n                deferred.resolve(url, file);\n                upload.blobUrls = upload.blobUrls || [];\n                upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;\n                upload.blobUrls.push({url: url, size: file.size});\n                upload.blobUrlsTotalSize += file.size || 0;\n                var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;\n                var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;\n                while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {\n                  var obj = upload.blobUrls.splice(0, 1)[0];\n                  URL.revokeObjectURL(obj.url);\n                  upload.blobUrlsTotalSize -= obj.size;\n                }\n              }\n            });\n          } else {\n            var fileReader = new FileReader();\n            fileReader.onload = function (e) {\n              $timeout(function () {\n                file.$ngfDataUrl = e.target.result;\n                deferred.resolve(e.target.result, file);\n                $timeout(function () {\n                  delete file.$ngfDataUrl;\n                }, 1000);\n              });\n            };\n            fileReader.onerror = function () {\n              $timeout(function () {\n                file.$ngfDataUrl = '';\n                deferred.reject();\n              });\n            };\n            fileReader.readAsDataURL(file);\n          }\n        } else {\n          $timeout(function () {\n            file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';\n            deferred.reject();\n          });\n        }\n      });\n\n      if (disallowObjectUrl) {\n        p = file.$$ngfDataUrlPromise = deferred.promise;\n      } else {\n        p = file.$$ngfBlobUrlPromise = deferred.promise;\n      }\n      p['finally'](function () {\n        delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];\n      });\n      return p;\n    };\n    return upload;\n  }]);\n\n  function getTagType(el) {\n    if (el.tagName.toLowerCase() === 'img') return 'image';\n    if (el.tagName.toLowerCase() === 'audio') return 'audio';\n    if (el.tagName.toLowerCase() === 'video') return 'video';\n    return /./;\n  }\n\n  function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {\n    function constructDataUrl(file) {\n      var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);\n      Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {\n        $timeout(function () {\n          var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;\n          if (isBackground) {\n            elem.css('background-image', 'url(\\'' + (src || '') + '\\')');\n          } else {\n            elem.attr('src', src);\n          }\n          if (src) {\n            elem.removeClass('ng-hide');\n          } else {\n            elem.addClass('ng-hide');\n          }\n        });\n      });\n    }\n\n    $timeout(function () {\n      var unwatch = scope.$watch(attr[directiveName], function (file) {\n        var size = resizeParams;\n        if (directiveName === 'ngfThumbnail') {\n          if (!size) {\n            size = {\n              width: elem[0].naturalWidth || elem[0].clientWidth,\n              height: elem[0].naturalHeight || elem[0].clientHeight\n            };\n          }\n          if (size.width === 0 && window.getComputedStyle) {\n            var style = getComputedStyle(elem[0]);\n            if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {\n              size = {\n                width: parseInt(style.width.slice(0, -2)),\n                height: parseInt(style.height.slice(0, -2))\n              };\n            }\n          }\n        }\n\n        if (angular.isString(file)) {\n          elem.removeClass('ng-hide');\n          if (isBackground) {\n            return elem.css('background-image', 'url(\\'' + file + '\\')');\n          } else {\n            return elem.attr('src', file);\n          }\n        }\n        if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&\n          (!isBackground || file.type.indexOf('image') === 0)) {\n          if (size && Upload.isResizeSupported()) {\n            size.resizeIf = function (width, height) {\n              return Upload.attrGetter('ngfResizeIf', attr, scope,\n                {$width: width, $height: height, $file: file});\n            };\n            Upload.resize(file, size).then(\n              function (f) {\n                constructDataUrl(f);\n              }, function (e) {\n                throw e;\n              }\n            );\n          } else {\n            constructDataUrl(file);\n          }\n        } else {\n          elem.addClass('ng-hide');\n        }\n      });\n\n      scope.$on('$destroy', function () {\n        unwatch();\n      });\n    });\n  }\n\n\n  /** @namespace attr.ngfSrc */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',\n          Upload.attrGetter('ngfResize', attr, scope), false);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfBackground */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',\n          Upload.attrGetter('ngfResize', attr, scope), true);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfThumbnail */\n  /** @namespace attr.ngfAsBackground */\n  /** @namespace attr.ngfSize */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        var size = Upload.attrGetter('ngfSize', attr, scope);\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,\n          Upload.attrGetter('ngfAsBackground', attr, scope));\n      }\n    };\n  }]);\n\n  ngFileUpload.config(['$compileProvider', function ($compileProvider) {\n    if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n    if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n  }]);\n\n  ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {\n    return function (file, disallowObjectUrl, trustedUrl) {\n      if (angular.isString(file)) {\n        return $sce.trustAsResourceUrl(file);\n      }\n      var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);\n      if (file && !src) {\n        if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {\n          file.$ngfDataUrlFilterInProgress = true;\n          UploadDataUrl.dataUrl(file, disallowObjectUrl);\n        }\n        return '';\n      }\n      if (file) delete file.$ngfDataUrlFilterInProgress;\n      return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';\n    };\n  }]);\n\n})();\n\nngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {\n  var upload = UploadDataUrl;\n\n  function globStringToRegex(str) {\n    var regexp = '', excludes = [];\n    if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {\n      regexp = str.substring(1, str.length - 1);\n    } else {\n      var split = str.split(',');\n      if (split.length > 1) {\n        for (var i = 0; i < split.length; i++) {\n          var r = globStringToRegex(split[i]);\n          if (r.regexp) {\n            regexp += '(' + r.regexp + ')';\n            if (i < split.length - 1) {\n              regexp += '|';\n            }\n          } else {\n            excludes = excludes.concat(r.excludes);\n          }\n        }\n      } else {\n        if (str.indexOf('!') === 0) {\n          excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');\n        } else {\n          if (str.indexOf('.') === 0) {\n            str = '*' + str;\n          }\n          regexp = '^' + str.replace(new RegExp('[.\\\\\\\\+*?\\\\[\\\\^\\\\]$(){}=!<>|:\\\\-]', 'g'), '\\\\$&') + '$';\n          regexp = regexp.replace(/\\\\\\*/g, '.*').replace(/\\\\\\?/g, '.');\n        }\n      }\n    }\n    return {regexp: regexp, excludes: excludes};\n  }\n\n  upload.validatePattern = function (file, val) {\n    if (!val) {\n      return true;\n    }\n    var pattern = globStringToRegex(val), valid = true;\n    if (pattern.regexp && pattern.regexp.length) {\n      var regexp = new RegExp(pattern.regexp, 'i');\n      valid = (file.type != null && regexp.test(file.type)) ||\n        (file.name != null && regexp.test(file.name));\n    }\n    var len = pattern.excludes.length;\n    while (len--) {\n      var exclude = new RegExp(pattern.excludes[len], 'i');\n      valid = valid && (file.type == null || exclude.test(file.type)) &&\n        (file.name == null || exclude.test(file.name));\n    }\n    return valid;\n  };\n\n  upload.ratioToFloat = function (val) {\n    var r = val.toString(), xIndex = r.search(/[x:]/i);\n    if (xIndex > -1) {\n      r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));\n    } else {\n      r = parseFloat(r);\n    }\n    return r;\n  };\n\n  upload.registerModelChangeValidator = function (ngModel, attr, scope) {\n    if (ngModel) {\n      ngModel.$formatters.push(function (files) {\n        if (ngModel.$dirty) {\n          var filesArray = files;\n          if (files && !angular.isArray(files)) {\n            filesArray = [files];\n          }\n          upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {\n            upload.applyModelValidation(ngModel, filesArray);\n          });\n        }\n        return files;\n      });\n    }\n  };\n\n  function markModelAsDirty(ngModel, files) {\n    if (files != null && !ngModel.$dirty) {\n      if (ngModel.$setDirty) {\n        ngModel.$setDirty();\n      } else {\n        ngModel.$dirty = true;\n      }\n    }\n  }\n\n  upload.applyModelValidation = function (ngModel, files) {\n    markModelAsDirty(ngModel, files);\n    angular.forEach(ngModel.$ngfValidations, function (validation) {\n      ngModel.$setValidity(validation.name, validation.valid);\n    });\n  };\n\n  upload.getValidationAttr = function (attr, scope, name, validationName, file) {\n    var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);\n    var val = upload.attrGetter(dName, attr, scope, {$file: file});\n    if (val == null) {\n      val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});\n      if (val) {\n        var split = (validationName || name).split('.');\n        val = val[split[0]];\n        if (split.length > 1) {\n          val = val && val[split[1]];\n        }\n      }\n    }\n    return val;\n  };\n\n  upload.validate = function (files, prevLength, ngModel, attr, scope) {\n    ngModel = ngModel || {};\n    ngModel.$ngfValidations = ngModel.$ngfValidations || [];\n\n    angular.forEach(ngModel.$ngfValidations, function (v) {\n      v.valid = true;\n    });\n\n    var attrGetter = function (name, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');\n    var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);\n\n    if (files == null || files.length === 0) {\n      return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});\n    }\n\n    files = files.length === undefined ? [files] : files.slice(0);\n    var invalidFiles = [];\n\n    function validateSync(name, validationName, fn) {\n      if (files) {\n        var i = files.length, valid = null;\n        while (i--) {\n          var file = files[i];\n          if (file) {\n            var val = upload.getValidationAttr(attr, scope, name, validationName, file);\n            if (val != null) {\n              if (!fn(file, val, i)) {\n                if (ignoredErrors.indexOf(name) === -1) {\n                  file.$error = name;\n                  (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n                  file.$errorParam = val;\n                  if (invalidFiles.indexOf(file) === -1) {\n                    invalidFiles.push(file);\n                  }\n                  if (!runAllValidation) {\n                    files.splice(i, 1);\n                  }\n                  valid = false;\n                } else {\n                  files.splice(i, 1);\n                }\n              }\n            }\n          }\n        }\n        if (valid !== null) {\n          ngModel.$ngfValidations.push({name: name, valid: valid});\n        }\n      }\n    }\n\n    validateSync('pattern', null, upload.validatePattern);\n    validateSync('minSize', 'size.min', function (file, val) {\n      return file.size + 0.1 >= upload.translateScalars(val);\n    });\n    validateSync('maxSize', 'size.max', function (file, val) {\n      return file.size - 0.1 <= upload.translateScalars(val);\n    });\n    var totalSize = 0;\n    validateSync('maxTotalSize', null, function (file, val) {\n      totalSize += file.size;\n      if (totalSize > upload.translateScalars(val)) {\n        files.splice(0, files.length);\n        return false;\n      }\n      return true;\n    });\n\n    validateSync('validateFn', null, function (file, r) {\n      return r === true || r === null || r === '';\n    });\n\n    if (!files.length) {\n      return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});\n    }\n\n    function validateAsync(name, validationName, type, asyncFn, fn) {\n      function resolveResult(defer, file, val) {\n        function resolveInternal(fn) {\n          if (fn()) {\n            if (ignoredErrors.indexOf(name) === -1) {\n              file.$error = name;\n              (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n              file.$errorParam = val;\n              if (invalidFiles.indexOf(file) === -1) {\n                invalidFiles.push(file);\n              }\n              if (!runAllValidation) {\n                var i = files.indexOf(file);\n                if (i > -1) files.splice(i, 1);\n              }\n              defer.resolve(false);\n            } else {\n              var j = files.indexOf(file);\n              if (j > -1) files.splice(j, 1);\n              defer.resolve(true);\n            }\n          } else {\n            defer.resolve(true);\n          }\n        }\n\n        if (val != null) {\n          asyncFn(file, val).then(function (d) {\n            resolveInternal(function () {\n              return !fn(d, val);\n            });\n          }, function () {\n            resolveInternal(function () {\n              return attrGetter('ngfValidateForce', {$file: file});\n            });\n          });\n        } else {\n          defer.resolve(true);\n        }\n      }\n\n      var promises = [upload.emptyPromise(true)];\n      if (files) {\n        files = files.length === undefined ? [files] : files;\n        angular.forEach(files, function (file) {\n          var defer = $q.defer();\n          promises.push(defer.promise);\n          if (type && (file.type == null || file.type.search(type) !== 0)) {\n            defer.resolve(true);\n            return;\n          }\n          if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {\n            upload.imageDimensions(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {\n            upload.mediaDuration(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDuration', {$file: file, $duration: d}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else {\n            resolveResult(defer, file,\n              upload.getValidationAttr(attr, scope, name, validationName, file));\n          }\n        });\n      }\n      var deffer = $q.defer();\n      $q.all(promises).then(function (values) {\n        var isValid = true;\n        for (var i = 0; i < values.length; i++) {\n          if (!values[i]) {\n            isValid = false;\n            break;\n          }\n        }\n        ngModel.$ngfValidations.push({name: name, valid: isValid});\n        deffer.resolve(isValid);\n      });\n      return deffer.promise;\n    }\n\n    var deffer = $q.defer();\n    var promises = [];\n\n    promises.push(validateAsync('maxHeight', 'height.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height <= val;\n      }));\n    promises.push(validateAsync('minHeight', 'height.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height >= val;\n      }));\n    promises.push(validateAsync('maxWidth', 'width.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width <= val;\n      }));\n    promises.push(validateAsync('minWidth', 'width.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width >= val;\n      }));\n    promises.push(validateAsync('dimensions', null, /image/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n    promises.push(validateAsync('ratio', null, /image/,\n      this.imageDimensions, function (d, val) {\n        var split = val.toString().split(','), valid = false;\n        for (var i = 0; i < split.length; i++) {\n          if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {\n            valid = true;\n          }\n        }\n        return valid;\n      }));\n    promises.push(validateAsync('maxRatio', 'ratio.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;\n      }));\n    promises.push(validateAsync('minRatio', 'ratio.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;\n      }));\n    promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d <= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d >= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('duration', null, /audio|video/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n\n    promises.push(validateAsync('validateAsyncFn', null, null,\n      function (file, val) {\n        return val;\n      }, function (r) {\n        return r === true || r === null || r === '';\n      }));\n\n    $q.all(promises).then(function () {\n\n      if (runAllValidation) {\n        for (var i = 0; i < files.length; i++) {\n          var file = files[i];\n          if (file.$error) {\n            files.splice(i--, 1);\n          }\n        }\n      }\n\n      runAllValidation = false;\n      validateSync('maxFiles', null, function (file, val, i) {\n        return prevLength + i < val;\n      });\n\n      deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});\n    });\n    return deffer.promise;\n  };\n\n  upload.imageDimensions = function (file) {\n    if (file.$ngfWidth && file.$ngfHeight) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});\n      });\n      return d.promise;\n    }\n    if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('image') !== 0) {\n        deferred.reject('not image');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var img = angular.element('<img>').attr('src', dataUrl)\n          .css('visibility', 'hidden').css('position', 'fixed')\n          .css('max-width', 'none !important').css('max-height', 'none !important');\n\n        function success() {\n          var width = img[0].naturalWidth || img[0].clientWidth;\n          var height = img[0].naturalHeight || img[0].clientHeight;\n          img.remove();\n          file.$ngfWidth = width;\n          file.$ngfHeight = height;\n          deferred.resolve({width: width, height: height});\n        }\n\n        function error() {\n          img.remove();\n          deferred.reject('load error');\n        }\n\n        img.on('load', success);\n        img.on('error', error);\n\n        var secondsCounter = 0;\n        function checkLoadErrorInCaseOfNoCallback() {\n          $timeout(function () {\n            if (img[0].parentNode) {\n              if (img[0].clientWidth) {\n                success();\n              } else if (secondsCounter++ > 10) {\n                error();\n              } else {\n                checkLoadErrorInCaseOfNoCallback();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadErrorInCaseOfNoCallback();\n\n        angular.element(document.getElementsByTagName('body')[0]).append(img);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDimensionPromise = deferred.promise;\n    file.$ngfDimensionPromise['finally'](function () {\n      delete file.$ngfDimensionPromise;\n    });\n    return file.$ngfDimensionPromise;\n  };\n\n  upload.mediaDuration = function (file) {\n    if (file.$ngfDuration) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve(file.$ngfDuration);\n      });\n      return d.promise;\n    }\n    if (file.$ngfDurationPromise) return file.$ngfDurationPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {\n        deferred.reject('not media');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')\n          .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');\n\n        function success() {\n          var duration = el[0].duration;\n          file.$ngfDuration = duration;\n          el.remove();\n          deferred.resolve(duration);\n        }\n\n        function error() {\n          el.remove();\n          deferred.reject('load error');\n        }\n\n        el.on('loadedmetadata', success);\n        el.on('error', error);\n        var count = 0;\n\n        function checkLoadError() {\n          $timeout(function () {\n            if (el[0].parentNode) {\n              if (el[0].duration) {\n                success();\n              } else if (count > 10) {\n                error();\n              } else {\n                checkLoadError();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadError();\n\n        angular.element(document.body).append(el);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDurationPromise = deferred.promise;\n    file.$ngfDurationPromise['finally'](function () {\n      delete file.$ngfDurationPromise;\n    });\n    return file.$ngfDurationPromise;\n  };\n  return upload;\n}\n]);\n\nngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {\n  var upload = UploadValidate;\n\n  /**\n   * Conserve aspect ratio of the original region. Useful when shrinking/enlarging\n   * images to fit into a certain area.\n   * Source:  http://stackoverflow.com/a/14731922\n   *\n   * @param {Number} srcWidth Source area width\n   * @param {Number} srcHeight Source area height\n   * @param {Number} maxWidth Nestable area maximum available width\n   * @param {Number} maxHeight Nestable area maximum available height\n   * @return {Object} { width, height }\n   */\n  var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {\n    var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :\n      Math.min(maxWidth / srcWidth, maxHeight / srcHeight);\n    return {\n      width: srcWidth * ratio, height: srcHeight * ratio,\n      marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight\n    };\n  };\n\n  // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89\n  var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {\n    var deferred = $q.defer();\n    var canvasElement = document.createElement('canvas');\n    var imageElement = document.createElement('img');\n    imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');\n    document.body.appendChild(imageElement);\n\n    imageElement.onload = function () {\n      var imgWidth = imageElement.width, imgHeight = imageElement.height;\n      imageElement.parentNode.removeChild(imageElement);\n      if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {\n        deferred.reject('resizeIf');\n        return;\n      }\n      try {\n        if (ratio) {\n          var ratioFloat = upload.ratioToFloat(ratio);\n          var imgRatio = imgWidth / imgHeight;\n          if (imgRatio < ratioFloat) {\n            width = imgWidth;\n            height = width / ratioFloat;\n          } else {\n            height = imgHeight;\n            width = height * ratioFloat;\n          }\n        }\n        if (!width) {\n          width = imgWidth;\n        }\n        if (!height) {\n          height = imgHeight;\n        }\n        var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);\n        canvasElement.width = Math.min(dimensions.width, width);\n        canvasElement.height = Math.min(dimensions.height, height);\n        var context = canvasElement.getContext('2d');\n        context.drawImage(imageElement,\n          Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),\n          dimensions.width, dimensions.height);\n        deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));\n      } catch (e) {\n        deferred.reject(e);\n      }\n    };\n    imageElement.onerror = function () {\n      imageElement.parentNode.removeChild(imageElement);\n      deferred.reject();\n    };\n    imageElement.src = imagen;\n    return deferred.promise;\n  };\n\n  upload.dataUrltoBlob = function (dataurl, name, origSize) {\n    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],\n      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n);\n    }\n    var blob = new window.Blob([u8arr], {type: mime});\n    blob.name = name;\n    blob.$ngfOrigSize = origSize;\n    return blob;\n  };\n\n  upload.isResizeSupported = function () {\n    var elem = document.createElement('canvas');\n    return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;\n  };\n\n  if (upload.isResizeSupported()) {\n    // add name getter to the blob constructor prototype\n    Object.defineProperty(window.Blob.prototype, 'name', {\n      get: function () {\n        return this.$ngfName;\n      },\n      set: function (v) {\n        this.$ngfName = v;\n      },\n      configurable: true\n    });\n  }\n\n  upload.resize = function (file, options) {\n    if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);\n\n    var deferred = $q.defer();\n    upload.dataUrl(file, true).then(function (url) {\n      resize(url, options.width, options.height, options.quality, options.type || file.type,\n        options.ratio, options.centerCrop, options.resizeIf)\n        .then(function (dataUrl) {\n          if (file.type === 'image/jpeg' && options.restoreExif !== false) {\n            try {\n              dataUrl = upload.restoreExif(url, dataUrl);\n            } catch (e) {\n              setTimeout(function () {throw e;}, 1);\n            }\n          }\n          try {\n            var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);\n            deferred.resolve(blob);\n          } catch (e) {\n            deferred.reject(e);\n          }\n        }, function (r) {\n          if (r === 'resizeIf') {\n            deferred.resolve(file);\n          }\n          deferred.reject(r);\n        });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  return upload;\n}]);\n\n(function () {\n  ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',\n    function ($parse, $timeout, $window, Upload, $http, $q) {\n      return {\n        restrict: 'AEC',\n        require: '?ngModel',\n        link: function (scope, elem, attr, ngModel) {\n          linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);\n        }\n      };\n    }]);\n\n  ngFileUpload.directive('ngfNoFileDrop', function () {\n    return function (scope, elem) {\n      if (dropAvailable()) elem.css('display', 'none');\n    };\n  });\n\n  ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {\n    return function (scope, elem, attr) {\n      if (dropAvailable()) {\n        var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));\n        $timeout(function () {\n          model(scope);\n          if (model.assign) {\n            model.assign(scope, true);\n          }\n        });\n      }\n    };\n  }]);\n\n  function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {\n    var available = dropAvailable();\n\n    var attrGetter = function (name, scope, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    if (attrGetter('dropAvailable')) {\n      $timeout(function () {\n        if (scope[attrGetter('dropAvailable')]) {\n          scope[attrGetter('dropAvailable')].value = available;\n        } else {\n          scope[attrGetter('dropAvailable')] = available;\n        }\n      });\n    }\n    if (!available) {\n      if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {\n        elem.css('display', 'none');\n      }\n      return;\n    }\n\n    function isDisabled() {\n      return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);\n    }\n\n    if (attrGetter('ngfSelect') == null) {\n      upload.registerModelChangeValidator(ngModel, attr, scope);\n    }\n\n    var leaveTimeout = null;\n    var stopPropagation = $parse(attrGetter('ngfStopPropagation'));\n    var dragOverDelay = 1;\n    var actualDragOverClass;\n\n    elem[0].addEventListener('dragover', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      // handling dragover events from the Chrome download bar\n      if (navigator.userAgent.indexOf('Chrome') > -1) {\n        var b = evt.dataTransfer.effectAllowed;\n        evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';\n      }\n      $timeout.cancel(leaveTimeout);\n      if (!actualDragOverClass) {\n        actualDragOverClass = 'C';\n        calculateDragOverClass(scope, attr, evt, function (clazz) {\n          actualDragOverClass = clazz;\n          elem.addClass(actualDragOverClass);\n          attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});\n        });\n      }\n    }, false);\n    elem[0].addEventListener('dragenter', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n    }, false);\n    elem[0].addEventListener('dragleave', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      leaveTimeout = $timeout(function () {\n        if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n        actualDragOverClass = null;\n        attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});\n      }, dragOverDelay || 100);\n    }, false);\n    elem[0].addEventListener('drop', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n      actualDragOverClass = null;\n      extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');\n    }, false);\n    elem[0].addEventListener('paste', function (evt) {\n      if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n        attrGetter('ngfEnableFirefoxPaste', scope)) {\n        evt.preventDefault();\n      }\n      if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;\n      extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');\n    }, false);\n\n    if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n      attrGetter('ngfEnableFirefoxPaste', scope)) {\n      elem.attr('contenteditable', true);\n      elem.on('keypress', function (e) {\n        if (!e.metaKey && !e.ctrlKey) {\n          e.preventDefault();\n        }\n      });\n    }\n\n    function extractFilesAndUpdateModel(source, evt, updateOnType) {\n      if (!source) return;\n      // html needs to be calculated on the same process otherwise the data will be wiped\n      // after promise resolve or setTimeout.\n      var html;\n      try {\n        html = source && source.getData && source.getData('text/html');\n      } catch (e) {/* Fix IE11 that throw error calling getData */\n      }\n      extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,\n        attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {\n        if (files.length) {\n          updateModel(files, evt);\n        } else {\n          extractFilesFromHtml(updateOnType, html).then(function (files) {\n            updateModel(files, evt);\n          });\n        }\n      });\n    }\n\n    function updateModel(files, evt) {\n      upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);\n    }\n\n    function extractFilesFromHtml(updateOn, html) {\n      if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);\n      var urls = [];\n      html.replace(/<(img src|img [^>]* src) *=\\\"([^\\\"]*)\\\"/gi, function (m, n, src) {\n        urls.push(src);\n      });\n      var promises = [], files = [];\n      if (urls.length) {\n        angular.forEach(urls, function (url) {\n          promises.push(upload.urlToBlob(url).then(function (blob) {\n            files.push(blob);\n          }));\n        });\n        var defer = $q.defer();\n        $q.all(promises).then(function () {\n          defer.resolve(files);\n        }, function (e) {\n          defer.reject(e);\n        });\n        return defer.promise;\n      }\n      return upload.emptyPromise();\n    }\n\n    function calculateDragOverClass(scope, attr, evt, callback) {\n      var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';\n      if (angular.isString(obj)) {\n        dClass = obj;\n      } else if (obj) {\n        if (obj.delay) dragOverDelay = obj.delay;\n        if (obj.accept || obj.reject) {\n          var items = evt.dataTransfer.items;\n          if (items == null || !items.length) {\n            dClass = obj.accept;\n          } else {\n            var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});\n            var len = items.length;\n            while (len--) {\n              if (!upload.validatePattern(items[len], pattern)) {\n                dClass = obj.reject;\n                break;\n              } else {\n                dClass = obj.accept;\n              }\n            }\n          }\n        }\n      }\n      callback(dClass);\n    }\n\n    function extractFiles(items, fileList, allowDir, multiple) {\n      var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');\n      if (maxFiles == null) {\n        maxFiles = Number.MAX_VALUE;\n      }\n      var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');\n      if (maxTotalSize == null) {\n        maxTotalSize = Number.MAX_VALUE;\n      }\n      var includeDir = attrGetter('ngfIncludeDir', scope);\n      var files = [], totalSize = 0;\n\n      function traverseFileTree(entry, path) {\n        var defer = $q.defer();\n        if (entry != null) {\n          if (entry.isDirectory) {\n            var promises = [upload.emptyPromise()];\n            if (includeDir) {\n              var file = {type: 'directory'};\n              file.name = file.path = (path || '') + entry.name;\n              files.push(file);\n            }\n            var dirReader = entry.createReader();\n            var entries = [];\n            var readEntries = function () {\n              dirReader.readEntries(function (results) {\n                try {\n                  if (!results.length) {\n                    angular.forEach(entries.slice(0), function (e) {\n                      if (files.length <= maxFiles && totalSize <= maxTotalSize) {\n                        promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));\n                      }\n                    });\n                    $q.all(promises).then(function () {\n                      defer.resolve();\n                    }, function (e) {\n                      defer.reject(e);\n                    });\n                  } else {\n                    entries = entries.concat(Array.prototype.slice.call(results || [], 0));\n                    readEntries();\n                  }\n                } catch (e) {\n                  defer.reject(e);\n                }\n              }, function (e) {\n                defer.reject(e);\n              });\n            };\n            readEntries();\n          } else {\n            entry.file(function (file) {\n              try {\n                file.path = (path ? path : '') + file.name;\n                if (includeDir) {\n                  file = upload.rename(file, file.path);\n                }\n                files.push(file);\n                totalSize += file.size;\n                defer.resolve();\n              } catch (e) {\n                defer.reject(e);\n              }\n            }, function (e) {\n              defer.reject(e);\n            });\n          }\n        }\n        return defer.promise;\n      }\n\n      var promises = [upload.emptyPromise()];\n\n      if (items && items.length > 0 && $window.location.protocol !== 'file:') {\n        for (var i = 0; i < items.length; i++) {\n          if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {\n            var entry = items[i].webkitGetAsEntry();\n            if (entry.isDirectory && !allowDir) {\n              continue;\n            }\n            if (entry != null) {\n              promises.push(traverseFileTree(entry));\n            }\n          } else {\n            var f = items[i].getAsFile();\n            if (f != null) {\n              files.push(f);\n              totalSize += f.size;\n            }\n          }\n          if (files.length > maxFiles || totalSize > maxTotalSize ||\n            (!multiple && files.length > 0)) break;\n        }\n      } else {\n        if (fileList != null) {\n          for (var j = 0; j < fileList.length; j++) {\n            var file = fileList.item(j);\n            if (file.type || file.size > 0) {\n              files.push(file);\n              totalSize += file.size;\n            }\n            if (files.length > maxFiles || totalSize > maxTotalSize ||\n              (!multiple && files.length > 0)) break;\n          }\n        }\n      }\n\n      var defer = $q.defer();\n      $q.all(promises).then(function () {\n        if (!multiple && !includeDir && files.length) {\n          var i = 0;\n          while (files[i] && files[i].type === 'directory') i++;\n          defer.resolve([files[i]]);\n        } else {\n          defer.resolve(files);\n        }\n      }, function (e) {\n        defer.reject(e);\n      });\n\n      return defer.promise;\n    }\n  }\n\n  function dropAvailable() {\n    var div = document.createElement('div');\n    return ('draggable' in div) && ('ondrop' in div) && !/Edge\\/12./i.test(navigator.userAgent);\n  }\n\n})();\n\n// customized version of https://github.com/exif-js/exif-js\nngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {\n  var upload = UploadResize;\n\n  upload.isExifSupported = function () {\n    return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();\n  };\n\n  function applyTransform(ctx, orientation, width, height) {\n    switch (orientation) {\n      case 2:\n        return ctx.transform(-1, 0, 0, 1, width, 0);\n      case 3:\n        return ctx.transform(-1, 0, 0, -1, width, height);\n      case 4:\n        return ctx.transform(1, 0, 0, -1, 0, height);\n      case 5:\n        return ctx.transform(0, 1, 1, 0, 0, 0);\n      case 6:\n        return ctx.transform(0, 1, -1, 0, height, 0);\n      case 7:\n        return ctx.transform(0, -1, -1, 0, height, width);\n      case 8:\n        return ctx.transform(0, -1, 1, 0, 0, width);\n    }\n  }\n\n  upload.readOrientation = function (file) {\n    var defer = $q.defer();\n    var reader = new FileReader();\n    var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;\n    reader.readAsArrayBuffer(slicedFile);\n    reader.onerror = function (e) {\n      return defer.reject(e);\n    };\n    reader.onload = function (e) {\n      var result = {orientation: 1};\n      var view = new DataView(this.result);\n      if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);\n\n      var length = view.byteLength,\n        offset = 2;\n      while (offset < length) {\n        var marker = view.getUint16(offset, false);\n        offset += 2;\n        if (marker === 0xFFE1) {\n          if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);\n\n          var little = view.getUint16(offset += 6, false) === 0x4949;\n          offset += view.getUint32(offset + 4, little);\n          var tags = view.getUint16(offset, little);\n          offset += 2;\n          for (var i = 0; i < tags; i++)\n            if (view.getUint16(offset + (i * 12), little) === 0x0112) {\n              var orientation = view.getUint16(offset + (i * 12) + 8, little);\n              if (orientation >= 2 && orientation <= 8) {\n                view.setUint16(offset + (i * 12) + 8, 1, little);\n                result.fixedArrayBuffer = e.target.result;\n              }\n              result.orientation = orientation;\n              return defer.resolve(result);\n            }\n        } else if ((marker & 0xFF00) !== 0xFF00) break;\n        else offset += view.getUint16(offset, false);\n      }\n      return defer.resolve(result);\n    };\n    return defer.promise;\n  };\n\n  function arrayBufferToBase64(buffer) {\n    var binary = '';\n    var bytes = new Uint8Array(buffer);\n    var len = bytes.byteLength;\n    for (var i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return window.btoa(binary);\n  }\n\n  upload.applyExifRotation = function (file) {\n    if (file.type.indexOf('image/jpeg') !== 0) {\n      return upload.emptyPromise(file);\n    }\n\n    var deferred = $q.defer();\n    upload.readOrientation(file).then(function (result) {\n      if (result.orientation < 2 || result.orientation > 8) {\n        return deferred.resolve(file);\n      }\n      upload.dataUrl(file, true).then(function (url) {\n        var canvas = document.createElement('canvas');\n        var img = document.createElement('img');\n\n        img.onload = function () {\n          try {\n            canvas.width = result.orientation > 4 ? img.height : img.width;\n            canvas.height = result.orientation > 4 ? img.width : img.height;\n            var ctx = canvas.getContext('2d');\n            applyTransform(ctx, result.orientation, img.width, img.height);\n            ctx.drawImage(img, 0, 0);\n            var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);\n            dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);\n            var blob = upload.dataUrltoBlob(dataUrl, file.name);\n            deferred.resolve(blob);\n          } catch (e) {\n            return deferred.reject(e);\n          }\n        };\n        img.onerror = function () {\n          deferred.reject();\n        };\n        img.src = url;\n      }, function (e) {\n        deferred.reject(e);\n      });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  upload.restoreExif = function (orig, resized) {\n    var ExifRestorer = {};\n\n    ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +\n      'QRSTUVWXYZabcdef' +\n      'ghijklmnopqrstuv' +\n      'wxyz0123456789+/' +\n      '=';\n\n    ExifRestorer.encode64 = function (input) {\n      var output = '',\n        chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0;\n\n      do {\n        chr1 = input[i++];\n        chr2 = input[i++];\n        chr3 = input[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n          enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n          enc4 = 64;\n        }\n\n        output = output +\n          this.KEY_STR.charAt(enc1) +\n          this.KEY_STR.charAt(enc2) +\n          this.KEY_STR.charAt(enc3) +\n          this.KEY_STR.charAt(enc4);\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n      } while (i < input.length);\n\n      return output;\n    };\n\n    ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {\n      if (origFileBase64.match('data:image/jpeg;base64,')) {\n        origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');\n      }\n\n      var rawImage = this.decode64(origFileBase64);\n      var segments = this.slice2Segments(rawImage);\n\n      var image = this.exifManipulation(resizedFileBase64, segments);\n\n      return 'data:image/jpeg;base64,' + this.encode64(image);\n    };\n\n\n    ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {\n      var exifArray = this.getExifArray(segments),\n        newImageArray = this.insertExif(resizedFileBase64, exifArray);\n      return new Uint8Array(newImageArray);\n    };\n\n\n    ExifRestorer.getExifArray = function (segments) {\n      var seg;\n      for (var x = 0; x < segments.length; x++) {\n        seg = segments[x];\n        if (seg[0] === 255 & seg[1] === 225) //(ff e1)\n        {\n          return seg;\n        }\n      }\n      return [];\n    };\n\n\n    ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {\n      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),\n        buf = this.decode64(imageData),\n        separatePoint = buf.indexOf(255, 3),\n        mae = buf.slice(0, separatePoint),\n        ato = buf.slice(separatePoint),\n        array = mae;\n\n      array = array.concat(exifArray);\n      array = array.concat(ato);\n      return array;\n    };\n\n\n    ExifRestorer.slice2Segments = function (rawImageArray) {\n      var head = 0,\n        segments = [];\n\n      while (1) {\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {\n          break;\n        }\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {\n          head += 2;\n        }\n        else {\n          var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n            endPoint = head + length + 2,\n            seg = rawImageArray.slice(head, endPoint);\n          segments.push(seg);\n          head = endPoint;\n        }\n        if (head > rawImageArray.length) {\n          break;\n        }\n      }\n\n      return segments;\n    };\n\n\n    ExifRestorer.decode64 = function (input) {\n      var chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0,\n        buf = [];\n\n      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n      var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n      if (base64test.exec(input)) {\n        console.log('There were invalid base64 characters in the input text.\\n' +\n          'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and \"=\"\\n' +\n          'Expect errors in decoding.');\n      }\n      input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, '');\n\n      do {\n        enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        buf.push(chr1);\n\n        if (enc3 !== 64) {\n          buf.push(chr2);\n        }\n        if (enc4 !== 64) {\n          buf.push(chr3);\n        }\n\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n\n      } while (i < input.length);\n\n      return buf;\n    };\n\n    return ExifRestorer.restore(orig, resized);  //<= EXIF\n  };\n\n  return upload;\n}]);\n\n"
  },
  {
    "path": "demo/src/main/webapp/js/ng-img-crop.css",
    "content": "/* line 1, ../../source/scss/ng-img-crop.scss */\nimg-crop {\n  width: 100%;\n  height: 100%;\n  display: block;\n  position: relative;\n  overflow: hidden;\n}\n/* line 7, ../../source/scss/ng-img-crop.scss */\nimg-crop canvas {\n  display: block;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  outline: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n  /* mobile webkit */\n}\n"
  },
  {
    "path": "demo/src/main/webapp/js/ng-img-crop.js",
    "content": "/*!\n * ngImgCrop v0.3.2\n * https://github.com/alexk111/ngImgCrop\n *\n * Copyright (c) 2014 Alex Kaul\n * License: MIT\n *\n * Generated at Wednesday, December 3rd, 2014, 3:54:12 PM\n */\n(function() {\n'use strict';\n\nvar crop = angular.module('ngImgCrop', []);\n\ncrop.factory('cropAreaCircle', ['cropArea', function(CropArea) {\n  var CropAreaCircle = function() {\n    CropArea.apply(this, arguments);\n\n    this._boxResizeBaseSize = 20;\n    this._boxResizeNormalRatio = 0.9;\n    this._boxResizeHoverRatio = 1.2;\n    this._iconMoveNormalRatio = 0.9;\n    this._iconMoveHoverRatio = 1.2;\n\n    this._boxResizeNormalSize = this._boxResizeBaseSize*this._boxResizeNormalRatio;\n    this._boxResizeHoverSize = this._boxResizeBaseSize*this._boxResizeHoverRatio;\n\n    this._posDragStartX=0;\n    this._posDragStartY=0;\n    this._posResizeStartX=0;\n    this._posResizeStartY=0;\n    this._posResizeStartSize=0;\n\n    this._boxResizeIsHover = false;\n    this._areaIsHover = false;\n    this._boxResizeIsDragging = false;\n    this._areaIsDragging = false;\n  };\n\n  CropAreaCircle.prototype = new CropArea();\n\n  CropAreaCircle.prototype._calcCirclePerimeterCoords=function(angleDegrees) {\n    var hSize=this._size/2;\n    var angleRadians=angleDegrees * (Math.PI / 180),\n        circlePerimeterX=this._x + hSize * Math.cos(angleRadians),\n        circlePerimeterY=this._y + hSize * Math.sin(angleRadians);\n    return [circlePerimeterX, circlePerimeterY];\n  };\n\n  CropAreaCircle.prototype._calcResizeIconCenterCoords=function() {\n    return this._calcCirclePerimeterCoords(-45);\n  };\n\n  CropAreaCircle.prototype._isCoordWithinArea=function(coord) {\n    return Math.sqrt((coord[0]-this._x)*(coord[0]-this._x) + (coord[1]-this._y)*(coord[1]-this._y)) < this._size/2;\n  };\n  CropAreaCircle.prototype._isCoordWithinBoxResize=function(coord) {\n    var resizeIconCenterCoords=this._calcResizeIconCenterCoords();\n    var hSize=this._boxResizeHoverSize/2;\n    return(coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize &&\n           coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize);\n  };\n\n  CropAreaCircle.prototype._drawArea=function(ctx,centerCoords,size){\n    ctx.arc(centerCoords[0],centerCoords[1],size/2,0,2*Math.PI);\n  };\n\n  CropAreaCircle.prototype.draw=function() {\n    CropArea.prototype.draw.apply(this, arguments);\n\n    // draw move icon\n    this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);\n\n    // draw resize cubes\n    this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover?this._boxResizeHoverRatio:this._boxResizeNormalRatio);\n  };\n\n  CropAreaCircle.prototype.processMouseMove=function(mouseCurX, mouseCurY) {\n    var cursor='default';\n    var res=false;\n\n    this._boxResizeIsHover = false;\n    this._areaIsHover = false;\n\n    if (this._areaIsDragging) {\n      this._x = mouseCurX - this._posDragStartX;\n      this._y = mouseCurY - this._posDragStartY;\n      this._areaIsHover = true;\n      cursor='move';\n      res=true;\n      this._events.trigger('area-move');\n    } else if (this._boxResizeIsDragging) {\n        cursor = 'nesw-resize';\n        var iFR, iFX, iFY;\n        iFX = mouseCurX - this._posResizeStartX;\n        iFY = this._posResizeStartY - mouseCurY;\n        if(iFX>iFY) {\n          iFR = this._posResizeStartSize + iFY*2;\n        } else {\n          iFR = this._posResizeStartSize + iFX*2;\n        }\n\n        this._size = Math.max(this._minSize, iFR);\n        this._boxResizeIsHover = true;\n        res=true;\n        this._events.trigger('area-resize');\n    } else if (this._isCoordWithinBoxResize([mouseCurX,mouseCurY])) {\n        cursor = 'nesw-resize';\n        this._areaIsHover = false;\n        this._boxResizeIsHover = true;\n        res=true;\n    } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {\n        cursor = 'move';\n        this._areaIsHover = true;\n        res=true;\n    }\n\n    this._dontDragOutside();\n    angular.element(this._ctx.canvas).css({'cursor': cursor});\n\n    return res;\n  };\n\n  CropAreaCircle.prototype.processMouseDown=function(mouseDownX, mouseDownY) {\n    if (this._isCoordWithinBoxResize([mouseDownX,mouseDownY])) {\n      this._areaIsDragging = false;\n      this._areaIsHover = false;\n      this._boxResizeIsDragging = true;\n      this._boxResizeIsHover = true;\n      this._posResizeStartX=mouseDownX;\n      this._posResizeStartY=mouseDownY;\n      this._posResizeStartSize = this._size;\n      this._events.trigger('area-resize-start');\n    } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {\n      this._areaIsDragging = true;\n      this._areaIsHover = true;\n      this._boxResizeIsDragging = false;\n      this._boxResizeIsHover = false;\n      this._posDragStartX = mouseDownX - this._x;\n      this._posDragStartY = mouseDownY - this._y;\n      this._events.trigger('area-move-start');\n    }\n  };\n\n  CropAreaCircle.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {\n    if(this._areaIsDragging) {\n      this._areaIsDragging = false;\n      this._events.trigger('area-move-end');\n    }\n    if(this._boxResizeIsDragging) {\n      this._boxResizeIsDragging = false;\n      this._events.trigger('area-resize-end');\n    }\n    this._areaIsHover = false;\n    this._boxResizeIsHover = false;\n\n    this._posDragStartX = 0;\n    this._posDragStartY = 0;\n  };\n\n  return CropAreaCircle;\n}]);\n\n\n\ncrop.factory('cropAreaSquare', ['cropArea', function(CropArea) {\n  var CropAreaSquare = function() {\n    CropArea.apply(this, arguments);\n\n    this._resizeCtrlBaseRadius = 10;\n    this._resizeCtrlNormalRatio = 0.75;\n    this._resizeCtrlHoverRatio = 1;\n    this._iconMoveNormalRatio = 0.9;\n    this._iconMoveHoverRatio = 1.2;\n\n    this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius*this._resizeCtrlNormalRatio;\n    this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius*this._resizeCtrlHoverRatio;\n\n    this._posDragStartX=0;\n    this._posDragStartY=0;\n    this._posResizeStartX=0;\n    this._posResizeStartY=0;\n    this._posResizeStartSize=0;\n\n    this._resizeCtrlIsHover = -1;\n    this._areaIsHover = false;\n    this._resizeCtrlIsDragging = -1;\n    this._areaIsDragging = false;\n  };\n\n  CropAreaSquare.prototype = new CropArea();\n\n  CropAreaSquare.prototype._calcSquareCorners=function() {\n    var hSize=this._size/2;\n    return [\n      [this._x-hSize, this._y-hSize],\n      [this._x+hSize, this._y-hSize],\n      [this._x-hSize, this._y+hSize],\n      [this._x+hSize, this._y+hSize]\n    ];\n  };\n\n  CropAreaSquare.prototype._calcSquareDimensions=function() {\n    var hSize=this._size/2;\n    return {\n      left: this._x-hSize,\n      top: this._y-hSize,\n      right: this._x+hSize,\n      bottom: this._y+hSize\n    };\n  };\n\n  CropAreaSquare.prototype._isCoordWithinArea=function(coord) {\n    var squareDimensions=this._calcSquareDimensions();\n    return (coord[0]>=squareDimensions.left&&coord[0]<=squareDimensions.right&&coord[1]>=squareDimensions.top&&coord[1]<=squareDimensions.bottom);\n  };\n\n  CropAreaSquare.prototype._isCoordWithinResizeCtrl=function(coord) {\n    var resizeIconsCenterCoords=this._calcSquareCorners();\n    var res=-1;\n    for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {\n      var resizeIconCenterCoords=resizeIconsCenterCoords[i];\n      if(coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&\n         coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {\n        res=i;\n        break;\n      }\n    }\n    return res;\n  };\n\n  CropAreaSquare.prototype._drawArea=function(ctx,centerCoords,size){\n    var hSize=size/2;\n    ctx.rect(centerCoords[0]-hSize,centerCoords[1]-hSize,size,size);\n  };\n\n  CropAreaSquare.prototype.draw=function() {\n    CropArea.prototype.draw.apply(this, arguments);\n\n    // draw move icon\n    this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);\n\n    // draw resize cubes\n    var resizeIconsCenterCoords=this._calcSquareCorners();\n    for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {\n      var resizeIconCenterCoords=resizeIconsCenterCoords[i];\n      this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover===i?this._resizeCtrlHoverRatio:this._resizeCtrlNormalRatio);\n    }\n  };\n\n  CropAreaSquare.prototype.processMouseMove=function(mouseCurX, mouseCurY) {\n    var cursor='default';\n    var res=false;\n\n    this._resizeCtrlIsHover = -1;\n    this._areaIsHover = false;\n\n    if (this._areaIsDragging) {\n      this._x = mouseCurX - this._posDragStartX;\n      this._y = mouseCurY - this._posDragStartY;\n      this._areaIsHover = true;\n      cursor='move';\n      res=true;\n      this._events.trigger('area-move');\n    } else if (this._resizeCtrlIsDragging>-1) {\n      var xMulti, yMulti;\n      switch(this._resizeCtrlIsDragging) {\n        case 0: // Top Left\n          xMulti=-1;\n          yMulti=-1;\n          cursor = 'nwse-resize';\n          break;\n        case 1: // Top Right\n          xMulti=1;\n          yMulti=-1;\n          cursor = 'nesw-resize';\n          break;\n        case 2: // Bottom Left\n          xMulti=-1;\n          yMulti=1;\n          cursor = 'nesw-resize';\n          break;\n        case 3: // Bottom Right\n          xMulti=1;\n          yMulti=1;\n          cursor = 'nwse-resize';\n          break;\n      }\n      var iFX = (mouseCurX - this._posResizeStartX)*xMulti;\n      var iFY = (mouseCurY - this._posResizeStartY)*yMulti;\n      var iFR;\n      if(iFX>iFY) {\n        iFR = this._posResizeStartSize + iFY;\n      } else {\n        iFR = this._posResizeStartSize + iFX;\n      }\n      var wasSize=this._size;\n      this._size = Math.max(this._minSize, iFR);\n      var posModifier=(this._size-wasSize)/2;\n      this._x+=posModifier*xMulti;\n      this._y+=posModifier*yMulti;\n      this._resizeCtrlIsHover = this._resizeCtrlIsDragging;\n      res=true;\n      this._events.trigger('area-resize');\n    } else {\n      var hoveredResizeBox=this._isCoordWithinResizeCtrl([mouseCurX,mouseCurY]);\n      if (hoveredResizeBox>-1) {\n        switch(hoveredResizeBox) {\n          case 0:\n            cursor = 'nwse-resize';\n            break;\n          case 1:\n            cursor = 'nesw-resize';\n            break;\n          case 2:\n            cursor = 'nesw-resize';\n            break;\n          case 3:\n            cursor = 'nwse-resize';\n            break;\n        }\n        this._areaIsHover = false;\n        this._resizeCtrlIsHover = hoveredResizeBox;\n        res=true;\n      } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {\n        cursor = 'move';\n        this._areaIsHover = true;\n        res=true;\n      }\n    }\n\n    this._dontDragOutside();\n    angular.element(this._ctx.canvas).css({'cursor': cursor});\n\n    return res;\n  };\n\n  CropAreaSquare.prototype.processMouseDown=function(mouseDownX, mouseDownY) {\n    var isWithinResizeCtrl=this._isCoordWithinResizeCtrl([mouseDownX,mouseDownY]);\n    if (isWithinResizeCtrl>-1) {\n      this._areaIsDragging = false;\n      this._areaIsHover = false;\n      this._resizeCtrlIsDragging = isWithinResizeCtrl;\n      this._resizeCtrlIsHover = isWithinResizeCtrl;\n      this._posResizeStartX=mouseDownX;\n      this._posResizeStartY=mouseDownY;\n      this._posResizeStartSize = this._size;\n      this._events.trigger('area-resize-start');\n    } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {\n      this._areaIsDragging = true;\n      this._areaIsHover = true;\n      this._resizeCtrlIsDragging = -1;\n      this._resizeCtrlIsHover = -1;\n      this._posDragStartX = mouseDownX - this._x;\n      this._posDragStartY = mouseDownY - this._y;\n      this._events.trigger('area-move-start');\n    }\n  };\n\n  CropAreaSquare.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {\n    if(this._areaIsDragging) {\n      this._areaIsDragging = false;\n      this._events.trigger('area-move-end');\n    }\n    if(this._resizeCtrlIsDragging>-1) {\n      this._resizeCtrlIsDragging = -1;\n      this._events.trigger('area-resize-end');\n    }\n    this._areaIsHover = false;\n    this._resizeCtrlIsHover = -1;\n\n    this._posDragStartX = 0;\n    this._posDragStartY = 0;\n  };\n\n  return CropAreaSquare;\n}]);\n\ncrop.factory('cropArea', ['cropCanvas', function(CropCanvas) {\n  var CropArea = function(ctx, events) {\n    this._ctx=ctx;\n    this._events=events;\n\n    this._minSize=80;\n\n    this._cropCanvas=new CropCanvas(ctx);\n\n    this._image=new Image();\n    this._x = 0;\n    this._y = 0;\n    this._size = 200;\n  };\n\n  /* GETTERS/SETTERS */\n\n  CropArea.prototype.getImage = function () {\n    return this._image;\n  };\n  CropArea.prototype.setImage = function (image) {\n    this._image = image;\n  };\n\n  CropArea.prototype.getX = function () {\n    return this._x;\n  };\n  CropArea.prototype.setX = function (x) {\n    this._x = x;\n    this._dontDragOutside();\n  };\n\n  CropArea.prototype.getY = function () {\n    return this._y;\n  };\n  CropArea.prototype.setY = function (y) {\n    this._y = y;\n    this._dontDragOutside();\n  };\n\n  CropArea.prototype.getSize = function () {\n    return this._size;\n  };\n  CropArea.prototype.setSize = function (size) {\n    this._size = Math.max(this._minSize, size);\n    this._dontDragOutside();\n  };\n\n  CropArea.prototype.getMinSize = function () {\n    return this._minSize;\n  };\n  CropArea.prototype.setMinSize = function (size) {\n    this._minSize = size;\n    this._size = Math.max(this._minSize, this._size);\n    this._dontDragOutside();\n  };\n\n  /* FUNCTIONS */\n  CropArea.prototype._dontDragOutside=function() {\n    var h=this._ctx.canvas.height,\n        w=this._ctx.canvas.width;\n    if(this._size>w) { this._size=w; }\n    if(this._size>h) { this._size=h; }\n    if(this._x<this._size/2) { this._x=this._size/2; }\n    if(this._x>w-this._size/2) { this._x=w-this._size/2; }\n    if(this._y<this._size/2) { this._y=this._size/2; }\n    if(this._y>h-this._size/2) { this._y=h-this._size/2; }\n  };\n\n  CropArea.prototype._drawArea=function() {};\n\n  CropArea.prototype.draw=function() {\n    // draw crop area\n    this._cropCanvas.drawCropArea(this._image,[this._x,this._y],this._size,this._drawArea);\n  };\n\n  CropArea.prototype.processMouseMove=function() {};\n\n  CropArea.prototype.processMouseDown=function() {};\n\n  CropArea.prototype.processMouseUp=function() {};\n\n  return CropArea;\n}]);\n\ncrop.factory('cropCanvas', [function() {\n  // Shape = Array of [x,y]; [0, 0] - center\n  var shapeArrowNW=[[-0.5,-2],[-3,-4.5],[-0.5,-7],[-7,-7],[-7,-0.5],[-4.5,-3],[-2,-0.5]];\n  var shapeArrowNE=[[0.5,-2],[3,-4.5],[0.5,-7],[7,-7],[7,-0.5],[4.5,-3],[2,-0.5]];\n  var shapeArrowSW=[[-0.5,2],[-3,4.5],[-0.5,7],[-7,7],[-7,0.5],[-4.5,3],[-2,0.5]];\n  var shapeArrowSE=[[0.5,2],[3,4.5],[0.5,7],[7,7],[7,0.5],[4.5,3],[2,0.5]];\n  var shapeArrowN=[[-1.5,-2.5],[-1.5,-6],[-5,-6],[0,-11],[5,-6],[1.5,-6],[1.5,-2.5]];\n  var shapeArrowW=[[-2.5,-1.5],[-6,-1.5],[-6,-5],[-11,0],[-6,5],[-6,1.5],[-2.5,1.5]];\n  var shapeArrowS=[[-1.5,2.5],[-1.5,6],[-5,6],[0,11],[5,6],[1.5,6],[1.5,2.5]];\n  var shapeArrowE=[[2.5,-1.5],[6,-1.5],[6,-5],[11,0],[6,5],[6,1.5],[2.5,1.5]];\n\n  // Colors\n  var colors={\n    areaOutline: '#fff',\n    resizeBoxStroke: '#fff',\n    resizeBoxFill: '#444',\n    resizeBoxArrowFill: '#fff',\n    resizeCircleStroke: '#fff',\n    resizeCircleFill: '#444',\n    moveIconFill: '#fff'\n  };\n\n  return function(ctx){\n\n    /* Base functions */\n\n    // Calculate Point\n    var calcPoint=function(point,offset,scale) {\n        return [scale*point[0]+offset[0], scale*point[1]+offset[1]];\n    };\n\n    // Draw Filled Polygon\n    var drawFilledPolygon=function(shape,fillStyle,centerCoords,scale) {\n        ctx.save();\n        ctx.fillStyle = fillStyle;\n        ctx.beginPath();\n        var pc, pc0=calcPoint(shape[0],centerCoords,scale);\n        ctx.moveTo(pc0[0],pc0[1]);\n\n        for(var p in shape) {\n            if (p > 0) {\n                pc=calcPoint(shape[p],centerCoords,scale);\n                ctx.lineTo(pc[0],pc[1]);\n            }\n        }\n\n        ctx.lineTo(pc0[0],pc0[1]);\n        ctx.fill();\n        ctx.closePath();\n        ctx.restore();\n    };\n\n    /* Icons */\n\n    this.drawIconMove=function(centerCoords, scale) {\n      drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale);\n      drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale);\n      drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale);\n      drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale);\n    };\n\n    this.drawIconResizeCircle=function(centerCoords, circleRadius, scale) {\n      var scaledCircleRadius=circleRadius*scale;\n      ctx.save();\n      ctx.strokeStyle = colors.resizeCircleStroke;\n      ctx.lineWidth = 2;\n      ctx.fillStyle = colors.resizeCircleFill;\n      ctx.beginPath();\n      ctx.arc(centerCoords[0],centerCoords[1],scaledCircleRadius,0,2*Math.PI);\n      ctx.fill();\n      ctx.stroke();\n      ctx.closePath();\n      ctx.restore();\n    };\n\n    this.drawIconResizeBoxBase=function(centerCoords, boxSize, scale) {\n      var scaledBoxSize=boxSize*scale;\n      ctx.save();\n      ctx.strokeStyle = colors.resizeBoxStroke;\n      ctx.lineWidth = 2;\n      ctx.fillStyle = colors.resizeBoxFill;\n      ctx.fillRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);\n      ctx.strokeRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);\n      ctx.restore();\n    };\n    this.drawIconResizeBoxNESW=function(centerCoords, boxSize, scale) {\n      this.drawIconResizeBoxBase(centerCoords, boxSize, scale);\n      drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale);\n      drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale);\n    };\n    this.drawIconResizeBoxNWSE=function(centerCoords, boxSize, scale) {\n      this.drawIconResizeBoxBase(centerCoords, boxSize, scale);\n      drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale);\n      drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale);\n    };\n\n    /* Crop Area */\n\n    this.drawCropArea=function(image, centerCoords, size, fnDrawClipPath) {\n      var xRatio=image.width/ctx.canvas.width,\n          yRatio=image.height/ctx.canvas.height,\n          xLeft=centerCoords[0]-size/2,\n          yTop=centerCoords[1]-size/2;\n\n      ctx.save();\n      ctx.strokeStyle = colors.areaOutline;\n      ctx.lineWidth = 2;\n      ctx.beginPath();\n      fnDrawClipPath(ctx, centerCoords, size);\n      ctx.stroke();\n      ctx.clip();\n\n      // draw part of original image\n      if (size > 0) {\n          ctx.drawImage(image, xLeft*xRatio, yTop*yRatio, size*xRatio, size*yRatio, xLeft, yTop, size, size);\n      }\n\n      ctx.beginPath();\n      fnDrawClipPath(ctx, centerCoords, size);\n      ctx.stroke();\n      ctx.clip();\n\n      ctx.restore();\n    };\n\n  };\n}]);\n\n/**\n * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js)\n */\n\ncrop.service('cropEXIF', [function() {\n  var debug = false;\n\n  var ExifTags = this.Tags = {\n\n      // version tags\n      0x9000 : \"ExifVersion\",             // EXIF version\n      0xA000 : \"FlashpixVersion\",         // Flashpix format version\n\n      // colorspace tags\n      0xA001 : \"ColorSpace\",              // Color space information tag\n\n      // image configuration\n      0xA002 : \"PixelXDimension\",         // Valid width of meaningful image\n      0xA003 : \"PixelYDimension\",         // Valid height of meaningful image\n      0x9101 : \"ComponentsConfiguration\", // Information about channels\n      0x9102 : \"CompressedBitsPerPixel\",  // Compressed bits per pixel\n\n      // user information\n      0x927C : \"MakerNote\",               // Any desired information written by the manufacturer\n      0x9286 : \"UserComment\",             // Comments by user\n\n      // related file\n      0xA004 : \"RelatedSoundFile\",        // Name of related sound file\n\n      // date and time\n      0x9003 : \"DateTimeOriginal\",        // Date and time when the original image was generated\n      0x9004 : \"DateTimeDigitized\",       // Date and time when the image was stored digitally\n      0x9290 : \"SubsecTime\",              // Fractions of seconds for DateTime\n      0x9291 : \"SubsecTimeOriginal\",      // Fractions of seconds for DateTimeOriginal\n      0x9292 : \"SubsecTimeDigitized\",     // Fractions of seconds for DateTimeDigitized\n\n      // picture-taking conditions\n      0x829A : \"ExposureTime\",            // Exposure time (in seconds)\n      0x829D : \"FNumber\",                 // F number\n      0x8822 : \"ExposureProgram\",         // Exposure program\n      0x8824 : \"SpectralSensitivity\",     // Spectral sensitivity\n      0x8827 : \"ISOSpeedRatings\",         // ISO speed rating\n      0x8828 : \"OECF\",                    // Optoelectric conversion factor\n      0x9201 : \"ShutterSpeedValue\",       // Shutter speed\n      0x9202 : \"ApertureValue\",           // Lens aperture\n      0x9203 : \"BrightnessValue\",         // Value of brightness\n      0x9204 : \"ExposureBias\",            // Exposure bias\n      0x9205 : \"MaxApertureValue\",        // Smallest F number of lens\n      0x9206 : \"SubjectDistance\",         // Distance to subject in meters\n      0x9207 : \"MeteringMode\",            // Metering mode\n      0x9208 : \"LightSource\",             // Kind of light source\n      0x9209 : \"Flash\",                   // Flash status\n      0x9214 : \"SubjectArea\",             // Location and area of main subject\n      0x920A : \"FocalLength\",             // Focal length of the lens in mm\n      0xA20B : \"FlashEnergy\",             // Strobe energy in BCPS\n      0xA20C : \"SpatialFrequencyResponse\",    //\n      0xA20E : \"FocalPlaneXResolution\",   // Number of pixels in width direction per FocalPlaneResolutionUnit\n      0xA20F : \"FocalPlaneYResolution\",   // Number of pixels in height direction per FocalPlaneResolutionUnit\n      0xA210 : \"FocalPlaneResolutionUnit\",    // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution\n      0xA214 : \"SubjectLocation\",         // Location of subject in image\n      0xA215 : \"ExposureIndex\",           // Exposure index selected on camera\n      0xA217 : \"SensingMethod\",           // Image sensor type\n      0xA300 : \"FileSource\",              // Image source (3 == DSC)\n      0xA301 : \"SceneType\",               // Scene type (1 == directly photographed)\n      0xA302 : \"CFAPattern\",              // Color filter array geometric pattern\n      0xA401 : \"CustomRendered\",          // Special processing\n      0xA402 : \"ExposureMode\",            // Exposure mode\n      0xA403 : \"WhiteBalance\",            // 1 = auto white balance, 2 = manual\n      0xA404 : \"DigitalZoomRation\",       // Digital zoom ratio\n      0xA405 : \"FocalLengthIn35mmFilm\",   // Equivalent foacl length assuming 35mm film camera (in mm)\n      0xA406 : \"SceneCaptureType\",        // Type of scene\n      0xA407 : \"GainControl\",             // Degree of overall image gain adjustment\n      0xA408 : \"Contrast\",                // Direction of contrast processing applied by camera\n      0xA409 : \"Saturation\",              // Direction of saturation processing applied by camera\n      0xA40A : \"Sharpness\",               // Direction of sharpness processing applied by camera\n      0xA40B : \"DeviceSettingDescription\",    //\n      0xA40C : \"SubjectDistanceRange\",    // Distance to subject\n\n      // other tags\n      0xA005 : \"InteroperabilityIFDPointer\",\n      0xA420 : \"ImageUniqueID\"            // Identifier assigned uniquely to each image\n  };\n\n  var TiffTags = this.TiffTags = {\n      0x0100 : \"ImageWidth\",\n      0x0101 : \"ImageHeight\",\n      0x8769 : \"ExifIFDPointer\",\n      0x8825 : \"GPSInfoIFDPointer\",\n      0xA005 : \"InteroperabilityIFDPointer\",\n      0x0102 : \"BitsPerSample\",\n      0x0103 : \"Compression\",\n      0x0106 : \"PhotometricInterpretation\",\n      0x0112 : \"Orientation\",\n      0x0115 : \"SamplesPerPixel\",\n      0x011C : \"PlanarConfiguration\",\n      0x0212 : \"YCbCrSubSampling\",\n      0x0213 : \"YCbCrPositioning\",\n      0x011A : \"XResolution\",\n      0x011B : \"YResolution\",\n      0x0128 : \"ResolutionUnit\",\n      0x0111 : \"StripOffsets\",\n      0x0116 : \"RowsPerStrip\",\n      0x0117 : \"StripByteCounts\",\n      0x0201 : \"JPEGInterchangeFormat\",\n      0x0202 : \"JPEGInterchangeFormatLength\",\n      0x012D : \"TransferFunction\",\n      0x013E : \"WhitePoint\",\n      0x013F : \"PrimaryChromaticities\",\n      0x0211 : \"YCbCrCoefficients\",\n      0x0214 : \"ReferenceBlackWhite\",\n      0x0132 : \"DateTime\",\n      0x010E : \"ImageDescription\",\n      0x010F : \"Make\",\n      0x0110 : \"Model\",\n      0x0131 : \"Software\",\n      0x013B : \"Artist\",\n      0x8298 : \"Copyright\"\n  };\n\n  var GPSTags = this.GPSTags = {\n      0x0000 : \"GPSVersionID\",\n      0x0001 : \"GPSLatitudeRef\",\n      0x0002 : \"GPSLatitude\",\n      0x0003 : \"GPSLongitudeRef\",\n      0x0004 : \"GPSLongitude\",\n      0x0005 : \"GPSAltitudeRef\",\n      0x0006 : \"GPSAltitude\",\n      0x0007 : \"GPSTimeStamp\",\n      0x0008 : \"GPSSatellites\",\n      0x0009 : \"GPSStatus\",\n      0x000A : \"GPSMeasureMode\",\n      0x000B : \"GPSDOP\",\n      0x000C : \"GPSSpeedRef\",\n      0x000D : \"GPSSpeed\",\n      0x000E : \"GPSTrackRef\",\n      0x000F : \"GPSTrack\",\n      0x0010 : \"GPSImgDirectionRef\",\n      0x0011 : \"GPSImgDirection\",\n      0x0012 : \"GPSMapDatum\",\n      0x0013 : \"GPSDestLatitudeRef\",\n      0x0014 : \"GPSDestLatitude\",\n      0x0015 : \"GPSDestLongitudeRef\",\n      0x0016 : \"GPSDestLongitude\",\n      0x0017 : \"GPSDestBearingRef\",\n      0x0018 : \"GPSDestBearing\",\n      0x0019 : \"GPSDestDistanceRef\",\n      0x001A : \"GPSDestDistance\",\n      0x001B : \"GPSProcessingMethod\",\n      0x001C : \"GPSAreaInformation\",\n      0x001D : \"GPSDateStamp\",\n      0x001E : \"GPSDifferential\"\n  };\n\n  var StringValues = this.StringValues = {\n      ExposureProgram : {\n          0 : \"Not defined\",\n          1 : \"Manual\",\n          2 : \"Normal program\",\n          3 : \"Aperture priority\",\n          4 : \"Shutter priority\",\n          5 : \"Creative program\",\n          6 : \"Action program\",\n          7 : \"Portrait mode\",\n          8 : \"Landscape mode\"\n      },\n      MeteringMode : {\n          0 : \"Unknown\",\n          1 : \"Average\",\n          2 : \"CenterWeightedAverage\",\n          3 : \"Spot\",\n          4 : \"MultiSpot\",\n          5 : \"Pattern\",\n          6 : \"Partial\",\n          255 : \"Other\"\n      },\n      LightSource : {\n          0 : \"Unknown\",\n          1 : \"Daylight\",\n          2 : \"Fluorescent\",\n          3 : \"Tungsten (incandescent light)\",\n          4 : \"Flash\",\n          9 : \"Fine weather\",\n          10 : \"Cloudy weather\",\n          11 : \"Shade\",\n          12 : \"Daylight fluorescent (D 5700 - 7100K)\",\n          13 : \"Day white fluorescent (N 4600 - 5400K)\",\n          14 : \"Cool white fluorescent (W 3900 - 4500K)\",\n          15 : \"White fluorescent (WW 3200 - 3700K)\",\n          17 : \"Standard light A\",\n          18 : \"Standard light B\",\n          19 : \"Standard light C\",\n          20 : \"D55\",\n          21 : \"D65\",\n          22 : \"D75\",\n          23 : \"D50\",\n          24 : \"ISO studio tungsten\",\n          255 : \"Other\"\n      },\n      Flash : {\n          0x0000 : \"Flash did not fire\",\n          0x0001 : \"Flash fired\",\n          0x0005 : \"Strobe return light not detected\",\n          0x0007 : \"Strobe return light detected\",\n          0x0009 : \"Flash fired, compulsory flash mode\",\n          0x000D : \"Flash fired, compulsory flash mode, return light not detected\",\n          0x000F : \"Flash fired, compulsory flash mode, return light detected\",\n          0x0010 : \"Flash did not fire, compulsory flash mode\",\n          0x0018 : \"Flash did not fire, auto mode\",\n          0x0019 : \"Flash fired, auto mode\",\n          0x001D : \"Flash fired, auto mode, return light not detected\",\n          0x001F : \"Flash fired, auto mode, return light detected\",\n          0x0020 : \"No flash function\",\n          0x0041 : \"Flash fired, red-eye reduction mode\",\n          0x0045 : \"Flash fired, red-eye reduction mode, return light not detected\",\n          0x0047 : \"Flash fired, red-eye reduction mode, return light detected\",\n          0x0049 : \"Flash fired, compulsory flash mode, red-eye reduction mode\",\n          0x004D : \"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected\",\n          0x004F : \"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected\",\n          0x0059 : \"Flash fired, auto mode, red-eye reduction mode\",\n          0x005D : \"Flash fired, auto mode, return light not detected, red-eye reduction mode\",\n          0x005F : \"Flash fired, auto mode, return light detected, red-eye reduction mode\"\n      },\n      SensingMethod : {\n          1 : \"Not defined\",\n          2 : \"One-chip color area sensor\",\n          3 : \"Two-chip color area sensor\",\n          4 : \"Three-chip color area sensor\",\n          5 : \"Color sequential area sensor\",\n          7 : \"Trilinear sensor\",\n          8 : \"Color sequential linear sensor\"\n      },\n      SceneCaptureType : {\n          0 : \"Standard\",\n          1 : \"Landscape\",\n          2 : \"Portrait\",\n          3 : \"Night scene\"\n      },\n      SceneType : {\n          1 : \"Directly photographed\"\n      },\n      CustomRendered : {\n          0 : \"Normal process\",\n          1 : \"Custom process\"\n      },\n      WhiteBalance : {\n          0 : \"Auto white balance\",\n          1 : \"Manual white balance\"\n      },\n      GainControl : {\n          0 : \"None\",\n          1 : \"Low gain up\",\n          2 : \"High gain up\",\n          3 : \"Low gain down\",\n          4 : \"High gain down\"\n      },\n      Contrast : {\n          0 : \"Normal\",\n          1 : \"Soft\",\n          2 : \"Hard\"\n      },\n      Saturation : {\n          0 : \"Normal\",\n          1 : \"Low saturation\",\n          2 : \"High saturation\"\n      },\n      Sharpness : {\n          0 : \"Normal\",\n          1 : \"Soft\",\n          2 : \"Hard\"\n      },\n      SubjectDistanceRange : {\n          0 : \"Unknown\",\n          1 : \"Macro\",\n          2 : \"Close view\",\n          3 : \"Distant view\"\n      },\n      FileSource : {\n          3 : \"DSC\"\n      },\n\n      Components : {\n          0 : \"\",\n          1 : \"Y\",\n          2 : \"Cb\",\n          3 : \"Cr\",\n          4 : \"R\",\n          5 : \"G\",\n          6 : \"B\"\n      }\n  };\n\n  function addEvent(element, event, handler) {\n      if (element.addEventListener) {\n          element.addEventListener(event, handler, false);\n      } else if (element.attachEvent) {\n          element.attachEvent(\"on\" + event, handler);\n      }\n  }\n\n  function imageHasData(img) {\n      return !!(img.exifdata);\n  }\n\n  function base64ToArrayBuffer(base64, contentType) {\n      contentType = contentType || base64.match(/^data\\:([^\\;]+)\\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'\n      base64 = base64.replace(/^data\\:([^\\;]+)\\;base64,/gmi, '');\n      var binary = atob(base64);\n      var len = binary.length;\n      var buffer = new ArrayBuffer(len);\n      var view = new Uint8Array(buffer);\n      for (var i = 0; i < len; i++) {\n          view[i] = binary.charCodeAt(i);\n      }\n      return buffer;\n  }\n\n  function objectURLToBlob(url, callback) {\n      var http = new XMLHttpRequest();\n      http.open(\"GET\", url, true);\n      http.responseType = \"blob\";\n      http.onload = function(e) {\n          if (this.status == 200 || this.status === 0) {\n              callback(this.response);\n          }\n      };\n      http.send();\n  }\n\n  function getImageData(img, callback) {\n      function handleBinaryFile(binFile) {\n          var data = findEXIFinJPEG(binFile);\n          var iptcdata = findIPTCinJPEG(binFile);\n          img.exifdata = data || {};\n          img.iptcdata = iptcdata || {};\n          if (callback) {\n              callback.call(img);\n          }\n      }\n\n      if (img.src) {\n          if (/^data\\:/i.test(img.src)) { // Data URI\n              var arrayBuffer = base64ToArrayBuffer(img.src);\n              handleBinaryFile(arrayBuffer);\n\n          } else if (/^blob\\:/i.test(img.src)) { // Object URL\n              var fileReader = new FileReader();\n              fileReader.onload = function(e) {\n                  handleBinaryFile(e.target.result);\n              };\n              objectURLToBlob(img.src, function (blob) {\n                  fileReader.readAsArrayBuffer(blob);\n              });\n          } else {\n              var http = new XMLHttpRequest();\n              http.onload = function() {\n                  if (this.status == 200 || this.status === 0) {\n                      handleBinaryFile(http.response);\n                  } else {\n                      throw \"Could not load image\";\n                  }\n                  http = null;\n              };\n              http.open(\"GET\", img.src, true);\n              http.responseType = \"arraybuffer\";\n              http.send(null);\n          }\n      } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {\n          var fileReader = new FileReader();\n          fileReader.onload = function(e) {\n              if (debug) console.log(\"Got file of length \" + e.target.result.byteLength);\n              handleBinaryFile(e.target.result);\n          };\n\n          fileReader.readAsArrayBuffer(img);\n      }\n  }\n\n  function findEXIFinJPEG(file) {\n      var dataView = new DataView(file);\n\n      if (debug) console.log(\"Got file of length \" + file.byteLength);\n      if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {\n          if (debug) console.log(\"Not a valid JPEG\");\n          return false; // not a valid jpeg\n      }\n\n      var offset = 2,\n          length = file.byteLength,\n          marker;\n\n      while (offset < length) {\n          if (dataView.getUint8(offset) != 0xFF) {\n              if (debug) console.log(\"Not a valid marker at offset \" + offset + \", found: \" + dataView.getUint8(offset));\n              return false; // not a valid marker, something is wrong\n          }\n\n          marker = dataView.getUint8(offset + 1);\n          if (debug) console.log(marker);\n\n          // we could implement handling for other markers here,\n          // but we're only looking for 0xFFE1 for EXIF data\n\n          if (marker == 225) {\n              if (debug) console.log(\"Found 0xFFE1 marker\");\n\n              return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);\n\n              // offset += 2 + file.getShortAt(offset+2, true);\n\n          } else {\n              offset += 2 + dataView.getUint16(offset+2);\n          }\n\n      }\n\n  }\n\n  function findIPTCinJPEG(file) {\n      var dataView = new DataView(file);\n\n      if (debug) console.log(\"Got file of length \" + file.byteLength);\n      if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {\n          if (debug) console.log(\"Not a valid JPEG\");\n          return false; // not a valid jpeg\n      }\n\n      var offset = 2,\n          length = file.byteLength;\n\n      var isFieldSegmentStart = function(dataView, offset){\n          return (\n              dataView.getUint8(offset) === 0x38 &&\n              dataView.getUint8(offset+1) === 0x42 &&\n              dataView.getUint8(offset+2) === 0x49 &&\n              dataView.getUint8(offset+3) === 0x4D &&\n              dataView.getUint8(offset+4) === 0x04 &&\n              dataView.getUint8(offset+5) === 0x04\n          );\n      };\n\n      while (offset < length) {\n\n          if ( isFieldSegmentStart(dataView, offset )){\n\n              // Get the length of the name header (which is padded to an even number of bytes)\n              var nameHeaderLength = dataView.getUint8(offset+7);\n              if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;\n              // Check for pre photoshop 6 format\n              if(nameHeaderLength === 0) {\n                  // Always 4\n                  nameHeaderLength = 4;\n              }\n\n              var startOffset = offset + 8 + nameHeaderLength;\n              var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);\n\n              return readIPTCData(file, startOffset, sectionLength);\n\n              break;\n\n          }\n\n          // Not the marker, continue searching\n          offset++;\n\n      }\n\n  }\n  var IptcFieldMap = {\n      0x78 : 'caption',\n      0x6E : 'credit',\n      0x19 : 'keywords',\n      0x37 : 'dateCreated',\n      0x50 : 'byline',\n      0x55 : 'bylineTitle',\n      0x7A : 'captionWriter',\n      0x69 : 'headline',\n      0x74 : 'copyright',\n      0x0F : 'category'\n  };\n  function readIPTCData(file, startOffset, sectionLength){\n      var dataView = new DataView(file);\n      var data = {};\n      var fieldValue, fieldName, dataSize, segmentType, segmentSize;\n      var segmentStartPos = startOffset;\n      while(segmentStartPos < startOffset+sectionLength) {\n          if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){\n              segmentType = dataView.getUint8(segmentStartPos+2);\n              if(segmentType in IptcFieldMap) {\n                  dataSize = dataView.getInt16(segmentStartPos+3);\n                  segmentSize = dataSize + 5;\n                  fieldName = IptcFieldMap[segmentType];\n                  fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);\n                  // Check if we already stored a value with this name\n                  if(data.hasOwnProperty(fieldName)) {\n                      // Value already stored with this name, create multivalue field\n                      if(data[fieldName] instanceof Array) {\n                          data[fieldName].push(fieldValue);\n                      }\n                      else {\n                          data[fieldName] = [data[fieldName], fieldValue];\n                      }\n                  }\n                  else {\n                      data[fieldName] = fieldValue;\n                  }\n              }\n\n          }\n          segmentStartPos++;\n      }\n      return data;\n  }\n\n  function readTags(file, tiffStart, dirStart, strings, bigEnd) {\n      var entries = file.getUint16(dirStart, !bigEnd),\n          tags = {},\n          entryOffset, tag,\n          i;\n\n      for (i=0;i<entries;i++) {\n          entryOffset = dirStart + i*12 + 2;\n          tag = strings[file.getUint16(entryOffset, !bigEnd)];\n          if (!tag && debug) console.log(\"Unknown tag: \" + file.getUint16(entryOffset, !bigEnd));\n          tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);\n      }\n      return tags;\n  }\n\n  function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {\n      var type = file.getUint16(entryOffset+2, !bigEnd),\n          numValues = file.getUint32(entryOffset+4, !bigEnd),\n          valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,\n          offset,\n          vals, val, n,\n          numerator, denominator;\n\n      switch (type) {\n          case 1: // byte, 8-bit unsigned int\n          case 7: // undefined, 8-bit byte, value depending on field\n              if (numValues == 1) {\n                  return file.getUint8(entryOffset + 8, !bigEnd);\n              } else {\n                  offset = numValues > 4 ? valueOffset : (entryOffset + 8);\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      vals[n] = file.getUint8(offset + n);\n                  }\n                  return vals;\n              }\n\n          case 2: // ascii, 8-bit byte\n              offset = numValues > 4 ? valueOffset : (entryOffset + 8);\n              return getStringFromDB(file, offset, numValues-1);\n\n          case 3: // short, 16 bit int\n              if (numValues == 1) {\n                  return file.getUint16(entryOffset + 8, !bigEnd);\n              } else {\n                  offset = numValues > 2 ? valueOffset : (entryOffset + 8);\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      vals[n] = file.getUint16(offset + 2*n, !bigEnd);\n                  }\n                  return vals;\n              }\n\n          case 4: // long, 32 bit int\n              if (numValues == 1) {\n                  return file.getUint32(entryOffset + 8, !bigEnd);\n              } else {\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);\n                  }\n                  return vals;\n              }\n\n          case 5:    // rational = two long values, first is numerator, second is denominator\n              if (numValues == 1) {\n                  numerator = file.getUint32(valueOffset, !bigEnd);\n                  denominator = file.getUint32(valueOffset+4, !bigEnd);\n                  val = new Number(numerator / denominator);\n                  val.numerator = numerator;\n                  val.denominator = denominator;\n                  return val;\n              } else {\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      numerator = file.getUint32(valueOffset + 8*n, !bigEnd);\n                      denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);\n                      vals[n] = new Number(numerator / denominator);\n                      vals[n].numerator = numerator;\n                      vals[n].denominator = denominator;\n                  }\n                  return vals;\n              }\n\n          case 9: // slong, 32 bit signed int\n              if (numValues == 1) {\n                  return file.getInt32(entryOffset + 8, !bigEnd);\n              } else {\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);\n                  }\n                  return vals;\n              }\n\n          case 10: // signed rational, two slongs, first is numerator, second is denominator\n              if (numValues == 1) {\n                  return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);\n              } else {\n                  vals = [];\n                  for (n=0;n<numValues;n++) {\n                      vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);\n                  }\n                  return vals;\n              }\n      }\n  }\n\n  function getStringFromDB(buffer, start, length) {\n      var outstr = \"\";\n      for (var n = start; n < start+length; n++) {\n          outstr += String.fromCharCode(buffer.getUint8(n));\n      }\n      return outstr;\n  }\n\n  function readEXIFData(file, start) {\n      if (getStringFromDB(file, start, 4) != \"Exif\") {\n          if (debug) console.log(\"Not valid EXIF data! \" + getStringFromDB(file, start, 4));\n          return false;\n      }\n\n      var bigEnd,\n          tags, tag,\n          exifData, gpsData,\n          tiffOffset = start + 6;\n\n      // test for TIFF validity and endianness\n      if (file.getUint16(tiffOffset) == 0x4949) {\n          bigEnd = false;\n      } else if (file.getUint16(tiffOffset) == 0x4D4D) {\n          bigEnd = true;\n      } else {\n          if (debug) console.log(\"Not valid TIFF data! (no 0x4949 or 0x4D4D)\");\n          return false;\n      }\n\n      if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {\n          if (debug) console.log(\"Not valid TIFF data! (no 0x002A)\");\n          return false;\n      }\n\n      var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);\n\n      if (firstIFDOffset < 0x00000008) {\n          if (debug) console.log(\"Not valid TIFF data! (First offset less than 8)\", file.getUint32(tiffOffset+4, !bigEnd));\n          return false;\n      }\n\n      tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);\n\n      if (tags.ExifIFDPointer) {\n          exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);\n          for (tag in exifData) {\n              switch (tag) {\n                  case \"LightSource\" :\n                  case \"Flash\" :\n                  case \"MeteringMode\" :\n                  case \"ExposureProgram\" :\n                  case \"SensingMethod\" :\n                  case \"SceneCaptureType\" :\n                  case \"SceneType\" :\n                  case \"CustomRendered\" :\n                  case \"WhiteBalance\" :\n                  case \"GainControl\" :\n                  case \"Contrast\" :\n                  case \"Saturation\" :\n                  case \"Sharpness\" :\n                  case \"SubjectDistanceRange\" :\n                  case \"FileSource\" :\n                      exifData[tag] = StringValues[tag][exifData[tag]];\n                      break;\n\n                  case \"ExifVersion\" :\n                  case \"FlashpixVersion\" :\n                      exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);\n                      break;\n\n                  case \"ComponentsConfiguration\" :\n                      exifData[tag] =\n                          StringValues.Components[exifData[tag][0]] +\n                          StringValues.Components[exifData[tag][1]] +\n                          StringValues.Components[exifData[tag][2]] +\n                          StringValues.Components[exifData[tag][3]];\n                      break;\n              }\n              tags[tag] = exifData[tag];\n          }\n      }\n\n      if (tags.GPSInfoIFDPointer) {\n          gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);\n          for (tag in gpsData) {\n              switch (tag) {\n                  case \"GPSVersionID\" :\n                      gpsData[tag] = gpsData[tag][0] +\n                          \".\" + gpsData[tag][1] +\n                          \".\" + gpsData[tag][2] +\n                          \".\" + gpsData[tag][3];\n                      break;\n              }\n              tags[tag] = gpsData[tag];\n          }\n      }\n\n      return tags;\n  }\n\n  this.getData = function(img, callback) {\n      if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false;\n\n      if (!imageHasData(img)) {\n          getImageData(img, callback);\n      } else {\n          if (callback) {\n              callback.call(img);\n          }\n      }\n      return true;\n  }\n\n  this.getTag = function(img, tag) {\n      if (!imageHasData(img)) return;\n      return img.exifdata[tag];\n  }\n\n  this.getAllTags = function(img) {\n      if (!imageHasData(img)) return {};\n      var a,\n          data = img.exifdata,\n          tags = {};\n      for (a in data) {\n          if (data.hasOwnProperty(a)) {\n              tags[a] = data[a];\n          }\n      }\n      return tags;\n  }\n\n  this.pretty = function(img) {\n      if (!imageHasData(img)) return \"\";\n      var a,\n          data = img.exifdata,\n          strPretty = \"\";\n      for (a in data) {\n          if (data.hasOwnProperty(a)) {\n              if (typeof data[a] == \"object\") {\n                  if (data[a] instanceof Number) {\n                      strPretty += a + \" : \" + data[a] + \" [\" + data[a].numerator + \"/\" + data[a].denominator + \"]\\r\\n\";\n                  } else {\n                      strPretty += a + \" : [\" + data[a].length + \" values]\\r\\n\";\n                  }\n              } else {\n                  strPretty += a + \" : \" + data[a] + \"\\r\\n\";\n              }\n          }\n      }\n      return strPretty;\n  }\n\n  this.readFromBinaryFile = function(file) {\n      return findEXIFinJPEG(file);\n  }\n}]);\n\ncrop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropEXIF', function($document, CropAreaCircle, CropAreaSquare, cropEXIF) {\n  /* STATIC FUNCTIONS */\n\n  // Get Element's Offset\n  var getElementOffset=function(elem) {\n      var box = elem.getBoundingClientRect();\n\n      var body = document.body;\n      var docElem = document.documentElement;\n\n      var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;\n      var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;\n\n      var clientTop = docElem.clientTop || body.clientTop || 0;\n      var clientLeft = docElem.clientLeft || body.clientLeft || 0;\n\n      var top  = box.top +  scrollTop - clientTop;\n      var left = box.left + scrollLeft - clientLeft;\n\n      return { top: Math.round(top), left: Math.round(left) };\n  };\n\n  return function(elCanvas, opts, events){\n    /* PRIVATE VARIABLES */\n\n    // Object Pointers\n    var ctx=null,\n        image=null,\n        theArea=null;\n\n    // Dimensions\n    var minCanvasDims=[100,100],\n        maxCanvasDims=[300,300];\n\n    // Result Image size\n    var resImgSize=200;\n\n    // Result Image type\n    var resImgFormat='image/png';\n\n    // Result Image quality\n    var resImgQuality=null;\n\n    /* PRIVATE FUNCTIONS */\n\n    // Draw Scene\n    function drawScene() {\n      // clear canvas\n      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n      if(image!==null) {\n        // draw source image\n        ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height);\n\n        ctx.save();\n\n        // and make it darker\n        ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';\n        ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n        ctx.restore();\n\n        // draw Area\n        theArea.draw();\n      }\n    }\n\n    // Resets CropHost\n    var resetCropHost=function() {\n      if(image!==null) {\n        theArea.setImage(image);\n        var imageDims=[image.width, image.height],\n            imageRatio=image.width/image.height,\n            canvasDims=imageDims;\n\n        if(canvasDims[0]>maxCanvasDims[0]) {\n          canvasDims[0]=maxCanvasDims[0];\n          canvasDims[1]=canvasDims[0]/imageRatio;\n        } else if(canvasDims[0]<minCanvasDims[0]) {\n          canvasDims[0]=minCanvasDims[0];\n          canvasDims[1]=canvasDims[0]/imageRatio;\n        }\n        if(canvasDims[1]>maxCanvasDims[1]) {\n          canvasDims[1]=maxCanvasDims[1];\n          canvasDims[0]=canvasDims[1]*imageRatio;\n        } else if(canvasDims[1]<minCanvasDims[1]) {\n          canvasDims[1]=minCanvasDims[1];\n          canvasDims[0]=canvasDims[1]*imageRatio;\n        }\n        elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});\n\n        theArea.setX(ctx.canvas.width/2);\n        theArea.setY(ctx.canvas.height/2);\n        theArea.setSize(Math.min(200, ctx.canvas.width/2, ctx.canvas.height/2));\n      } else {\n        elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});\n      }\n\n      drawScene();\n    };\n\n    /**\n     * Returns event.changedTouches directly if event is a TouchEvent.\n     * If event is a jQuery event, return changedTouches of event.originalEvent\n     */\n    var getChangedTouches=function(event){\n      if(angular.isDefined(event.changedTouches)){\n        return event.changedTouches;\n      }else{\n        return event.originalEvent.changedTouches;\n      }\n    };\n\n    var onMouseMove=function(e) {\n      if(image!==null) {\n        var offset=getElementOffset(ctx.canvas),\n            pageX, pageY;\n        if(e.type === 'touchmove') {\n          pageX=getChangedTouches(e)[0].pageX;\n          pageY=getChangedTouches(e)[0].pageY;\n        } else {\n          pageX=e.pageX;\n          pageY=e.pageY;\n        }\n        theArea.processMouseMove(pageX-offset.left, pageY-offset.top);\n        drawScene();\n      }\n    };\n\n    var onMouseDown=function(e) {\n      e.preventDefault();\n      e.stopPropagation();\n      if(image!==null) {\n        var offset=getElementOffset(ctx.canvas),\n            pageX, pageY;\n        if(e.type === 'touchstart') {\n          pageX=getChangedTouches(e)[0].pageX;\n          pageY=getChangedTouches(e)[0].pageY;\n        } else {\n          pageX=e.pageX;\n          pageY=e.pageY;\n        }\n        theArea.processMouseDown(pageX-offset.left, pageY-offset.top);\n        drawScene();\n      }\n    };\n\n    var onMouseUp=function(e) {\n      if(image!==null) {\n        var offset=getElementOffset(ctx.canvas),\n            pageX, pageY;\n        if(e.type === 'touchend') {\n          pageX=getChangedTouches(e)[0].pageX;\n          pageY=getChangedTouches(e)[0].pageY;\n        } else {\n          pageX=e.pageX;\n          pageY=e.pageY;\n        }\n        theArea.processMouseUp(pageX-offset.left, pageY-offset.top);\n        drawScene();\n      }\n    };\n\n    this.getResultImageDataURI=function() {\n      var temp_ctx, temp_canvas;\n      temp_canvas = angular.element('<canvas></canvas>')[0];\n      temp_ctx = temp_canvas.getContext('2d');\n      temp_canvas.width = resImgSize;\n      temp_canvas.height = resImgSize;\n      if(image!==null){\n        temp_ctx.drawImage(image, (theArea.getX()-theArea.getSize()/2)*(image.width/ctx.canvas.width), (theArea.getY()-theArea.getSize()/2)*(image.height/ctx.canvas.height), theArea.getSize()*(image.width/ctx.canvas.width), theArea.getSize()*(image.height/ctx.canvas.height), 0, 0, resImgSize, resImgSize);\n      }\n      if (resImgQuality!==null ){\n        return temp_canvas.toDataURL(resImgFormat, resImgQuality);\n      }\n      return temp_canvas.toDataURL(resImgFormat);\n    };\n\n    this.setNewImageSource=function(imageSource) {\n      image=null;\n      resetCropHost();\n      events.trigger('image-updated');\n      if(!!imageSource) {\n        var newImage = new Image();\n        if(imageSource.substring(0,4).toLowerCase()==='http') {\n          newImage.crossOrigin = 'anonymous';\n        }\n        newImage.onload = function(){\n          events.trigger('load-done');\n\n          cropEXIF.getData(newImage,function(){\n            var orientation=cropEXIF.getTag(newImage,'Orientation');\n\n            if([3,6,8].indexOf(orientation)>-1) {\n              var canvas = document.createElement(\"canvas\"),\n                  ctx=canvas.getContext(\"2d\"),\n                  cw = newImage.width, ch = newImage.height, cx = 0, cy = 0, deg=0;\n              switch(orientation) {\n                case 3:\n                  cx=-newImage.width;\n                  cy=-newImage.height;\n                  deg=180;\n                  break;\n                case 6:\n                  cw = newImage.height;\n                  ch = newImage.width;\n                  cy=-newImage.height;\n                  deg=90;\n                  break;\n                case 8:\n                  cw = newImage.height;\n                  ch = newImage.width;\n                  cx=-newImage.width;\n                  deg=270;\n                  break;\n              }\n\n              canvas.width = cw;\n              canvas.height = ch;\n              ctx.rotate(deg*Math.PI/180);\n              ctx.drawImage(newImage, cx, cy);\n\n              image=new Image();\n              image.src = canvas.toDataURL(\"image/png\");\n            } else {\n              image=newImage;\n            }\n            resetCropHost();\n            events.trigger('image-updated');\n          });\n        };\n        newImage.onerror=function() {\n          events.trigger('load-error');\n        };\n        events.trigger('load-start');\n        newImage.src=imageSource;\n      }\n    };\n\n    this.setMaxDimensions=function(width, height) {\n      maxCanvasDims=[width,height];\n\n      if(image!==null) {\n        var curWidth=ctx.canvas.width,\n            curHeight=ctx.canvas.height;\n\n        var imageDims=[image.width, image.height],\n            imageRatio=image.width/image.height,\n            canvasDims=imageDims;\n\n        if(canvasDims[0]>maxCanvasDims[0]) {\n          canvasDims[0]=maxCanvasDims[0];\n          canvasDims[1]=canvasDims[0]/imageRatio;\n        } else if(canvasDims[0]<minCanvasDims[0]) {\n          canvasDims[0]=minCanvasDims[0];\n          canvasDims[1]=canvasDims[0]/imageRatio;\n        }\n        if(canvasDims[1]>maxCanvasDims[1]) {\n          canvasDims[1]=maxCanvasDims[1];\n          canvasDims[0]=canvasDims[1]*imageRatio;\n        } else if(canvasDims[1]<minCanvasDims[1]) {\n          canvasDims[1]=minCanvasDims[1];\n          canvasDims[0]=canvasDims[1]*imageRatio;\n        }\n        elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});\n\n        var ratioNewCurWidth=ctx.canvas.width/curWidth,\n            ratioNewCurHeight=ctx.canvas.height/curHeight,\n            ratioMin=Math.min(ratioNewCurWidth, ratioNewCurHeight);\n\n        theArea.setX(theArea.getX()*ratioNewCurWidth);\n        theArea.setY(theArea.getY()*ratioNewCurHeight);\n        theArea.setSize(theArea.getSize()*ratioMin);\n      } else {\n        elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});\n      }\n\n      drawScene();\n\n    };\n\n    this.setAreaMinSize=function(size) {\n      size=parseInt(size,10);\n      if(!isNaN(size)) {\n        theArea.setMinSize(size);\n        drawScene();\n      }\n    };\n\n    this.setResultImageSize=function(size) {\n      size=parseInt(size,10);\n      if(!isNaN(size)) {\n        resImgSize=size;\n      }\n    };\n\n    this.setResultImageFormat=function(format) {\n      resImgFormat = format;\n    };\n\n    this.setResultImageQuality=function(quality){\n      quality = parseFloat(quality);\n      if (!isNaN(quality) && quality>=0 && quality<=1){\n        resImgQuality = quality;\n      }\n    };\n\n    this.setAreaType=function(type) {\n      var curSize=theArea.getSize(),\n          curMinSize=theArea.getMinSize(),\n          curX=theArea.getX(),\n          curY=theArea.getY();\n\n      var AreaClass=CropAreaCircle;\n      if(type==='square') {\n        AreaClass=CropAreaSquare;\n      }\n      theArea = new AreaClass(ctx, events);\n      theArea.setMinSize(curMinSize);\n      theArea.setSize(curSize);\n      theArea.setX(curX);\n      theArea.setY(curY);\n\n      // resetCropHost();\n      if(image!==null) {\n        theArea.setImage(image);\n      }\n\n      drawScene();\n    };\n\n    /* Life Cycle begins */\n\n    // Init Context var\n    ctx = elCanvas[0].getContext('2d');\n\n    // Init CropArea\n    theArea = new CropAreaCircle(ctx, events);\n\n    // Init Mouse Event Listeners\n    $document.on('mousemove',onMouseMove);\n    elCanvas.on('mousedown',onMouseDown);\n    $document.on('mouseup',onMouseUp);\n\n    // Init Touch Event Listeners\n    $document.on('touchmove',onMouseMove);\n    elCanvas.on('touchstart',onMouseDown);\n    $document.on('touchend',onMouseUp);\n\n    // CropHost Destructor\n    this.destroy=function() {\n      $document.off('mousemove',onMouseMove);\n      elCanvas.off('mousedown',onMouseDown);\n      $document.off('mouseup',onMouseMove);\n\n      $document.off('touchmove',onMouseMove);\n      elCanvas.off('touchstart',onMouseDown);\n      $document.off('touchend',onMouseMove);\n\n      elCanvas.remove();\n    };\n  };\n\n}]);\n\n\ncrop.factory('cropPubSub', [function() {\n  return function() {\n    var events = {};\n    // Subscribe\n    this.on = function(names, handler) {\n      names.split(' ').forEach(function(name) {\n        if (!events[name]) {\n          events[name] = [];\n        }\n        events[name].push(handler);\n      });\n      return this;\n    };\n    // Publish\n    this.trigger = function(name, args) {\n      angular.forEach(events[name], function(handler) {\n        handler.call(null, args);\n      });\n      return this;\n    };\n  };\n}]);\n\ncrop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function($timeout, CropHost, CropPubSub) {\n  return {\n    restrict: 'E',\n    scope: {\n      image: '=',\n      resultImage: '=',\n\n      changeOnFly: '=',\n      areaType: '@',\n      areaMinSize: '=',\n      resultImageSize: '=',\n      resultImageFormat: '@',\n      resultImageQuality: '=',\n\n      onChange: '&',\n      onLoadBegin: '&',\n      onLoadDone: '&',\n      onLoadError: '&'\n    },\n    template: '<canvas></canvas>',\n    controller: ['$scope', function($scope) {\n      $scope.events = new CropPubSub();\n    }],\n    link: function(scope, element/*, attrs*/) {\n      // Init Events Manager\n      var events = scope.events;\n\n      // Init Crop Host\n      var cropHost=new CropHost(element.find('canvas'), {}, events);\n\n      // Store Result Image to check if it's changed\n      var storedResultImage;\n\n      var updateResultImage=function(scope) {\n        var resultImage=cropHost.getResultImageDataURI();\n        if(storedResultImage!==resultImage) {\n          storedResultImage=resultImage;\n          if(angular.isDefined(scope.resultImage)) {\n            scope.resultImage=resultImage;\n          }\n          scope.onChange({$dataURI: scope.resultImage});\n        }\n      };\n\n      // Wrapper to safely exec functions within $apply on a running $digest cycle\n      var fnSafeApply=function(fn) {\n        return function(){\n          $timeout(function(){\n            scope.$apply(function(scope){\n              fn(scope);\n            });\n          });\n        };\n      };\n\n      // Setup CropHost Event Handlers\n      events\n        .on('load-start', fnSafeApply(function(scope){\n          scope.onLoadBegin({});\n        }))\n        .on('load-done', fnSafeApply(function(scope){\n          scope.onLoadDone({});\n        }))\n        .on('load-error', fnSafeApply(function(scope){\n          scope.onLoadError({});\n        }))\n        .on('area-move area-resize', fnSafeApply(function(scope){\n          if(!!scope.changeOnFly) {\n            updateResultImage(scope);\n          }\n        }))\n        .on('area-move-end area-resize-end image-updated', fnSafeApply(function(scope){\n          updateResultImage(scope);\n        }));\n\n      // Sync CropHost with Directive's options\n      scope.$watch('image',function(){\n        cropHost.setNewImageSource(scope.image);\n      });\n      scope.$watch('areaType',function(){\n        cropHost.setAreaType(scope.areaType);\n        updateResultImage(scope);\n      });\n      scope.$watch('areaMinSize',function(){\n        cropHost.setAreaMinSize(scope.areaMinSize);\n        updateResultImage(scope);\n      });\n      scope.$watch('resultImageSize',function(){\n        cropHost.setResultImageSize(scope.resultImageSize);\n        updateResultImage(scope);\n      });\n      scope.$watch('resultImageFormat',function(){\n        cropHost.setResultImageFormat(scope.resultImageFormat);\n        updateResultImage(scope);\n      });\n      scope.$watch('resultImageQuality',function(){\n        cropHost.setResultImageQuality(scope.resultImageQuality);\n        updateResultImage(scope);\n      });\n\n      // Update CropHost dimensions when the directive element is resized\n      scope.$watch(\n        function () {\n          return [element[0].clientWidth, element[0].clientHeight];\n        },\n        function (value) {\n          cropHost.setMaxDimensions(value[0],value[1]);\n          updateResultImage(scope);\n        },\n        true\n      );\n\n      // Destroy CropHost Instance when the directive is destroying\n      scope.$on('$destroy', function(){\n          cropHost.destroy();\n      });\n    }\n  };\n}]);\n}());"
  },
  {
    "path": "demo/src/main/webapp/js/upload.js",
    "content": "'use strict';\n\n\nvar app = angular.module('fileUpload', ['ngFileUpload']);\nvar version = '11.1.3';\n\napp.controller('MyCtrl', ['$scope', '$http', '$timeout', '$compile', 'Upload', function ($scope, $http, $timeout, $compile, Upload) {\n  $scope.usingFlash = FileAPI && FileAPI.upload != null;\n  //Upload.setDefaults({ngfKeep: true, ngfPattern:'image/*'});\n  $scope.changeAngularVersion = function () {\n    window.location.hash = $scope.angularVersion;\n    window.location.reload(true);\n  };\n  $scope.angularVersion = window.location.hash.length > 1 ? (window.location.hash.indexOf('/') === 1 ?\n    window.location.hash.substring(2) : window.location.hash.substring(1)) : '1.2.24';\n\n  $scope.invalidFiles = [];\n\n  // make invalidFiles array for not multiple to be able to be used in ng-repeat in the ui\n  $scope.$watch('invalidFiles', function (invalidFiles) {\n    if (invalidFiles != null && !angular.isArray(invalidFiles)) {\n      $timeout(function () {$scope.invalidFiles = [invalidFiles];});\n    }\n  });\n\n  $scope.$watch('files', function (files) {\n    $scope.formUpload = false;\n    if (files != null) {\n      // make files array for not multiple to be able to be used in ng-repeat in the ui\n      if (!angular.isArray(files)) {\n        $timeout(function () {\n          $scope.files = files = [files];\n        });\n        return;\n      }\n      for (var i = 0; i < files.length; i++) {\n        $scope.errorMsg = null;\n        (function (f) {\n          $scope.upload(f, true);\n        })(files[i]);\n      }\n    }\n  });\n\n  $scope.uploadPic = function (file) {\n    $scope.formUpload = true;\n    if (file != null) {\n      $scope.upload(file);\n    }\n  };\n\n  $scope.upload = function (file, resumable) {\n    $scope.errorMsg = null;\n    if ($scope.howToSend === 1) {\n      uploadUsingUpload(file, resumable);\n    } else if ($scope.howToSend == 2) {\n      uploadUsing$http(file);\n    } else {\n      uploadS3(file);\n    }\n  };\n\n  $scope.isResumeSupported = Upload.isResumeSupported();\n\n  $scope.restart = function (file) {\n    if (Upload.isResumeSupported()) {\n      $http.get('https://angular-file-upload-cors-srv.appspot.com/upload?restart=true&name=' + encodeURIComponent(file.name)).then(function () {\n        $scope.upload(file, true);\n      });\n    } else {\n      $scope.upload(file);\n    }\n  };\n\n  $scope.chunkSize = 100000;\n  function uploadUsingUpload(file, resumable) {\n    file.upload = Upload.upload({\n      url: 'https://angular-file-upload-cors-srv.appspot.com/upload' + $scope.getReqParams(),\n      resumeSizeUrl: resumable ? 'https://angular-file-upload-cors-srv.appspot.com/upload?name=' + encodeURIComponent(file.name) : null,\n      resumeChunkSize: resumable ? $scope.chunkSize : null,\n      headers: {\n        'optional-header': 'header-value'\n      },\n      data: {username: $scope.username, file: file}\n    });\n\n    file.upload.then(function (response) {\n      $timeout(function () {\n        file.result = response.data;\n      });\n    }, function (response) {\n      if (response.status > 0)\n        $scope.errorMsg = response.status + ': ' + response.data;\n    }, function (evt) {\n      // Math.min is to fix IE which reports 200% sometimes\n      file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));\n    });\n\n    file.upload.xhr(function (xhr) {\n      // xhr.upload.addEventListener('abort', function(){console.log('abort complete')}, false);\n    });\n  }\n\n  function uploadUsing$http(file) {\n    file.upload = Upload.http({\n      url: 'https://angular-file-upload-cors-srv.appspot.com/upload' + $scope.getReqParams(),\n      method: 'POST',\n      headers: {\n        'Content-Type': file.type\n      },\n      data: file\n    });\n\n    file.upload.then(function (response) {\n      file.result = response.data;\n    }, function (response) {\n      if (response.status > 0)\n        $scope.errorMsg = response.status + ': ' + response.data;\n    });\n\n    file.upload.progress(function (evt) {\n      file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));\n    });\n  }\n\n  function uploadS3(file) {\n    file.upload = Upload.upload({\n      url: $scope.s3url,\n      method: 'POST',\n      data: {\n        key: file.name,\n        AWSAccessKeyId: $scope.AWSAccessKeyId,\n        acl: $scope.acl,\n        policy: $scope.policy,\n        signature: $scope.signature,\n        'Content-Type': file.type === null || file.type === '' ? 'application/octet-stream' : file.type,\n        filename: file.name,\n        file: file\n      }\n    });\n\n    file.upload.then(function (response) {\n      $timeout(function () {\n        file.result = response.data;\n      });\n    }, function (response) {\n      if (response.status > 0)\n        $scope.errorMsg = response.status + ': ' + response.data;\n    });\n\n    file.upload.progress(function (evt) {\n      file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));\n    });\n    storeS3UploadConfigInLocalStore();\n  }\n\n  $scope.generateSignature = function () {\n    $http.post('/s3sign?aws-secret-key=' + encodeURIComponent($scope.AWSSecretKey), $scope.jsonPolicy).success(function (data) {\n      $scope.policy = data.policy;\n      $scope.signature = data.signature;\n    });\n  };\n\n  if (localStorage) {\n    $scope.s3url = localStorage.getItem('s3url');\n    $scope.AWSAccessKeyId = localStorage.getItem('AWSAccessKeyId');\n    $scope.acl = localStorage.getItem('acl');\n    $scope.success_action_redirect = localStorage.getItem('success_action_redirect');\n    $scope.policy = localStorage.getItem('policy');\n    $scope.signature = localStorage.getItem('signature');\n  }\n\n  $scope.success_action_redirect = $scope.success_action_redirect || window.location.protocol + '//' + window.location.host;\n  $scope.jsonPolicy = $scope.jsonPolicy || '{\\n  \"expiration\": \"2020-01-01T00:00:00Z\",\\n  \"conditions\": [\\n    {\"bucket\": \"angular-file-upload\"},\\n    [\"starts-with\", \"$key\", \"\"],\\n    {\"acl\": \"private\"},\\n    [\"starts-with\", \"$Content-Type\", \"\"],\\n    [\"starts-with\", \"$filename\", \"\"],\\n    [\"content-length-range\", 0, 524288000]\\n  ]\\n}';\n  $scope.acl = $scope.acl || 'private';\n\n  function storeS3UploadConfigInLocalStore() {\n    if ($scope.howToSend === 3 && localStorage) {\n      localStorage.setItem('s3url', $scope.s3url);\n      localStorage.setItem('AWSAccessKeyId', $scope.AWSAccessKeyId);\n      localStorage.setItem('acl', $scope.acl);\n      localStorage.setItem('success_action_redirect', $scope.success_action_redirect);\n      localStorage.setItem('policy', $scope.policy);\n      localStorage.setItem('signature', $scope.signature);\n    }\n  }\n\n  (function handleDynamicEditingOfScriptsAndHtml($scope) {\n    $scope.defaultHtml = document.getElementById('editArea').innerHTML.replace(/\\t\\t\\t\\t/g, '').replace(/&amp;/g, '&');\n\n    var fromLocal = (localStorage && localStorage.getItem('editHtml' + version));\n    $scope.editHtml = fromLocal || $scope.defaultHtml;\n    function htmlEdit() {\n      document.getElementById('editArea').innerHTML = $scope.editHtml;\n      $compile(document.getElementById('editArea'))($scope);\n      $scope.editHtml && localStorage && localStorage.setItem('editHtml' + version, $scope.editHtml);\n      if ($scope.editHtml != $scope.htmlEditor.getValue()) $scope.htmlEditor.setValue($scope.editHtml);\n    }\n\n    $scope.$watch('editHtml', htmlEdit);\n\n    $scope.htmlEditor = CodeMirror(document.getElementById('htmlEdit'), {\n      lineNumbers: true, indentUnit: 4,\n      mode: 'htmlmixed'\n    });\n    $scope.htmlEditor.on('change', function () {\n      if ($scope.editHtml != $scope.htmlEditor.getValue()) {\n        $scope.editHtml = $scope.htmlEditor.getValue();\n        htmlEdit();\n      }\n    });\n  })($scope, $http);\n\n  $scope.confirm = function () {\n    return confirm('Are you sure? Your local changes will be lost.');\n  };\n\n  $scope.getReqParams = function () {\n    return $scope.generateErrorOnServer ? '?errorCode=' + $scope.serverErrorCode +\n    '&errorMessage=' + $scope.serverErrorMsg : '';\n  };\n\n  angular.element(window).bind('dragover', function (e) {\n    e.preventDefault();\n  });\n  angular.element(window).bind('drop', function (e) {\n    e.preventDefault();\n  });\n\n  $scope.modelOptionsObj = {};\n  $scope.$watch('validate+dragOverClass+modelOptions+resize+resizeIf', function (v) {\n    $scope.validateObj = eval('(function(){return ' + $scope.validate + ';})()');\n    $scope.dragOverClassObj = eval('(function(){return ' + $scope.dragOverClass + ';})()');\n    $scope.modelOptionsObj = eval('(function(){return ' + $scope.modelOptions + ';})()');\n    $scope.resizeObj = eval('(function($file){return ' + $scope.resize + ';})()');\n    $scope.resizeIfFn = eval('(function(){var fn = function($file, $width, $height){return ' + $scope.resizeIf + ';};return fn;})()');\n  });\n\n  $timeout(function () {\n    $scope.capture = localStorage.getItem('capture' + version) || 'camera';\n    $scope.pattern = localStorage.getItem('pattern' + version) || 'image/*,audio/*,video/*';\n    $scope.acceptSelect = localStorage.getItem('acceptSelect' + version) || 'image/*,audio/*,video/*';\n    $scope.modelOptions = localStorage.getItem('modelOptions' + version) || '{debounce:100}';\n    $scope.dragOverClass = localStorage.getItem('dragOverClass' + version) || '{accept:\\'dragover\\', reject:\\'dragover-err\\', pattern:\\'image/*,audio/*,video/*,text/*\\'}';\n    $scope.disabled = localStorage.getItem('disabled' + version) == 'true' || false;\n    $scope.multiple = localStorage.getItem('multiple' + version) == 'true' || false;\n    $scope.allowDir = localStorage.getItem('allowDir' + version) == 'true' || true;\n    $scope.validate = localStorage.getItem('validate' + version) || '{size: {max: \\'20MB\\', min: \\'10B\\'}, height: {max: 12000}, width: {max: 12000}, duration: {max: \\'5m\\'}}';\n    $scope.keep = localStorage.getItem('keep' + version) == 'true' || false;\n    $scope.keepDistinct = localStorage.getItem('keepDistinct' + version) == 'true' || false;\n    $scope.orientation = localStorage.getItem('orientation' + version) == 'true' || false;\n    $scope.runAllValidations = localStorage.getItem('runAllValidations' + version) == 'true' || false;\n    $scope.resize = localStorage.getItem('resize' + version) || \"{width: 1000, height: 1000, centerCrop: true}\";\n    $scope.resizeIf = localStorage.getItem('resizeIf' + version) || \"$width > 5000 || $height > 5000\";\n    $scope.dimensions = localStorage.getItem('dimensions' + version) || \"$width < 12000 || $height < 12000\";\n    $scope.duration = localStorage.getItem('duration' + version) || \"$duration < 10000\";\n    $scope.maxFiles = localStorage.getItem('maxFiles' + version) || \"500\";\n    $scope.ignoreInvalid = localStorage.getItem('ignoreInvalid' + version) || \"\";\n    $scope.$watch('validate+capture+pattern+acceptSelect+disabled+capture+multiple+allowDir+keep+orientation+' +\n      'keepDistinct+modelOptions+dragOverClass+resize+resizeIf+maxFiles+duration+dimensions+ignoreInvalid+runAllValidations', function () {\n      localStorage.setItem('capture' + version, $scope.capture);\n      localStorage.setItem('pattern' + version, $scope.pattern);\n      localStorage.setItem('acceptSelect' + version, $scope.acceptSelect);\n      localStorage.setItem('disabled' + version, $scope.disabled);\n      localStorage.setItem('multiple' + version, $scope.multiple);\n      localStorage.setItem('allowDir' + version, $scope.allowDir);\n      localStorage.setItem('validate' + version, $scope.validate);\n      localStorage.setItem('keep' + version, $scope.keep);\n      localStorage.setItem('orientation' + version, $scope.orientation);\n      localStorage.setItem('keepDistinct' + version, $scope.keepDistinct);\n      localStorage.setItem('dragOverClass' + version, $scope.dragOverClass);\n      localStorage.setItem('modelOptions' + version, $scope.modelOptions);\n      localStorage.setItem('resize' + version, $scope.resize);\n      localStorage.setItem('resizeIf' + version, $scope.resizeIf);\n      localStorage.setItem('dimensions' + version, $scope.dimensions);\n      localStorage.setItem('duration' + version, $scope.duration);\n      localStorage.setItem('maxFiles' + version, $scope.maxFiles);\n      localStorage.setItem('ignoreInvalid' + version, $scope.ignoreInvalid);\n      localStorage.setItem('runAllValidations' + version, $scope.runAllValidations);\n    });\n  });\n}]);\n"
  },
  {
    "path": "dist/FileAPI.js",
    "content": "/*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git\n * 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.\n */\n\n/*\n * JavaScript Canvas to Blob 2.0.5\n * https://github.com/blueimp/JavaScript-Canvas-to-Blob\n *\n * Copyright 2012, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n *\n * Based on stackoverflow user Stoive's code snippet:\n * http://stackoverflow.com/q/4998908\n */\n\n/*jslint nomen: true, regexp: true */\n/*global window, atob, Blob, ArrayBuffer, Uint8Array */\n\n(function (window) {\n    'use strict';\n    var CanvasPrototype = window.HTMLCanvasElement &&\n            window.HTMLCanvasElement.prototype,\n        hasBlobConstructor = window.Blob && (function () {\n            try {\n                return Boolean(new Blob());\n            } catch (e) {\n                return false;\n            }\n        }()),\n        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&\n            (function () {\n                try {\n                    return new Blob([new Uint8Array(100)]).size === 100;\n                } catch (e) {\n                    return false;\n                }\n            }()),\n        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||\n            window.MozBlobBuilder || window.MSBlobBuilder,\n        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&\n            window.ArrayBuffer && window.Uint8Array && function (dataURI) {\n                var byteString,\n                    arrayBuffer,\n                    intArray,\n                    i,\n                    mimeString,\n                    bb;\n                if (dataURI.split(',')[0].indexOf('base64') >= 0) {\n                    // Convert base64 to raw binary data held in a string:\n                    byteString = atob(dataURI.split(',')[1]);\n                } else {\n                    // Convert base64/URLEncoded data component to raw binary data:\n                    byteString = decodeURIComponent(dataURI.split(',')[1]);\n                }\n                // Write the bytes of the string to an ArrayBuffer:\n                arrayBuffer = new ArrayBuffer(byteString.length);\n                intArray = new Uint8Array(arrayBuffer);\n                for (i = 0; i < byteString.length; i += 1) {\n                    intArray[i] = byteString.charCodeAt(i);\n                }\n                // Separate out the mime component:\n                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];\n                // Write the ArrayBuffer (or ArrayBufferView) to a blob:\n                if (hasBlobConstructor) {\n                    return new Blob(\n                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],\n                        {type: mimeString}\n                    );\n                }\n                bb = new BlobBuilder();\n                bb.append(arrayBuffer);\n                return bb.getBlob(mimeString);\n            };\n    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {\n        if (CanvasPrototype.mozGetAsFile) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {\n                    callback(dataURLtoBlob(this.toDataURL(type, quality)));\n                } else {\n                    callback(this.mozGetAsFile('blob', type));\n                }\n            };\n        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                callback(dataURLtoBlob(this.toDataURL(type, quality)));\n            };\n        }\n    }\n    window.dataURLtoBlob = dataURLtoBlob;\n})(window);\n\n/*jslint evil: true */\n/*global window, URL, webkitURL, ActiveXObject */\n\n(function (window, undef){\n\t'use strict';\n\n\tvar\n\t\tgid = 1,\n\t\tnoop = function (){},\n\n\t\tdocument = window.document,\n\t\tdoctype = document.doctype || {},\n\t\tuserAgent = window.navigator.userAgent,\n\n\t\t// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48\n\t\tapiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),\n\n\t\tBlob = window.Blob,\n\t\tFile = window.File,\n\t\tFileReader = window.FileReader,\n\t\tFormData = window.FormData,\n\n\n\t\tXMLHttpRequest = window.XMLHttpRequest,\n\t\tjQuery = window.jQuery,\n\n\t\thtml5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))\n\t\t\t\t&& !(/safari\\//i.test(userAgent) && !/chrome\\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25\n\n\t\tcors = html5 && ('withCredentials' in (new XMLHttpRequest)),\n\n\t\tchunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),\n\n\t\t// https://github.com/blueimp/JavaScript-Canvas-to-Blob\n\t\tdataURLtoBlob = window.dataURLtoBlob,\n\n\n\t\t_rimg = /img/i,\n\t\t_rcanvas = /canvas/i,\n\t\t_rimgcanvas = /img|canvas/i,\n\t\t_rinput = /input/i,\n\t\t_rdata = /^data:[^,]+,/,\n\n\t\t_toString = {}.toString,\n\n\n\t\tMath = window.Math,\n\n\t\t_SIZE_CONST = function (pow){\n\t\t\tpow = new window.Number(Math.pow(1024, pow));\n\t\t\tpow.from = function (sz){ return Math.round(sz * this); };\n\t\t\treturn\tpow;\n\t\t},\n\n\t\t_elEvents = {}, // element event listeners\n\t\t_infoReader = [], // list of file info processors\n\n\t\t_readerEvents = 'abort progress error load loadend',\n\t\t_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),\n\n\t\tcurrentTarget = 'currentTarget', // for minimize\n\t\tpreventDefault = 'preventDefault', // and this too\n\n\t\t_isArray = function (ar) {\n\t\t\treturn\tar && ('length' in ar);\n\t\t},\n\n\t\t/**\n\t\t * Iterate over a object or array\n\t\t */\n\t\t_each = function (obj, fn, ctx){\n\t\t\tif( obj ){\n\t\t\t\tif( _isArray(obj) ){\n\t\t\t\t\tfor( var i = 0, n = obj.length; i < n; i++ ){\n\t\t\t\t\t\tif( i in obj ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[i], i, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfor( var key in obj ){\n\t\t\t\t\t\tif( obj.hasOwnProperty(key) ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[key], key, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Merge the contents of two or more objects together into the first object\n\t\t */\n\t\t_extend = function (dst){\n\t\t\tvar args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };\n\t\t\tfor( ; i < args.length; i++ ){\n\t\t\t\t_each(args[i], _ext);\n\t\t\t}\n\t\t\treturn  dst;\n\t\t},\n\n\t\t/**\n\t\t * Add event listener\n\t\t */\n\t\t_on = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el);\n\n\t\t\t\tif( !_elEvents[uid] ){\n\t\t\t\t\t_elEvents[uid] = {};\n\t\t\t\t}\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.add(el, type, fn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif( !_elEvents[uid][type] ){\n\t\t\t\t\t\t\t_elEvents[uid][type] = [];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t_elEvents[uid][type].push(fn);\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.addEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.attachEvent ){ el.attachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = fn; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t/**\n\t\t * Remove event listener\n\t\t */\n\t\t_off = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el), events = _elEvents[uid] || {};\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.remove(el, type, fn);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar fns = events[type] || [], i = fns.length;\n\n\t\t\t\t\t\twhile( i-- ){\n\t\t\t\t\t\t\tif( fns[i] === fn ){\n\t\t\t\t\t\t\t\tfns.splice(i, 1);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.removeEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.detachEvent ){ el.detachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = null; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t_one = function(el, type, fn){\n\t\t\t_on(el, type, function _(evt){\n\t\t\t\t_off(el, type, _);\n\t\t\t\tfn(evt);\n\t\t\t});\n\t\t},\n\n\n\t\t_fixEvent = function (evt){\n\t\t\tif( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }\n\t\t\tif( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }\n\t\t\treturn  evt;\n\t\t},\n\n\n\t\t_supportInputAttr = function (attr){\n\t\t\tvar input = document.createElement('input');\n\t\t\tinput.setAttribute('type', \"file\");\n\t\t\treturn attr in input;\n\t\t},\n\n\t\t/**\n\t\t * FileAPI (core object)\n\t\t */\n\t\tapi = {\n\t\t\tversion: '2.0.7',\n\n\t\t\tcors: false,\n\t\t\thtml5: true,\n\t\t\tmedia: false,\n\t\t\tformData: true,\n\t\t\tmultiPassResize: true,\n\n\t\t\tdebug: false,\n\t\t\tpingUrl: false,\n\t\t\tmultiFlash: false,\n\t\t\tflashAbortTimeout: 0,\n\t\t\twithCredentials: true,\n\n\t\t\tstaticPath: './dist/',\n\n\t\t\tflashUrl: 0, // @default: './FileAPI.flash.swf'\n\t\t\tflashImageUrl: 0, // @default: './FileAPI.flash.image.swf'\n\n\t\t\tpostNameConcat: function (name, idx){\n\t\t\t\treturn\tname + (idx != null ? '['+ idx +']' : '');\n\t\t\t},\n\n\t\t\text2mime: {\n\t\t\t\t  jpg:\t'image/jpeg'\n\t\t\t\t, tif:\t'image/tiff'\n\t\t\t\t, txt:\t'text/plain'\n\t\t\t},\n\n\t\t\t// Fallback for flash\n\t\t\taccept: {\n\t\t\t\t  'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'\n\t\t\t\t, 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'\n\t\t\t\t, 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'\n\t\t\t},\n\n\t\t\tuploadRetry : 0,\n\t\t\tnetworkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down\n\n\t\t\tchunkSize : 0,\n\t\t\tchunkUploadRetry : 0,\n\t\t\tchunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down\n\n\t\t\tKB: _SIZE_CONST(1),\n\t\t\tMB: _SIZE_CONST(2),\n\t\t\tGB: _SIZE_CONST(3),\n\t\t\tTB: _SIZE_CONST(4),\n\n\t\t\tEMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',\n\n\t\t\texpando: 'fileapi' + (new Date).getTime(),\n\n\t\t\tuid: function (obj){\n\t\t\t\treturn\tobj\n\t\t\t\t\t? (obj[api.expando] = obj[api.expando] || api.uid())\n\t\t\t\t\t: (++gid, api.expando + gid)\n\t\t\t\t;\n\t\t\t},\n\n\t\t\tlog: function (){\n\t\t\t\t// ngf fix for IE8 #1071\n\t\t\t\tif( api.debug && api._supportConsoleLog ){\n\t\t\t\t\tif( api._supportConsoleLogApply ){\n\t\t\t\t\t\tconsole.log.apply(console, arguments);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tconsole.log([].join.call(arguments, ' '));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Create new image\n\t\t\t *\n\t\t\t * @param {String} [src]\n\t\t\t * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element\n\t\t\t * @returns {HTMLElement}\n\t\t\t */\n\t\t\tnewImage: function (src, fn){\n\t\t\t\tvar img = document.createElement('img');\n\t\t\t\tif( fn ){\n\t\t\t\t\tapi.event.one(img, 'error load', function (evt){\n\t\t\t\t\t\tfn(evt.type == 'error', img);\n\t\t\t\t\t\timg = null;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\timg.src = src;\n\t\t\t\treturn\timg;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Get XHR\n\t\t\t * @returns {XMLHttpRequest}\n\t\t\t */\n\t\t\tgetXHR: function (){\n\t\t\t\tvar xhr;\n\n\t\t\t\tif( XMLHttpRequest ){\n\t\t\t\t\txhr = new XMLHttpRequest;\n\t\t\t\t}\n\t\t\t\telse if( window.ActiveXObject ){\n\t\t\t\t\ttry {\n\t\t\t\t\t\txhr = new ActiveXObject('MSXML2.XMLHttp.3.0');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\txhr = new ActiveXObject('Microsoft.XMLHTTP');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tisArray: _isArray,\n\n\t\t\tsupport: {\n\t\t\t\tdnd:     cors && ('ondrop' in document.createElement('div')),\n\t\t\t\tcors:    cors,\n\t\t\t\thtml5:   html5,\n\t\t\t\tchunked: chunked,\n\t\t\t\tdataURI: true,\n\t\t\t\taccept:   _supportInputAttr('accept'),\n\t\t\t\tmultiple: _supportInputAttr('multiple')\n\t\t\t},\n\n\t\t\tevent: {\n\t\t\t\t  on: _on\n\t\t\t\t, off: _off\n\t\t\t\t, one: _one\n\t\t\t\t, fix: _fixEvent\n\t\t\t},\n\n\n\t\t\tthrottle: function(fn, delay) {\n\t\t\t\tvar id, args;\n\n\t\t\t\treturn function _throttle(){\n\t\t\t\t\targs = arguments;\n\n\t\t\t\t\tif( !id ){\n\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\tid = setTimeout(function (){\n\t\t\t\t\t\t\tid = 0;\n\t\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\t}, delay);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\n\t\t\tF: function (){},\n\n\n\t\t\tparseJSON: function (str){\n\t\t\t\tvar json;\n\t\t\t\tif( window.JSON && JSON.parse ){\n\t\t\t\t\tjson = JSON.parse(str);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tjson = (new Function('return ('+str.replace(/([\\r\\n])/g, '\\\\$1')+');'))();\n\t\t\t\t}\n\t\t\t\treturn json;\n\t\t\t},\n\n\n\t\t\ttrim: function (str){\n\t\t\t\tstr = String(str);\n\t\t\t\treturn\tstr.trim ? str.trim() : str.replace(/^\\s+|\\s+$/g, '');\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Simple Defer\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\tdefer: function (){\n\t\t\t\tvar\n\t\t\t\t\t  list = []\n\t\t\t\t\t, result\n\t\t\t\t\t, error\n\t\t\t\t\t, defer = {\n\t\t\t\t\t\tresolve: function (err, res){\n\t\t\t\t\t\t\tdefer.resolve = noop;\n\t\t\t\t\t\t\terror\t= err || false;\n\t\t\t\t\t\t\tresult\t= res;\n\n\t\t\t\t\t\t\twhile( res = list.shift() ){\n\t\t\t\t\t\t\t\tres(error, result);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tthen: function (fn){\n\t\t\t\t\t\t\tif( error !== undef ){\n\t\t\t\t\t\t\t\tfn(error, result);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlist.push(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\treturn\tdefer;\n\t\t\t},\n\n\t\t\tqueue: function (fn){\n\t\t\t\tvar\n\t\t\t\t\t  _idx = 0\n\t\t\t\t\t, _length = 0\n\t\t\t\t\t, _fail = false\n\t\t\t\t\t, _end = false\n\t\t\t\t\t, queue = {\n\t\t\t\t\t\tinc: function (){\n\t\t\t\t\t\t\t_length++;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tnext: function (){\n\t\t\t\t\t\t\t_idx++;\n\t\t\t\t\t\t\tsetTimeout(queue.check, 0);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tcheck: function (){\n\t\t\t\t\t\t\t(_idx >= _length) && !_fail && queue.end();\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tisFail: function (){\n\t\t\t\t\t\t\treturn _fail;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tfail: function (){\n\t\t\t\t\t\t\t!_fail && fn(_fail = true);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tend: function (){\n\t\t\t\t\t\t\tif( !_end ){\n\t\t\t\t\t\t\t\t_end = true;\n\t\t\t\t\t\t\t\tfn();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t;\n\t\t\t\treturn queue;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * For each object\n\t\t\t *\n\t\t\t * @param\t{Object|Array}\tobj\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{*}\t\t\t\t[ctx]\n\t\t\t */\n\t\t\teach: _each,\n\n\n\t\t\t/**\n\t\t\t * Async for\n\t\t\t * @param {Array} array\n\t\t\t * @param {Function} callback\n\t\t\t */\n\t\t\tafor: function (array, callback){\n\t\t\t\tvar i = 0, n = array.length;\n\n\t\t\t\tif( _isArray(array) && n-- ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tcallback(n != i && _next, array[i], i++);\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Merge the contents of two or more objects together into the first object\n\t\t\t *\n\t\t\t * @param\t{Object}\tdst\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\textend: _extend,\n\n\n\t\t\t/**\n\t\t\t * Is file?\n\t\t\t * @param  {File}  file\n\t\t\t * @return {Boolean}\n\t\t\t */\n\t\t\tisFile: function (file){\n\t\t\t\treturn _toString.call(file) === '[object File]';\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is blob?\n\t\t\t * @param   {Blob}  blob\n\t\t\t * @returns {Boolean}\n\t\t\t */\n\t\t\tisBlob: function (blob) {\n\t\t\t\treturn this.isFile(blob) || (_toString.call(blob) === '[object Blob]');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is canvas element\n\t\t\t *\n\t\t\t * @param\t{HTMLElement}\tel\n\t\t\t * @return\t{Boolean}\n\t\t\t */\n\t\t\tisCanvas: function (el){\n\t\t\t\treturn\tel && _rcanvas.test(el.nodeName);\n\t\t\t},\n\n\n\t\t\tgetFilesFilter: function (filter){\n\t\t\t\tfilter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');\n\t\t\t\treturn\tfilter ? new RegExp('('+ filter.replace(/\\./g, '\\\\.').replace(/,/g, '|') +')$', 'i') : /./;\n\t\t\t},\n\n\n\n\t\t\t/**\n\t\t\t * Read as DataURL\n\t\t\t *\n\t\t\t * @param {File|Element} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsDataURL: function (file, fn){\n\t\t\t\tif( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', api.toDataURL(file));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_readAs(file, fn, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as Binary string\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsBinaryString: function (file, fn){\n\t\t\t\tif( _hasSupportReadAs('BinaryString') ){\n\t\t\t\t\t_readAs(file, fn, 'BinaryString');\n\t\t\t\t} else {\n\t\t\t\t\t// Hello IE10!\n\t\t\t\t\t_readAs(file, function (evt){\n\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// dataURL -> binaryString\n\t\t\t\t\t\t\t\tevt.result = api.toBinaryString(evt.result);\n\t\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\tevt.message = e.toString();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfn(evt);\n\t\t\t\t\t}, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as ArrayBuffer\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsArrayBuffer: function(file, fn){\n\t\t\t\t_readAs(file, fn, 'ArrayBuffer');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as text\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {String} encoding\n\t\t\t * @param {Function} [fn]\n\t\t\t */\n\t\t\treadAsText: function(file, encoding, fn){\n\t\t\t\tif( !fn ){\n\t\t\t\t\tfn\t= encoding;\n\t\t\t\t\tencoding = 'utf-8';\n\t\t\t\t}\n\n\t\t\t\t_readAs(file, fn, 'Text', encoding);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Convert image or canvas to DataURL\n\t\t\t *\n\t\t\t * @param   {Element}  el      Image or Canvas element\n\t\t\t * @param   {String}   [type]  mime-type\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoDataURL: function (el, type){\n\t\t\t\tif( typeof el == 'string' ){\n\t\t\t\t\treturn  el;\n\t\t\t\t}\n\t\t\t\telse if( el.toDataURL ){\n\t\t\t\t\treturn  el.toDataURL(type || 'image/png');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Canvert string, image or canvas to binary string\n\t\t\t *\n\t\t\t * @param   {String|Element} val\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoBinaryString: function (val){\n\t\t\t\treturn  window.atob(api.toDataURL(val).replace(_rdata, ''));\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read file or DataURL as ImageElement\n\t\t\t *\n\t\t\t * @param\t{File|String}\tfile\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{Boolean}\t\t[progress]\n\t\t\t */\n\t\t\treadAsImage: function (file, fn, progress){\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\tif( apiURL ){\n\t\t\t\t\t\t/** @namespace apiURL.createObjectURL */\n\t\t\t\t\t\tvar data = apiURL.createObjectURL(file);\n\t\t\t\t\t\tif( data === undef ){\n\t\t\t\t\t\t\t_emit(file, fn, 'error');\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tapi.readAsImage(data, fn, progress);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\tapi.readAsImage(evt.result, fn, progress);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( progress || evt.type == 'error' ){\n\t\t\t\t\t\t\t\t_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t}\n\t\t\t\telse if( _rimg.test(file.nodeName) ){\n\t\t\t\t\tif( file.complete ){\n\t\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar events = 'error abort load';\n\t\t\t\t\t\t_one(file, events, function _fn(evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' && apiURL ){\n\t\t\t\t\t\t\t\t/** @namespace apiURL.revokeObjectURL */\n\t\t\t\t\t\t\t\tapiURL.revokeObjectURL(file.src);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_off(file, events, _fn);\n\t\t\t\t\t\t\t_emit(file, fn, evt, file);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( file.iframe ){\n\t\t\t\t\t_emit(file, fn, { type: 'error' });\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Created image\n\t\t\t\t\tvar img = api.newImage(file.dataURL || file);\n\t\t\t\t\tapi.readAsImage(img, fn, progress);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Make file by name\n\t\t\t *\n\t\t\t * @param\t{String}\tname\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tcheckFileObj: function (name){\n\t\t\t\tvar file = {}, accept = api.accept;\n\n\t\t\t\tif( typeof name == 'object' ){\n\t\t\t\t\tfile = name;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfile.name = (name + '').split(/\\\\|\\//g).pop();\n\t\t\t\t}\n\n\t\t\t\tif( file.type == null ){\n\t\t\t\t\tfile.type = file.name.split('.').pop();\n\t\t\t\t}\n\n\t\t\t\t_each(accept, function (ext, type){\n\t\t\t\t\text = new RegExp(ext.replace(/\\s/g, '|'), 'i');\n\t\t\t\t\tif( ext.test(file.type) || api.ext2mime[file.type] ){\n\t\t\t\t\t\tfile.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn\tfile;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get drop files\n\t\t\t *\n\t\t\t * @param\t{Event}\tevt\n\t\t\t * @param\t{Function} callback\n\t\t\t */\n\t\t\tgetDropFiles: function (evt, callback){\n\t\t\t\tvar\n\t\t\t\t\t  files = []\n\t\t\t\t\t, dataTransfer = _getDataTransfer(evt)\n\t\t\t\t\t, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])\n\t\t\t\t\t, queue = api.queue(function (){ callback(files); })\n\t\t\t\t;\n\n\t\t\t\t_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){\n\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( entrySupport ){\n\t\t\t\t\t\t\t_readEntryAsFiles(item, function (err, entryFiles){\n\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\tapi.log('[err] getDropFiles:', err);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfiles.push.apply(files, entryFiles);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t_isRegularFile(item, function (yes){\n\t\t\t\t\t\t\t\tyes && files.push(item);\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcatch( err ){\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\tapi.log('[err] getDropFiles: ', err);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get file list\n\t\t\t *\n\t\t\t * @param\t{HTMLInputElement|Event}\tinput\n\t\t\t * @param\t{String|Function}\t[filter]\n\t\t\t * @param\t{Function}\t\t\t[callback]\n\t\t\t * @return\t{Array|Null}\n\t\t\t */\n\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\tvar files = [];\n\n\t\t\t\tif( callback ){\n\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tif( input.jquery ){\n\t\t\t\t\t// jQuery object\n\t\t\t\t\tinput.each(function (){\n\t\t\t\t\t\tfiles = files.concat(api.getFiles(this));\n\t\t\t\t\t});\n\t\t\t\t\tinput\t= files;\n\t\t\t\t\tfiles\t= [];\n\t\t\t\t}\n\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t}\n\n\t\t\t\tif( input.originalEvent ){\n\t\t\t\t\t// jQuery event\n\t\t\t\t\tinput = _fixEvent(input.originalEvent);\n\t\t\t\t}\n\t\t\t\telse if( input.srcElement ){\n\t\t\t\t\t// IE Event\n\t\t\t\t\tinput = _fixEvent(input);\n\t\t\t\t}\n\n\n\t\t\t\tif( input.dataTransfer ){\n\t\t\t\t\t// Drag'n'Drop\n\t\t\t\t\tinput = input.dataTransfer;\n\t\t\t\t}\n\t\t\t\telse if( input.target ){\n\t\t\t\t\t// Event\n\t\t\t\t\tinput = input.target;\n\t\t\t\t}\n\n\t\t\t\tif( input.files ){\n\t\t\t\t\t// Input[type=\"file\"]\n\t\t\t\t\tfiles = input.files;\n\n\t\t\t\t\tif( !html5 ){\n\t\t\t\t\t\t// Partial support for file api\n\t\t\t\t\t\tfiles[0].blob\t= input;\n\t\t\t\t\t\tfiles[0].iframe\t= true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( !html5 && isInputFile(input) ){\n\t\t\t\t\tif( api.trim(input.value) ){\n\t\t\t\t\t\tfiles = [api.checkFileObj(input.value)];\n\t\t\t\t\t\tfiles[0].blob   = input;\n\t\t\t\t\t\tfiles[0].iframe = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( _isArray(input) ){\n\t\t\t\t\tfiles\t= input;\n\t\t\t\t}\n\n\t\t\t\treturn\tapi.filter(files, function (file){ return !filter || filter.test(file.name); });\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get total file size\n\t\t\t * @param\t{Array}\tfiles\n\t\t\t * @return\t{Number}\n\t\t\t */\n\t\t\tgetTotalSize: function (files){\n\t\t\t\tvar size = 0, i = files && files.length;\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tsize += files[i].size;\n\t\t\t\t}\n\t\t\t\treturn\tsize;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get image information\n\t\t\t *\n\t\t\t * @param\t{File}\t\tfile\n\t\t\t * @param\t{Function}\tfn\n\t\t\t */\n\t\t\tgetInfo: function (file, fn){\n\t\t\t\tvar info = {}, readers = _infoReader.concat();\n\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tvar reader = readers.shift();\n\t\t\t\t\t\tif( reader ){\n\t\t\t\t\t\t\tif( reader.test(file.type) ){\n\t\t\t\t\t\t\t\treader(file, function (err, res){\n\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t_extend(info, res);\n\t\t\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tfn(false, info);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfn('not_support_info', info);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Add information reader\n\t\t\t *\n\t\t\t * @param {RegExp} mime\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\taddInfoReader: function (mime, fn){\n\t\t\t\tfn.test = function (type){ return mime.test(type); };\n\t\t\t\t_infoReader.push(fn);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter of array\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tinput\n\t\t\t * @param\t{Function}\tfn\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tfilter: function (input, fn){\n\t\t\t\tvar result = [], i = 0, n = input.length, val;\n\n\t\t\t\tfor( ; i < n; i++ ){\n\t\t\t\t\tif( i in input ){\n\t\t\t\t\t\tval = input[i];\n\t\t\t\t\t\tif( fn.call(val, val, i, input) ){\n\t\t\t\t\t\t\tresult.push(val);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn\tresult;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter files\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tfiles\n\t\t\t * @param\t{Function}\teachFn\n\t\t\t * @param\t{Function}\tresultFn\n\t\t\t */\n\t\t\tfilterFiles: function (files, eachFn, resultFn){\n\t\t\t\tif( files.length ){\n\t\t\t\t\t// HTML5 or Flash\n\t\t\t\t\tvar queue = files.concat(), file, result = [], deleted = [];\n\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tif( queue.length ){\n\t\t\t\t\t\t\tfile = queue.shift();\n\t\t\t\t\t\t\tapi.getInfo(file, function (err, info){\n\t\t\t\t\t\t\t\t(eachFn(file, err ? false : info) ? result : deleted).push(file);\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresultFn(result, deleted);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tresultFn([], files);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\tupload: function (options){\n\t\t\t\toptions = _extend({\n\t\t\t\t\t  jsonp: 'callback'\n\t\t\t\t\t, prepare: api.F\n\t\t\t\t\t, beforeupload: api.F\n\t\t\t\t\t, upload: api.F\n\t\t\t\t\t, fileupload: api.F\n\t\t\t\t\t, fileprogress: api.F\n\t\t\t\t\t, filecomplete: api.F\n\t\t\t\t\t, progress: api.F\n\t\t\t\t\t, complete: api.F\n\t\t\t\t\t, pause: api.F\n\t\t\t\t\t, imageOriginal: true\n\t\t\t\t\t, chunkSize: api.chunkSize\n\t\t\t\t\t, chunkUploadRetry: api.chunkUploadRetry\n\t\t\t\t\t, uploadRetry: api.uploadRetry\n\t\t\t\t}, options);\n\n\n\t\t\t\tif( options.imageAutoOrientation && !options.imageTransform ){\n\t\t\t\t\toptions.imageTransform = { rotate: 'auto' };\n\t\t\t\t}\n\n\n\t\t\t\tvar\n\t\t\t\t\t  proxyXHR = new api.XHR(options)\n\t\t\t\t\t, dataArray = this._getFilesDataArray(options.files)\n\t\t\t\t\t, _this = this\n\t\t\t\t\t, _total = 0\n\t\t\t\t\t, _loaded = 0\n\t\t\t\t\t, _nextFile\n\t\t\t\t\t, _complete = false\n\t\t\t\t;\n\n\n\t\t\t\t// calc total size\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\t_total += data.size;\n\t\t\t\t});\n\n\t\t\t\t// Array of files\n\t\t\t\tproxyXHR.files = [];\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t});\n\n\t\t\t\t// Set upload status props\n\t\t\t\tproxyXHR.total\t= _total;\n\t\t\t\tproxyXHR.loaded\t= 0;\n\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t// emit \"beforeupload\"  event\n\t\t\t\toptions.beforeupload(proxyXHR, options);\n\n\t\t\t\t// Upload by file\n\t\t\t\t_nextFile = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  data = dataArray.shift()\n\t\t\t\t\t\t, _file = data && data.file\n\t\t\t\t\t\t, _fileLoaded = false\n\t\t\t\t\t\t, _fileOptions = _simpleClone(options)\n\t\t\t\t\t;\n\n\t\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t\tif( _file && _file.name === api.expando ){\n\t\t\t\t\t\t_file = null;\n\t\t\t\t\t\tapi.log('[warn] FileAPI.upload() — called without files');\n\t\t\t\t\t}\n\n\t\t\t\t\tif( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){\n\t\t\t\t\t\t// Mark active job\n\t\t\t\t\t\t_complete = false;\n\n\t\t\t\t\t\t// Set current upload file\n\t\t\t\t\t\tproxyXHR.currentFile = _file;\n\n\t\t\t\t\t\t// Prepare file options\n\t\t\t\t\t\tif (_file && options.prepare(_file, _fileOptions) === false) {\n\t\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_fileOptions.file = _file;\n\n\t\t\t\t\t\t_this._getFormData(_fileOptions, data, function (form){\n\t\t\t\t\t\t\tif( !_loaded ){\n\t\t\t\t\t\t\t\t// emit \"upload\" event\n\t\t\t\t\t\t\t\toptions.upload(proxyXHR, options);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar xhr = new api.XHR(_extend({}, _fileOptions, {\n\n\t\t\t\t\t\t\t\tupload: _file ? function (){\n\t\t\t\t\t\t\t\t\t// emit \"fileupload\" event\n\t\t\t\t\t\t\t\t\toptions.fileupload(_file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tprogress: _file ? function (evt){\n\t\t\t\t\t\t\t\t\tif( !_fileLoaded ){\n\t\t\t\t\t\t\t\t\t\t// For ignore the double calls.\n\t\t\t\t\t\t\t\t\t\t_fileLoaded = (evt.loaded === evt.total);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"fileprogress\" event\n\t\t\t\t\t\t\t\t\t\toptions.fileprogress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  data.total = evt.total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: data.loaded = evt.loaded\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"progress\" event\n\t\t\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  _total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tcomplete: function (err){\n\t\t\t\t\t\t\t\t\t_each(_xhrPropsExport, function (name){\n\t\t\t\t\t\t\t\t\t\tproxyXHR[name] = xhr[name];\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tif( _file ){\n\t\t\t\t\t\t\t\t\t\tdata.total = (data.total || data.size);\n\t\t\t\t\t\t\t\t\t\tdata.loaded\t= data.total;\n\n\t\t\t\t\t\t\t\t\t\tif( !err ) {\n\t\t\t\t\t\t\t\t\t\t\t// emulate 100% \"progress\"\n\t\t\t\t\t\t\t\t\t\t\tthis.progress(data);\n\n\t\t\t\t\t\t\t\t\t\t\t// fixed throttle event\n\t\t\t\t\t\t\t\t\t\t\t_fileLoaded = true;\n\n\t\t\t\t\t\t\t\t\t\t\t// bytes loaded\n\t\t\t\t\t\t\t\t\t\t\t_loaded += data.size; // data.size != data.total, it's desirable fix this\n\t\t\t\t\t\t\t\t\t\t\tproxyXHR.loaded = _loaded;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// emit \"filecomplete\" event\n\t\t\t\t\t\t\t\t\t\toptions.filecomplete(err, xhr, _file, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// upload next file\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {_nextFile.call(_this);}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})); // xhr\n\n\n\t\t\t\t\t\t\t// ...\n\t\t\t\t\t\t\tproxyXHR.abort = function (current){\n\t\t\t\t\t\t\t\tif (!current) { dataArray.length = 0; }\n\t\t\t\t\t\t\t\tthis.current = current;\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Start upload\n\t\t\t\t\t\t\txhr.send(form);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;\n\t\t\t\t\t\toptions.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);\n\t\t\t\t\t\t// Mark done state\n\t\t\t\t\t\t_complete = true;\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Next tick\n\t\t\t\tsetTimeout(_nextFile, 0);\n\n\n\t\t\t\t// Append more files to the existing request\n\t\t\t\t// first - add them to the queue head/tail\n\t\t\t\tproxyXHR.append = function (files, first) {\n\t\t\t\t\tfiles = api._getFilesDataArray([].concat(files));\n\n\t\t\t\t\t_each(files, function (data) {\n\t\t\t\t\t\t_total += data.size;\n\t\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\tdataArray.unshift(data);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdataArray.push(data);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tproxyXHR.statusText = \"\";\n\n\t\t\t\t\tif( _complete ){\n\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Removes file from queue by file reference and returns it\n\t\t\t\tproxyXHR.remove = function (file) {\n\t\t\t\t    var i = dataArray.length, _file;\n\t\t\t\t    while( i-- ){\n\t\t\t\t\t\tif( dataArray[i].file == file ){\n\t\t\t\t\t\t\t_file = dataArray.splice(i, 1);\n\t\t\t\t\t\t\t_total -= _file.size;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn\t_file;\n\t\t\t\t};\n\n\t\t\t\treturn proxyXHR;\n\t\t\t},\n\n\n\t\t\t_getFilesDataArray: function (data){\n\t\t\t\tvar files = [], oFiles = {};\n\n\t\t\t\tif( isInputFile(data) ){\n\t\t\t\t\tvar tmp = api.getFiles(data);\n\t\t\t\t\toFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];\n\t\t\t\t}\n\t\t\t\telse if( _isArray(data) && isInputFile(data[0]) ){\n\t\t\t\t\t_each(data, function (input){\n\t\t\t\t\t\toFiles[input.name || 'file'] = api.getFiles(input);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\toFiles = data;\n\t\t\t\t}\n\n\t\t\t\t_each(oFiles, function add(file, name){\n\t\t\t\t\tif( _isArray(file) ){\n\t\t\t\t\t\t_each(file, function (file){\n\t\t\t\t\t\t\tadd(file, name);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( file && (file.name || file.image) ){\n\t\t\t\t\t\tfiles.push({\n\t\t\t\t\t\t\t  name: name\n\t\t\t\t\t\t\t, file: file\n\t\t\t\t\t\t\t, size: file.size\n\t\t\t\t\t\t\t, total: file.size\n\t\t\t\t\t\t\t, loaded: 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif( !files.length ){\n\t\t\t\t\t// Create fake `file` object\n\t\t\t\t\tfiles.push({ file: { name: api.expando } });\n\t\t\t\t}\n\n\t\t\t\treturn\tfiles;\n\t\t\t},\n\n\n\t\t\t_getFormData: function (options, data, fn){\n\t\t\t\tvar\n\t\t\t\t\t  file = data.file\n\t\t\t\t\t, name = data.name\n\t\t\t\t\t, filename = file.name\n\t\t\t\t\t, filetype = file.type\n\t\t\t\t\t, trans = api.support.transform && options.imageTransform\n\t\t\t\t\t, Form = new api.Form\n\t\t\t\t\t, queue = api.queue(function (){ fn(Form); })\n\t\t\t\t\t, isOrignTrans = trans && _isOriginTransform(trans)\n\t\t\t\t\t, postNameConcat = api.postNameConcat\n\t\t\t\t;\n\n\t\t\t\t// Append data\n\t\t\t\t_each(options.data, function add(val, name){\n\t\t\t\t\tif( typeof val == 'object' ){\n\t\t\t\t\t\t_each(val, function (v, i){\n\t\t\t\t\t\t\tadd(v, postNameConcat(name, i));\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tForm.append(name, val);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t(function _addFile(file/**Object*/){\n\t\t\t\t\tif( file.image ){ // This is a FileAPI.Image\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tfile.toData(function (err, image){\n\t\t\t\t\t\t\t// @todo: error\n\t\t\t\t\t\t\tfilename = filename || (new Date).getTime()+'.png';\n\n\t\t\t\t\t\t\t_addFile(image);\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tif( isOrignTrans ){\n\t\t\t\t\t\t\t// Convert to array for transform function\n\t\t\t\t\t\t\ttrans = [trans];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tapi.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){\n\t\t\t\t\t\t\tif( isOrignTrans && !err ){\n\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t// Canvas.toBlob or Flash not supported, use multipart\n\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tForm.append(name, images[0], filename,  trans[0].type || filetype);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar addOrigin = 0;\n\n\t\t\t\t\t\t\t\tif( !err ){\n\t\t\t\t\t\t\t\t\t_each(images, function (image, idx){\n\t\t\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif( !trans[idx].postName ){\n\t\t\t\t\t\t\t\t\t\t\taddOrigin = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tForm.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif( err || options.imageOriginal ){\n\t\t\t\t\t\t\t\t\tForm.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( filename !== api.expando ){\n\t\t\t\t\t\tForm.append(name, file, filename);\n\t\t\t\t\t}\n\t\t\t\t})(file);\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\treset: function (inp, notRemove){\n\t\t\t\tvar parent, clone;\n\n\t\t\t\tif( jQuery ){\n\t\t\t\t\tclone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tjQuery(inp).remove();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tparent  = inp.parentNode;\n\t\t\t\t\tclone   = parent.insertBefore(inp.cloneNode(true), inp);\n\t\t\t\t\tclone.value = '';\n\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tparent.removeChild(inp);\n\t\t\t\t\t}\n\n\t\t\t\t\t_each(_elEvents[api.uid(inp)], function (fns, type){\n\t\t\t\t\t\t_each(fns, function (fn){\n\t\t\t\t\t\t\t_off(inp, type, fn);\n\t\t\t\t\t\t\t_on(clone, type, fn);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn  clone;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Load remote file\n\t\t\t *\n\t\t\t * @param   {String}    url\n\t\t\t * @param   {Function}  fn\n\t\t\t * @return  {XMLHttpRequest}\n\t\t\t */\n\t\t\tload: function (url, fn){\n\t\t\t\tvar xhr = api.getXHR();\n\t\t\t\tif( xhr ){\n\t\t\t\t\txhr.open('GET', url, true);\n\n\t\t\t\t\tif( xhr.overrideMimeType ){\n\t\t\t\t        xhr.overrideMimeType('text/plain; charset=x-user-defined');\n\t\t\t\t\t}\n\n\t\t\t\t\t_on(xhr, 'progress', function (/**Event*/evt){\n\t\t\t\t\t\t/** @namespace evt.lengthComputable */\n\t\t\t\t\t\tif( evt.lengthComputable ){\n\t\t\t\t\t\t\tfn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\txhr.onreadystatechange = function(){\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\t\t\t\t\t\t\tif( xhr.status == 200 ){\n\t\t\t\t\t\t\t\turl = url.split('/');\n\t\t\t\t\t\t\t\t/** @namespace xhr.responseBody */\n\t\t\t\t\t\t\t\tvar file = {\n\t\t\t\t\t\t\t\t      name: url[url.length-1]\n\t\t\t\t\t\t\t\t\t, size: xhr.getResponseHeader('Content-Length')\n\t\t\t\t\t\t\t\t\t, type: xhr.getResponseHeader('Content-Type')\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfile.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);\n\t\t\t\t\t\t\t\tfn({ type: 'load', result: file }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tfn({ type: 'error' }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t    }\n\t\t\t\t\t};\n\t\t\t\t    xhr.send(null);\n\t\t\t\t} else {\n\t\t\t\t\tfn({ type: 'error' });\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tencode64: function (str){\n\t\t\t\tvar b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;\n\n\t\t\t\tif( typeof str !== 'string' ){\n\t\t\t\t\tstr\t= String(str);\n\t\t\t\t}\n\n\t\t\t\twhile( i < str.length ){\n\t\t\t\t\t//all three \"& 0xff\" added below are there to fix a known bug\n\t\t\t\t\t//with bytes returned by xhr.responseText\n\t\t\t\t\tvar\n\t\t\t\t\t\t  byte1 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte2 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte3 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, enc1 = byte1 >> 2\n\t\t\t\t\t\t, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)\n\t\t\t\t\t\t, enc3, enc4\n\t\t\t\t\t;\n\n\t\t\t\t\tif( isNaN(byte2) ){\n\t\t\t\t\t\tenc3 = enc4 = 64;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tenc3 = ((byte2 & 15) << 2) | (byte3 >> 6);\n\t\t\t\t\t\tenc4 = isNaN(byte3) ? 64 : byte3 & 63;\n\t\t\t\t\t}\n\n\t\t\t\t\toutStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);\n\t\t\t\t}\n\n\t\t\t\treturn  outStr;\n\t\t\t}\n\n\t\t} // api\n\t;\n\n\n\tfunction _emit(target, fn, name, res, ext){\n\t\tvar evt = {\n\t\t\t  type:\t\tname.type || name\n\t\t\t, target:\ttarget\n\t\t\t, result:\tres\n\t\t};\n\t\t_extend(evt, ext);\n\t\tfn(evt);\n\t}\n\n\n\tfunction _hasSupportReadAs(as){\n\t\treturn\tFileReader && !!FileReader.prototype['readAs'+as];\n\t}\n\n\n\tfunction _readAs(file, fn, as, encoding){\n\t\tif( api.isBlob(file) && _hasSupportReadAs(as) ){\n\t\t\tvar Reader = new FileReader;\n\n\t\t\t// Add event listener\n\t\t\t_on(Reader, _readerEvents, function _fn(evt){\n\t\t\t\tvar type = evt.type;\n\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t}\n\t\t\t\telse if( type == 'loadend' ){\n\t\t\t\t\t_off(Reader, _readerEvents, _fn);\n\t\t\t\t\tReader = null;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\ttry {\n\t\t\t\t// ReadAs ...\n\t\t\t\tif( encoding ){\n\t\t\t\t\tReader['readAs'+as](file, encoding);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tReader['readAs'+as](file);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (err){\n\t\t\t\t_emit(file, fn, 'error', undef, { error: err.toString() });\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });\n\t\t}\n\t}\n\n\n\tfunction _isRegularFile(file, callback){\n\t\t// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects\n\t\tif( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){\n\t\t\tif( FileReader ){\n\t\t\t\ttry {\n\t\t\t\t\tvar Reader = new FileReader();\n\n\t\t\t\t\t_one(Reader, _readerEvents, function (evt){\n\t\t\t\t\t\tvar isFile = evt.type != 'error';\n\t\t\t\t\t\tcallback(isFile);\n\t\t\t\t\t\tif( isFile ){\n\t\t\t\t\t\t\tReader.abort();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tReader.readAsDataURL(file);\n\t\t\t\t} catch( err ){\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcallback(null);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcallback(true);\n\t\t}\n\t}\n\n\n\tfunction _getAsEntry(item){\n\t\tvar entry;\n\t\tif( item.getAsEntry ){ entry = item.getAsEntry(); }\n\t\telse if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }\n\t\treturn\tentry;\n\t}\n\n\n\tfunction _readEntryAsFiles(entry, callback){\n\t\tif( !entry ){\n\t\t\t// error\n\t\t\tcallback('invalid entry');\n\t\t}\n\t\telse if( entry.isFile ){\n\t\t\t// Read as file\n\t\t\tentry.file(function(file){\n\t\t\t\t// success\n\t\t\t\tfile.fullPath = entry.fullPath;\n\t\t\t\tcallback(false, [file]);\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('FileError.code: '+err.code);\n\t\t\t});\n\t\t}\n\t\telse if( entry.isDirectory ){\n\t\t\tvar reader = entry.createReader(), result = [];\n\n\t\t\treader.readEntries(function(entries){\n\t\t\t\t// success\n\t\t\t\tapi.afor(entries, function (next, entry){\n\t\t\t\t\t_readEntryAsFiles(entry, function (err, files){\n\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\tapi.log(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresult = result.concat(files);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( next ){\n\t\t\t\t\t\t\tnext();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tcallback(false, result);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('directory_reader: ' + err);\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\t_readEntryAsFiles(_getAsEntry(entry), callback);\n\t\t}\n\t}\n\n\n\tfunction _simpleClone(obj){\n\t\tvar copy = {};\n\t\t_each(obj, function (val, key){\n\t\t\tif( val && (typeof val === 'object') && (val.nodeType === void 0) ){\n\t\t\t\tval = _extend({}, val);\n\t\t\t}\n\t\t\tcopy[key] = val;\n\t\t});\n\t\treturn\tcopy;\n\t}\n\n\n\tfunction isInputFile(el){\n\t\treturn\t_rinput.test(el && el.tagName);\n\t}\n\n\n\tfunction _getDataTransfer(evt){\n\t\treturn\t(evt.originalEvent || evt || '').dataTransfer || {};\n\t}\n\n\n\tfunction _isOriginTransform(trans){\n\t\tvar key;\n\t\tfor( key in trans ){\n\t\t\tif( trans.hasOwnProperty(key) ){\n\t\t\t\tif( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){\n\t\t\t\t\treturn\ttrue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn\tfalse;\n\t}\n\n\n\t// Add default image info reader\n\tapi.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){\n\t\tif( !file.__dimensions ){\n\t\t\tvar defer = file.__dimensions = api.defer();\n\n\t\t\tapi.readAsImage(file, function (evt){\n\t\t\t\tvar img = evt.target;\n\t\t\t\tdefer.resolve(evt.type == 'load' ? false : 'error', {\n\t\t\t\t\t  width:  img.width\n\t\t\t\t\t, height: img.height\n\t\t\t\t});\n                img.src = api.EMPTY_PNG;\n\t\t\t\timg = null;\n\t\t\t});\n\t\t}\n\n\t\tfile.__dimensions.then(callback);\n\t});\n\n\n\t/**\n\t * Drag'n'Drop special event\n\t *\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd = function (el, onHover, onDrop){\n\t\tvar _id, _type;\n\n\t\tif( !onDrop ){\n\t\t\tonDrop = onHover;\n\t\t\tonHover = api.F;\n\t\t}\n\n\t\tif( FileReader ){\n\t\t\t// Hover\n\t\t\t_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){\n\t\t\t\tvar\n\t\t\t\t\t  types = _getDataTransfer(evt).types\n\t\t\t\t\t, i = types && types.length\n\t\t\t\t\t, debounceTrigger = false\n\t\t\t\t;\n\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tif( ~types[i].indexOf('File') ){\n\t\t\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t\t\tif( _type !== evt.type ){\n\t\t\t\t\t\t\t_type = evt.type; // Store current type of event\n\n\t\t\t\t\t\t\tif( _type != 'dragleave' ){\n\t\t\t\t\t\t\t\tonHover.call(evt[currentTarget], true, evt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdebounceTrigger = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak; // exit from \"while\"\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( debounceTrigger ){\n\t\t\t\t\tclearTimeout(_id);\n\t\t\t\t\t_id = setTimeout(function (){\n\t\t\t\t\t\tonHover.call(evt[currentTarget], _type != 'dragleave', evt);\n\t\t\t\t\t}, 50);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\t// Drop\n\t\t\t_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){\n\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t_type = 0;\n\t\t\t\tonHover.call(evt[currentTarget], false, evt);\n\n\t\t\t\tapi.getDropFiles(evt, function (files){\n\t\t\t\t\tonDrop.call(evt[currentTarget], files, evt);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\tapi.log(\"Drag'n'Drop -- not supported\");\n\t\t}\n\t};\n\n\n\t/**\n\t * Remove drag'n'drop\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd.off = function (el, onHover, onDrop){\n\t\t_off(el, 'dragenter dragleave dragover', onHover.ff);\n\t\t_off(el, 'drop', onDrop.ff);\n\t};\n\n\n\t// Support jQuery\n\tif( jQuery && !jQuery.fn.dnd ){\n\t\tjQuery.fn.dnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\n\t\tjQuery.fn.offdnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd.off(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\t}\n\n\t// @export\n\twindow.FileAPI  = _extend(api, window.FileAPI);\n\n\n\t// Debug info\n\tapi.log('FileAPI: ' + api.version);\n\tapi.log('protocol: ' + window.location.protocol);\n\tapi.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);\n\n\n\t// @detect 'x-ua-compatible'\n\t_each(document.getElementsByTagName('meta'), function (meta){\n\t\tif( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){\n\t\t\tapi.log('meta.http-equiv: ' + meta.getAttribute('content'));\n\t\t}\n\t});\n\n\n\t// configuration\n\ttry {\n\t\tapi._supportConsoleLog = !!console.log;\n\t\tapi._supportConsoleLogApply = !!console.log.apply;\n\t} catch (err) {}\n\n\tif( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }\n\tif( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }\n\tif( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }\n})(window, void 0);\n\n/*global window, FileAPI, document */\n\n(function (api, document, undef) {\n\t'use strict';\n\n\tvar\n\t\tmin = Math.min,\n\t\tround = Math.round,\n\t\tgetCanvas = function () { return document.createElement('canvas'); },\n\t\tsupport = false,\n\t\texifOrientation = {\n\t\t\t  8:\t270\n\t\t\t, 3:\t180\n\t\t\t, 6:\t90\n\t\t\t, 7:\t270\n\t\t\t, 4:\t180\n\t\t\t, 5:\t90\n\t\t}\n\t;\n\n\ttry {\n\t\tsupport = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;\n\t}\n\tcatch (e){}\n\n\n\tfunction Image(file){\n\t\tif( file instanceof Image ){\n\t\t\tvar img = new Image(file.file);\n\t\t\tapi.extend(img.matrix, file.matrix);\n\t\t\treturn\timg;\n\t\t}\n\t\telse if( !(this instanceof Image) ){\n\t\t\treturn\tnew Image(file);\n\t\t}\n\n\t\tthis.file   = file;\n\t\tthis.size   = file.size || 100;\n\n\t\tthis.matrix\t= {\n\t\t\tsx: 0,\n\t\t\tsy: 0,\n\t\t\tsw: 0,\n\t\t\tsh: 0,\n\t\t\tdx: 0,\n\t\t\tdy: 0,\n\t\t\tdw: 0,\n\t\t\tdh: 0,\n\t\t\tresize: 0, // min, max OR preview\n\t\t\tdeg: 0,\n\t\t\tquality: 1, // jpeg quality\n\t\t\tfilter: 0\n\t\t};\n\t}\n\n\n\tImage.prototype = {\n\t\timage: true,\n\t\tconstructor: Image,\n\n\t\tset: function (attrs){\n\t\t\tapi.extend(this.matrix, attrs);\n\t\t\treturn\tthis;\n\t\t},\n\n\t\tcrop: function (x, y, w, h){\n\t\t\tif( w === undef ){\n\t\t\t\tw\t= x;\n\t\t\t\th\t= y;\n\t\t\t\tx = y = 0;\n\t\t\t}\n\t\t\treturn\tthis.set({ sx: x, sy: y, sw: w, sh: h || w });\n\t\t},\n\n\t\tresize: function (w, h, strategy){\n\t\t\tif( /min|max/.test(h) ){\n\t\t\t\tstrategy = h;\n\t\t\t\th = w;\n\t\t\t}\n\n\t\t\treturn\tthis.set({ dw: w, dh: h || w, resize: strategy });\n\t\t},\n\n\t\tpreview: function (w, h){\n\t\t\treturn\tthis.resize(w, h || w, 'preview');\n\t\t},\n\n\t\trotate: function (deg){\n\t\t\treturn\tthis.set({ deg: deg });\n\t\t},\n\n\t\tfilter: function (filter){\n\t\t\treturn\tthis.set({ filter: filter });\n\t\t},\n\n\t\toverlay: function (images){\n\t\t\treturn\tthis.set({ overlay: images });\n\t\t},\n\n\t\tclone: function (){\n\t\t\treturn\tnew Image(this);\n\t\t},\n\n\t\t_load: function (image, fn){\n\t\t\tvar self = this;\n\n\t\t\tif( /img|video/i.test(image.nodeName) ){\n\t\t\t\tfn.call(self, null, image);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.readAsImage(image, function (evt){\n\t\t\t\t\tfn.call(self, evt.type != 'load', evt.result);\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\t_apply: function (image, fn){\n\t\t\tvar\n\t\t\t\t  canvas = getCanvas()\n\t\t\t\t, m = this.getMatrix(image)\n\t\t\t\t, ctx = canvas.getContext('2d')\n\t\t\t\t, width = image.videoWidth || image.width\n\t\t\t\t, height = image.videoHeight || image.height\n\t\t\t\t, deg = m.deg\n\t\t\t\t, dw = m.dw\n\t\t\t\t, dh = m.dh\n\t\t\t\t, w = width\n\t\t\t\t, h = height\n\t\t\t\t, filter = m.filter\n\t\t\t\t, copy // canvas copy\n\t\t\t\t, buffer = image\n\t\t\t\t, overlay = m.overlay\n\t\t\t\t, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })\n\t\t\t\t, renderImageToCanvas = api.renderImageToCanvas\n\t\t\t;\n\n\t\t\t// Normalize angle\n\t\t\tdeg = deg - Math.floor(deg/360)*360;\n\n\t\t\t// For `renderImageToCanvas`\n\t\t\timage._type = this.file.type;\n\n\t\t\twhile(m.multipass && min(w/dw, h/dh) > 2 ){\n\t\t\t\tw = (w/2 + 0.5)|0;\n\t\t\t\th = (h/2 + 0.5)|0;\n\n\t\t\t\tcopy = getCanvas();\n\t\t\t\tcopy.width  = w;\n\t\t\t\tcopy.height = h;\n\n\t\t\t\tif( buffer !== image ){\n\t\t\t\t\trenderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t\trenderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);\n\t\t\t\t\tm.sx = m.sy = m.sw = m.sh = 0;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tcanvas.width  = (deg % 180) ? dh : dw;\n\t\t\tcanvas.height = (deg % 180) ? dw : dh;\n\n\t\t\tcanvas.type = m.type;\n\t\t\tcanvas.quality = m.quality;\n\n\t\t\tctx.rotate(deg * Math.PI / 180);\n\t\t\trenderImageToCanvas(ctx.canvas, buffer\n\t\t\t\t, m.sx, m.sy\n\t\t\t\t, m.sw || buffer.width\n\t\t\t\t, m.sh || buffer.height\n\t\t\t\t, (deg == 180 || deg == 270 ? -dw : 0)\n\t\t\t\t, (deg == 90 || deg == 180 ? -dh : 0)\n\t\t\t\t, dw, dh\n\t\t\t);\n\t\t\tdw = canvas.width;\n\t\t\tdh = canvas.height;\n\n\t\t\t// Apply overlay\n\t\t\toverlay && api.each([].concat(overlay), function (over){\n\t\t\t\tqueue.inc();\n\t\t\t\t// preload\n\t\t\t\tvar img = new window.Image, fn = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  x = over.x|0\n\t\t\t\t\t\t, y = over.y|0\n\t\t\t\t\t\t, w = over.w || img.width\n\t\t\t\t\t\t, h = over.h || img.height\n\t\t\t\t\t\t, rel = over.rel\n\t\t\t\t\t;\n\n\t\t\t\t\t// center  |  right  |  left\n\t\t\t\t\tx = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);\n\n\t\t\t\t\t// center  |  bottom  |  top\n\t\t\t\t\ty = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);\n\n\t\t\t\t\tapi.event.off(img, 'error load abort', fn);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tctx.globalAlpha = over.opacity || 1;\n\t\t\t\t\t\tctx.drawImage(img, x, y, w, h);\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er){}\n\n\t\t\t\t\tqueue.next();\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(img, 'error load abort', fn);\n\t\t\t\timg.src = over.src;\n\n\t\t\t\tif( img.complete ){\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif( filter ){\n\t\t\t\tqueue.inc();\n\t\t\t\tImage.applyFilter(canvas, filter, queue.next);\n\t\t\t}\n\n\t\t\tqueue.check();\n\t\t},\n\n\t\tgetMatrix: function (image){\n\t\t\tvar\n\t\t\t\t  m  = api.extend({}, this.matrix)\n\t\t\t\t, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width\n\t\t\t\t, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height\n\t\t\t\t, dw = m.dw = m.dw || sw\n\t\t\t\t, dh = m.dh = m.dh || sh\n\t\t\t\t, sf = sw/sh, df = dw/dh\n\t\t\t\t, strategy = m.resize\n\t\t\t;\n\n\t\t\tif( strategy == 'preview' ){\n\t\t\t\tif( dw != sw || dh != sh ){\n\t\t\t\t\t// Make preview\n\t\t\t\t\tvar w, h;\n\n\t\t\t\t\tif( df >= sf ){\n\t\t\t\t\t\tw\t= sw;\n\t\t\t\t\t\th\t= w / df;\n\t\t\t\t\t} else {\n\t\t\t\t\t\th\t= sh;\n\t\t\t\t\t\tw\t= h * df;\n\t\t\t\t\t}\n\n\t\t\t\t\tif( w != sw || h != sh ){\n\t\t\t\t\t\tm.sx\t= ~~((sw - w)/2);\n\t\t\t\t\t\tm.sy\t= ~~((sh - h)/2);\n\t\t\t\t\t\tsw\t\t= w;\n\t\t\t\t\t\tsh\t\t= h;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if( strategy ){\n\t\t\t\tif( !(sw > dw || sh > dh) ){\n\t\t\t\t\tdw = sw;\n\t\t\t\t\tdh = sh;\n\t\t\t\t}\n\t\t\t\telse if( strategy == 'min' ){\n\t\t\t\t\tdw = round(sf < df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf < df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdw = round(sf >= df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf >= df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm.sw = sw;\n\t\t\tm.sh = sh;\n\t\t\tm.dw = dw;\n\t\t\tm.dh = dh;\n\t\t\tm.multipass = api.multiPassResize;\n\t\t\treturn\tm;\n\t\t},\n\n\t\t_trans: function (fn){\n\t\t\tthis._load(this.file, function (err, image){\n\t\t\t\tif( err ){\n\t\t\t\t\tfn(err);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis._apply(image, fn);\n\t\t\t\t\t} catch (err){\n\t\t\t\t\t\tapi.log('[err] FileAPI.Image.fn._apply:', err);\n\t\t\t\t\t\tfn(err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\tget: function (fn){\n\t\t\tif( api.support.transform ){\n\t\t\t\tvar _this = this, matrix = _this.matrix;\n\n\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\tapi.getInfo(_this.file, function (err, info){\n\t\t\t\t\t\t// rotate by exif orientation\n\t\t\t\t\t\tmatrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t_this._trans(fn);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_this._trans(fn);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn('not_support_transform');\n\t\t\t}\n\n\t\t\treturn this;\n\t\t},\n\n\n\t\ttoData: function (fn){\n\t\t\treturn this.get(fn);\n\t\t}\n\n\t};\n\n\n\tImage.exifOrientation = exifOrientation;\n\n\n\tImage.transform = function (file, transform, autoOrientation, fn){\n\t\tfunction _transform(err, img){\n\t\t\t// img -- info object\n\t\t\tvar\n\t\t\t\t  images = {}\n\t\t\t\t, queue = api.queue(function (err){\n\t\t\t\t\tfn(err, images);\n\t\t\t\t})\n\t\t\t;\n\n\t\t\tif( !err ){\n\t\t\t\tapi.each(transform, function (params, name){\n\t\t\t\t\tif( !queue.isFail() ){\n\t\t\t\t\t\tvar ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';\n\n\t\t\t\t\t\tif( isFn ){\n\t\t\t\t\t\t\tparams(img, ImgTrans);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( params.width ){\n\t\t\t\t\t\t\tImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tif( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){\n\t\t\t\t\t\t\t\tImgTrans.resize(params.maxWidth, params.maxHeight, 'max');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.crop ){\n\t\t\t\t\t\t\tvar crop = params.crop;\n\t\t\t\t\t\t\tImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.rotate === undef && autoOrientation ){\n\t\t\t\t\t\t\tparams.rotate = 'auto';\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });\n\n\t\t\t\t\t\tif( !isFn ){\n\t\t\t\t\t\t\tImgTrans.set({\n\t\t\t\t\t\t\t\t  deg: params.rotate\n\t\t\t\t\t\t\t\t, overlay: params.overlay\n\t\t\t\t\t\t\t\t, filter: params.filter\n\t\t\t\t\t\t\t\t, quality: params.quality || 1\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\tImgTrans.toData(function (err, image){\n\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\tqueue.fail();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\timages[name] = image;\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tqueue.fail();\n\t\t\t}\n\t\t}\n\n\n\t\t// @todo: Оло-ло, нужно рефакторить это место\n\t\tif( file.width ){\n\t\t\t_transform(false, file);\n\t\t} else {\n\t\t\tapi.getInfo(file, _transform);\n\t\t}\n\t};\n\n\n\t// @const\n\tapi.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){\n\t\tapi.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){\n\t\t\tImage[x+'_'+y] = i*3 + j;\n\t\t\tImage[y+'_'+x] = i*3 + j;\n\t\t});\n\t});\n\n\n\t/**\n\t * Trabsform element to canvas\n\t *\n\t * @param    {Image|HTMLVideoElement}   el\n\t * @returns  {Canvas}\n\t */\n\tImage.toCanvas = function(el){\n\t\tvar canvas\t\t= document.createElement('canvas');\n\t\tcanvas.width\t= el.videoWidth || el.width;\n\t\tcanvas.height\t= el.videoHeight || el.height;\n\t\tcanvas.getContext('2d').drawImage(el, 0, 0);\n\t\treturn\tcanvas;\n\t};\n\n\n\t/**\n\t * Create image from DataURL\n\t * @param  {String}  dataURL\n\t * @param  {Object}  size\n\t * @param  {Function}  callback\n\t */\n\tImage.fromDataURL = function (dataURL, size, callback){\n\t\tvar img = api.newImage(dataURL);\n\t\tapi.extend(img, size);\n\t\tcallback(img);\n\t};\n\n\n\t/**\n\t * Apply filter (caman.js)\n\t *\n\t * @param  {Canvas|Image}   canvas\n\t * @param  {String|Function}  filter\n\t * @param  {Function}  doneFn\n\t */\n\tImage.applyFilter = function (canvas, filter, doneFn){\n\t\tif( typeof filter == 'function' ){\n\t\t\tfilter(canvas, doneFn);\n\t\t}\n\t\telse if( window.Caman ){\n\t\t\t// http://camanjs.com/guides/\n\t\t\twindow.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tthis[filter]();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tapi.each(filter, function (val, method){\n\t\t\t\t\t\tthis[method](val);\n\t\t\t\t\t}, this);\n\t\t\t\t}\n\t\t\t\tthis.render(doneFn);\n\t\t\t});\n\t\t}\n\t};\n\n\n\t/**\n\t * For load-image-ios.js\n\t */\n\tapi.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){\n\t\ttry {\n\t\t\treturn canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);\n\t\t} catch (ex) {\n\t\t\tapi.log('renderImageToCanvas failed');\n\t\t\tthrow ex;\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.support.canvas = api.support.transform = support;\n\tapi.Image = Image;\n})(FileAPI, document);\n\n/*\n * JavaScript Load Image iOS scaling fixes 1.0.3\n * https://github.com/blueimp/JavaScript-Load-Image\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * iOS image scaling fixes based on\n * https://github.com/stomita/ios-imagefile-megapixel\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, bitwise: true */\n/*global FileAPI, window, document */\n\n(function (factory) {\n\t'use strict';\n\tfactory(FileAPI);\n}(function (loadImage) {\n    'use strict';\n\n    // Only apply fixes on the iOS platform:\n    if (!window.navigator || !window.navigator.platform ||\n             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {\n        return;\n    }\n\n    var originalRenderMethod = loadImage.renderImageToCanvas;\n\n    // Detects subsampling in JPEG images:\n    loadImage.detectSubsampling = function (img) {\n        var canvas,\n            context;\n        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images\n            canvas = document.createElement('canvas');\n            canvas.width = canvas.height = 1;\n            context = canvas.getContext('2d');\n            context.drawImage(img, -img.width + 1, 0);\n            // subsampled image becomes half smaller in rendering size.\n            // check alpha channel value to confirm image is covering edge pixel or not.\n            // if alpha value is 0 image is not covering, hence subsampled.\n            return context.getImageData(0, 0, 1, 1).data[3] === 0;\n        }\n        return false;\n    };\n\n    // Detects vertical squash in JPEG images:\n    loadImage.detectVerticalSquash = function (img, subsampled) {\n        var naturalHeight = img.naturalHeight || img.height,\n            canvas = document.createElement('canvas'),\n            context = canvas.getContext('2d'),\n            data,\n            sy,\n            ey,\n            py,\n            alpha;\n        if (subsampled) {\n            naturalHeight /= 2;\n        }\n        canvas.width = 1;\n        canvas.height = naturalHeight;\n        context.drawImage(img, 0, 0);\n        data = context.getImageData(0, 0, 1, naturalHeight).data;\n        // search image edge pixel position in case it is squashed vertically:\n        sy = 0;\n        ey = naturalHeight;\n        py = naturalHeight;\n        while (py > sy) {\n            alpha = data[(py - 1) * 4 + 3];\n            if (alpha === 0) {\n                ey = py;\n            } else {\n                sy = py;\n            }\n            py = (ey + sy) >> 1;\n        }\n        return (py / naturalHeight) || 1;\n    };\n\n    // Renders image to canvas while working around iOS image scaling bugs:\n    // https://github.com/blueimp/JavaScript-Load-Image/issues/13\n    loadImage.renderImageToCanvas = function (\n        canvas,\n        img,\n        sourceX,\n        sourceY,\n        sourceWidth,\n        sourceHeight,\n        destX,\n        destY,\n        destWidth,\n        destHeight\n    ) {\n        if (img._type === 'image/jpeg') {\n            var context = canvas.getContext('2d'),\n                tmpCanvas = document.createElement('canvas'),\n                tileSize = 1024,\n                tmpContext = tmpCanvas.getContext('2d'),\n                subsampled,\n                vertSquashRatio,\n                tileX,\n                tileY;\n            tmpCanvas.width = tileSize;\n            tmpCanvas.height = tileSize;\n            context.save();\n            subsampled = loadImage.detectSubsampling(img);\n            if (subsampled) {\n                sourceX /= 2;\n                sourceY /= 2;\n                sourceWidth /= 2;\n                sourceHeight /= 2;\n            }\n            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);\n            if (subsampled || vertSquashRatio !== 1) {\n                sourceY *= vertSquashRatio;\n                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);\n                destHeight = Math.ceil(\n                    tileSize * destHeight / sourceHeight / vertSquashRatio\n                );\n                destY = 0;\n                tileY = 0;\n                while (tileY < sourceHeight) {\n                    destX = 0;\n                    tileX = 0;\n                    while (tileX < sourceWidth) {\n                        tmpContext.clearRect(0, 0, tileSize, tileSize);\n                        tmpContext.drawImage(\n                            img,\n                            sourceX,\n                            sourceY,\n                            sourceWidth,\n                            sourceHeight,\n                            -tileX,\n                            -tileY,\n                            sourceWidth,\n                            sourceHeight\n                        );\n                        context.drawImage(\n                            tmpCanvas,\n                            0,\n                            0,\n                            tileSize,\n                            tileSize,\n                            destX,\n                            destY,\n                            destWidth,\n                            destHeight\n                        );\n                        tileX += tileSize;\n                        destX += destWidth;\n                    }\n                    tileY += tileSize;\n                    destY += destHeight;\n                }\n                context.restore();\n                return canvas;\n            }\n        }\n        return originalRenderMethod(\n            canvas,\n            img,\n            sourceX,\n            sourceY,\n            sourceWidth,\n            sourceHeight,\n            destX,\n            destY,\n            destWidth,\n            destHeight\n        );\n    };\n\n}));\n\n/*global window, FileAPI */\n\n(function (api, window){\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, FormData = window.FormData\n\t\t, Form = function (){ this.items = []; }\n\t\t, encodeURIComponent = window.encodeURIComponent\n\t;\n\n\n\tForm.prototype = {\n\n\t\tappend: function (name, blob, file, type){\n\t\t\tthis.items.push({\n\t\t\t\t  name: name\n\t\t\t\t, blob: blob && blob.blob || (blob == void 0 ? '' : blob)\n\t\t\t\t, file: blob && (file || blob.name)\n\t\t\t\t, type:\tblob && (type || blob.type)\n\t\t\t});\n\t\t},\n\n\t\teach: function (fn){\n\t\t\tvar i = 0, n = this.items.length;\n\t\t\tfor( ; i < n; i++ ){\n\t\t\t\tfn.call(this, this.items[i]);\n\t\t\t}\n\t\t},\n\n\t\ttoData: function (fn, options){\n\t\t    // allow chunked transfer if we have only one file to send\n\t\t    // flag is used below and in XHR._send\n\t\t    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;\n\n\t\t\tif( !api.support.html5 ){\n\t\t\t\tapi.log('FileAPI.Form.toHtmlData');\n\t\t\t\tthis.toHtmlData(fn);\n\t\t\t}\n\t\t\telse if( !api.formData || this.multipart || !FormData ){\n\t\t\t\tapi.log('FileAPI.Form.toMultipartData');\n\t\t\t\tthis.toMultipartData(fn);\n\t\t\t}\n\t\t\telse if( options._chunked ){\n\t\t\t\tapi.log('FileAPI.Form.toPlainData');\n\t\t\t\tthis.toPlainData(fn);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.log('FileAPI.Form.toFormData');\n\t\t\t\tthis.toFormData(fn);\n\t\t\t}\n\t\t},\n\n\t\t_to: function (data, complete, next, arg){\n\t\t\tvar queue = api.queue(function (){\n\t\t\t\tcomplete(data);\n\t\t\t});\n\n\t\t\tthis.each(function (file){\n\t\t\t\tnext(file, data, queue, arg);\n\t\t\t});\n\n\t\t\tqueue.check();\n\t\t},\n\n\n\t\ttoHtmlData: function (fn){\n\t\t\tthis._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){\n\t\t\t\tvar blob = file.blob, hidden;\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tapi.reset(blob, true);\n\t\t\t\t\t// set new name\n\t\t\t\t\tblob.name = file.name;\n\t\t\t\t\tblob.disabled = false;\n\t\t\t\t\tdata.appendChild(blob);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thidden = document.createElement('input');\n\t\t\t\t\thidden.name  = file.name;\n\t\t\t\t\thidden.type  = 'hidden';\n\t\t\t\t\thidden.value = blob;\n\t\t\t\t\tdata.appendChild(hidden);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\ttoPlainData: function (fn){\n\t\t\tthis._to({}, fn, function (file, data, queue){\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.type = file.file;\n\t\t\t\t}\n\n\t\t\t\tif( file.blob.toBlob ){\n\t\t\t\t    // canvas\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.name = file.name;\n\t\t\t\t\t\tdata.file = blob;\n\t\t\t\t\t\tdata.size = blob.length;\n\t\t\t\t\t\tdata.type = file.type;\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t    // file\n\t\t\t\t\tdata.name = file.blob.name;\n\t\t\t\t\tdata.file = file.blob;\n\t\t\t\t\tdata.size = file.blob.size;\n\t\t\t\t\tdata.type = file.type;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t    // additional data\n\t\t\t\t    if( !data.params ){\n\t\t\t\t        data.params = [];\n\t\t\t\t    }\n\t\t\t\t    data.params.push(encodeURIComponent(file.name) +\"=\"+ encodeURIComponent(file.blob));\n\t\t\t\t}\n\n\t\t\t\tdata.start = -1;\n\t\t\t\tdata.end = data.file && data.file.FileAPIReadPosition || -1;\n\t\t\t\tdata.retry = 0;\n\t\t\t});\n\t\t},\n\n\t\ttoFormData: function (fn){\n\t\t\tthis._to(new FormData, fn, function (file, data, queue){\n\t\t\t\tif( file.blob && file.blob.toBlob ){\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.append(file.name, blob, file.file);\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t\tdata.append(file.name, file.blob, file.file);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdata.append(file.name, file.blob);\n\t\t\t\t}\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.append('_'+file.name, file.file);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\ttoMultipartData: function (fn){\n\t\t\tthis._to([], fn, function (file, data, queue, boundary){\n\t\t\t\tqueue.inc();\n\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\tdata.push(\n\t\t\t\t\t\t  '--_' + boundary + ('\\r\\nContent-Disposition: form-data; name=\"'+ file.name +'\"'+ (file.file ? '; filename=\"'+ encodeURIComponent(file.file) +'\"' : '')\n\t\t\t\t\t\t+ (file.file ? '\\r\\nContent-Type: '+ (file.type || 'application/octet-stream') : '')\n\t\t\t\t\t\t+ '\\r\\n'\n\t\t\t\t\t\t+ '\\r\\n'+ (file.file ? blob : encodeURIComponent(blob))\n\t\t\t\t\t\t+ '\\r\\n')\n\t\t\t\t\t);\n\t\t\t\t\tqueue.next();\n\t\t\t\t}, true);\n\t\t\t}, api.expando);\n\t\t}\n\t};\n\n\n\tfunction _convertFile(file, fn, useBinaryString){\n\t\tvar blob = file.blob, filename = file.file;\n\n\t\tif( filename ){\n\t\t\tif( !blob.toDataURL ){\n\t\t\t\t// The Blob is not an image.\n\t\t\t\tapi.readAsBinaryString(blob, function (evt){\n\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\tfn(file, evt.result);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar\n\t\t\t\t  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }\n\t\t\t\t, type = mime[file.type] ? file.type : 'image/png'\n\t\t\t\t, ext  = mime[type] || '.png'\n\t\t\t\t, quality = blob.quality || 1\n\t\t\t;\n\n\t\t\tif( !filename.match(new RegExp(ext+'$', 'i')) ){\n\t\t\t\t// Does not change the current extension, but add a new one.\n\t\t\t\tfilename += ext.replace('?', '');\n\t\t\t}\n\n\t\t\tfile.file = filename;\n\t\t\tfile.type = type;\n\n\t\t\tif( !useBinaryString && blob.toBlob ){\n\t\t\t\tblob.toBlob(function (blob){\n\t\t\t\t\tfn(file, blob);\n\t\t\t\t}, type, quality);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn(file, api.toBinaryString(blob.toDataURL(type, quality)));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfn(file, blob);\n\t\t}\n\t}\n\n\n\t// @export\n\tapi.Form = Form;\n})(FileAPI, window);\n\n/*global window, FileAPI, Uint8Array */\n\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\t  noop = function (){}\n\t\t, document = window.document\n\n\t\t, XHR = function (options){\n\t\t\tthis.uid = api.uid();\n\t\t\tthis.xhr = {\n\t\t\t\t  abort: noop\n\t\t\t\t, getResponseHeader: noop\n\t\t\t\t, getAllResponseHeaders: noop\n\t\t\t};\n\t\t\tthis.options = options;\n\t\t},\n\n\t\t_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }\n\t;\n\n\n\tXHR.prototype = {\n\t\tstatus: 0,\n\t\tstatusText: '',\n\t\tconstructor: XHR,\n\n\t\tgetResponseHeader: function (name){\n\t\t\treturn this.xhr.getResponseHeader(name);\n\t\t},\n\n\t\tgetAllResponseHeaders: function (){\n\t\t\treturn this.xhr.getAllResponseHeaders() || {};\n\t\t},\n\n\t\tend: function (status, statusText){\n\t\t\tvar _this = this, options = _this.options;\n\n\t\t\t_this.end\t\t=\n\t\t\t_this.abort\t\t= noop;\n\t\t\t_this.status\t= status;\n\n\t\t\tif( statusText ){\n\t\t\t\t_this.statusText = statusText;\n\t\t\t}\n\n\t\t\tapi.log('xhr.end:', status, statusText);\n\t\t\toptions.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);\n\n\t\t\tif( _this.xhr && _this.xhr.node ){\n\t\t\t\tsetTimeout(function (){\n\t\t\t\t\tvar node = _this.xhr.node;\n\t\t\t\t\ttry { node.parentNode.removeChild(node); } catch (e){}\n\t\t\t\t\ttry { delete window[_this.uid]; } catch (e){}\n\t\t\t\t\twindow[_this.uid] = _this.xhr.node = null;\n\t\t\t\t}, 9);\n\t\t\t}\n\t\t},\n\n\t\tabort: function (){\n\t\t\tthis.end(0, 'abort');\n\n\t\t\tif( this.xhr ){\n\t\t\t\tthis.xhr.aborted = true;\n\t\t\t\tthis.xhr.abort();\n\t\t\t}\n\t\t},\n\n\t\tsend: function (FormData){\n\t\t\tvar _this = this, options = this.options;\n\n\t\t\tFormData.toData(function (data){\n\t\t\t\t// Start uploading\n\t\t\t\toptions.upload(options, _this);\n\t\t\t\t_this._send.call(_this, options, data);\n\t\t\t}, options);\n\t\t},\n\n\t\t_send: function (options, data){\n\t\t\tvar _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + \"Load\", url = options.url;\n\n\t\t\tapi.log('XHR._send:', data);\n\n\t\t\tif( !options.cache ){\n\t\t\t\t// No cache\n\t\t\t\turl += (~url.indexOf('?') ? '&' : '?') + api.uid();\n\t\t\t}\n\n\t\t\tif( data.nodeName ){\n\t\t\t\tvar jsonp = options.jsonp;\n\n\t\t\t\t// prepare callback in GET\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)/i, '$1='+uid);\n\n\t\t\t\t// legacy\n\t\t\t\toptions.upload(options, _this);\n\n\t\t\t\tvar\n\t\t\t\t\tonPostMessage = function (evt){\n\t\t\t\t\t\tif( ~url.indexOf(evt.origin) ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar result = api.parseJSON(evt.data);\n\t\t\t\t\t\t\t\tif( result.id == uid ){\n\t\t\t\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\tcomplete(0, err.message);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// jsonp-callack\n\t\t\t\t\tcomplete = window[uid] = function (status, statusText, response){\n\t\t\t\t\t\t_this.readyState\t= 4;\n\t\t\t\t\t\t_this.responseText\t= response;\n\t\t\t\t\t\t_this.end(status, statusText);\n\n\t\t\t\t\t\tapi.event.off(window, 'message', onPostMessage);\n\t\t\t\t\t\twindow[uid] = xhr = transport = window[onloadFuncName] = null;\n\t\t\t\t\t}\n\t\t\t\t;\n\n\t\t\t\t_this.xhr.abort = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( transport.stop ){ transport.stop(); }\n\t\t\t\t\t\telse if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }\n\t\t\t\t\t\telse { transport.contentWindow.document.execCommand('Stop'); }\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er) {}\n\t\t\t\t\tcomplete(0, \"abort\");\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(window, 'message', onPostMessage);\n\n\t\t\t\twindow[onloadFuncName] = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  win = transport.contentWindow\n\t\t\t\t\t\t\t, doc = win.document\n\t\t\t\t\t\t\t, result = win.result || api.parseJSON(doc.body.innerHTML)\n\t\t\t\t\t\t;\n\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('[transport.onload]', e);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\txhr = document.createElement('div');\n\t\t\t\txhr.innerHTML = '<form target=\"'+ uid +'\" action=\"'+ url +'\" method=\"POST\" enctype=\"multipart/form-data\" style=\"position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;\">'\n\t\t\t\t\t\t\t+ '<iframe name=\"'+ uid +'\" src=\"javascript:false;\" onload=\"' + onloadFuncName + '()\"></iframe>'\n\t\t\t\t\t\t\t+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value=\"'+ uid +'\" name=\"'+jsonp+'\" type=\"hidden\"/>' : '')\n\t\t\t\t\t\t\t+ '</form>'\n\t\t\t\t;\n\n\t\t\t\t// get form-data & transport\n\t\t\t\tvar\n\t\t\t\t\t  form = xhr.getElementsByTagName('form')[0]\n\t\t\t\t\t, transport = xhr.getElementsByTagName('iframe')[0]\n\t\t\t\t;\n\n\t\t\t\tform.appendChild(data);\n\n\t\t\t\tapi.log(form.parentNode.innerHTML);\n\n\t\t\t\t// append to DOM\n\t\t\t\tdocument.body.appendChild(xhr);\n\n\t\t\t\t// keep a reference to node-transport\n\t\t\t\t_this.xhr.node = xhr;\n\n\t\t\t\t// send\n\t\t\t\t_this.readyState = 2; // loaded\n\t\t\t\tform.submit();\n\t\t\t\tform = null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Clean url\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)&?/i, '');\n\n\t\t\t\t// html5\n\t\t\t\tif (this.xhr && this.xhr.aborted) {\n\t\t\t\t\tapi.log(\"Error: already aborted\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\txhr = _this.xhr = api.getXHR();\n\n\t\t\t\tif (data.params) {\n\t\t\t\t\turl += (url.indexOf('?') < 0 ? \"?\" : \"&\") + data.params.join(\"&\");\n\t\t\t\t}\n\n\t\t\t\txhr.open('POST', url, true);\n\n\t\t\t\tif( api.withCredentials ){\n\t\t\t\t\txhr.withCredentials = \"true\";\n\t\t\t\t}\n\n\t\t\t\tif( !options.headers || !options.headers['X-Requested-With'] ){\n\t\t\t\t\txhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\t\t\t}\n\n\t\t\t\tapi.each(options.headers, function (val, key){\n\t\t\t\t\txhr.setRequestHeader(key, val);\n\t\t\t\t});\n\n\n\t\t\t\tif ( options._chunked ) {\n\t\t\t\t\t// chunked upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\tif (!data.retry) {\n\t\t\t\t\t\t\t\t// show progress only for correct chunk uploads\n\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t  type:\t\t\tevt.type\n\t\t\t\t\t\t\t\t\t, total:\t\tdata.size\n\t\t\t\t\t\t\t\t\t, loaded:\t\tdata.start + evt.loaded\n\t\t\t\t\t\t\t\t\t, totalSize:\tdata.size\n\t\t\t\t\t\t\t\t}, _this, options);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\tvar lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);\n\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}catch(_){}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status - 201 > 0) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\t// some kind of error\n\t\t\t\t\t\t\t\t// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action\n\t\t\t\t\t\t\t\t// up - server error\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {\n\t\t\t\t\t\t\t\t\t// let's try again the same chunk\n\t\t\t\t\t\t\t\t\t// only applicable for recoverable error codes 500 && 416\n\t\t\t\t\t\t\t\t\tvar delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(data.file, options);\n\n\t\t\t\t\t\t\t\t\t// smart restart if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdata.end = data.start - 1;\n\t\t\t\t\t\t\t\t\t\tif (416 == xhr.status) {\n\t\t\t\t\t\t\t\t\t\t\tdata.end = data.end - options.chunkSize;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// no mo retries\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// success\n\t\t\t\t\t\t\t\tdata.retry = 0;\n\n\t\t\t\t\t\t\t\tif (data.end == data.size - 1) {\n\t\t\t\t\t\t\t\t\t// finished\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// next chunk\n\n\t\t\t\t\t\t\t\t\t// shift position if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdata.file.FileAPIReadPosition = data.end;\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tdata.start = data.end + 1;\n\t\t\t\t\tdata.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);\n\n\t\t\t\t\t// Retrieve a slice of file\n\t\t\t\t\tvar\n\t\t\t\t\t\t  file = data.file\n\t\t\t\t\t\t, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)\n\t\t\t\t\t;\n\n\t\t\t\t\tif( data.size && !slice.size ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\t_this.end(-1);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Range\", \"bytes \" + data.start + \"-\" + data.end + \"/\" + data.size);\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Disposition\", 'attachment; filename=' + encodeURIComponent(data.name));\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Type\", data.type || \"application/octet-stream\");\n\n\t\t\t\t\t\txhr.send(slice);\n\t\t\t\t\t}\n\n\t\t\t\t\tfile = slice = null;\n\t\t\t\t} else {\n\t\t\t\t\t// single piece upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\t// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\toptions.progress(evt, _this, options);\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status > 201) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {\n\t\t\t\t\t\t\t\t\toptions.retry = (options.retry || 0) + 1;\n\t\t\t\t\t\t\t\t\tvar delay = api.networkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(options.file, options);\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif( api.isArray(data) ){\n\t\t\t\t\t\t// multipart\n\t\t\t\t\t\txhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);\n\t\t\t\t\t\tvar rawData = data.join('') +'--_'+ api.expando +'--';\n\n\t\t\t\t\t\t/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */\n\t\t\t\t\t\tif( xhr.sendAsBinary ){\n\t\t\t\t\t\t\txhr.sendAsBinary(rawData);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tvar bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });\n\t\t\t\t\t\t\txhr.send(new Uint8Array(bytes).buffer);\n\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// FormData\n\t\t\t\t\t\txhr.send(data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.XHR = XHR;\n})(window, FileAPI);\n\n/**\n * @class\tFileAPI.Camera\n * @author\tRubaXa\t<trash@rubaxa.org>\n * @support\tChrome 21+, FF 18+, Opera 12+\n */\n\n/*global window, FileAPI, jQuery */\n/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\tURL = window.URL || window.webkitURL,\n\n\t\tdocument = window.document,\n\t\tnavigator = window.navigator,\n\n\t\tgetMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,\n\n\t\thtml5 = !!getMedia\n\t;\n\n\n\t// Support \"media\"\n\tapi.support.media = html5;\n\n\n\tvar Camera = function (video){\n\t\tthis.video = video;\n\t};\n\n\n\tCamera.prototype = {\n\t\tisActive: function (){\n\t\t\treturn\t!!this._active;\n\t\t},\n\n\n\t\t/**\n\t\t * Start camera streaming\n\t\t * @param\t{Function}\tcallback\n\t\t */\n\t\tstart: function (callback){\n\t\t\tvar\n\t\t\t\t  _this = this\n\t\t\t\t, video = _this.video\n\t\t\t\t, _successId\n\t\t\t\t, _failId\n\t\t\t\t, _complete = function (err){\n\t\t\t\t\t_this._active = !err;\n\t\t\t\t\tclearTimeout(_failId);\n\t\t\t\t\tclearTimeout(_successId);\n//\t\t\t\t\tapi.event.off(video, 'loadedmetadata', _complete);\n\t\t\t\t\tcallback && callback(err, _this);\n\t\t\t\t}\n\t\t\t;\n\n\t\t\tgetMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){\n\t\t\t\t// Success\n\t\t\t\t_this.stream = stream;\n\n//\t\t\t\tapi.event.on(video, 'loadedmetadata', function (){\n//\t\t\t\t\t_complete(null);\n//\t\t\t\t});\n\n\t\t\t\t// Set camera stream\n\t\t\t\tvideo.src = URL.createObjectURL(stream);\n\n\t\t\t\t// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.\n\t\t\t\t// See crbug.com/110938.\n\t\t\t\t_successId = setInterval(function (){\n\t\t\t\t\tif( _detectVideoSignal(video) ){\n\t\t\t\t\t\t_complete(null);\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\n\t\t\t\t_failId = setTimeout(function (){\n\t\t\t\t\t_complete('timeout');\n\t\t\t\t}, 5000);\n\n\t\t\t\t// Go-go-go!\n\t\t\t\tvideo.play();\n\t\t\t}, _complete/*error*/);\n\t\t},\n\n\n\t\t/**\n\t\t * Stop camera streaming\n\t\t */\n\t\tstop: function (){\n\t\t\ttry {\n\t\t\t\tthis._active = false;\n\t\t\t\tthis.video.pause();\n\t\t\t\tthis.stream.stop();\n\t\t\t} catch( err ){ }\n\t\t},\n\n\n\t\t/**\n\t\t * Create screenshot\n\t\t * @return {FileAPI.Camera.Shot}\n\t\t */\n\t\tshot: function (){\n\t\t\treturn\tnew Shot(this.video);\n\t\t}\n\t};\n\n\n\t/**\n\t * Get camera element from container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @return\t{Camera}\n\t */\n\tCamera.get = function (el){\n\t\treturn\tnew Camera(el.firstChild);\n\t};\n\n\n\t/**\n\t * Publish camera element into container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Object}\t\toptions\n\t * @param\t{Function}\t\t[callback]\n\t */\n\tCamera.publish = function (el, options, callback){\n\t\tif( typeof options == 'function' ){\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\n\t\t// Dimensions of \"camera\"\n\t\toptions = api.extend({}, {\n\t\t\t  width:\t'100%'\n\t\t\t, height:\t'100%'\n\t\t\t, start:\ttrue\n\t\t}, options);\n\n\n\t\tif( el.jquery ){\n\t\t\t// Extract first element, from jQuery collection\n\t\t\tel = el[0];\n\t\t}\n\n\n\t\tvar doneFn = function (err){\n\t\t\tif( err ){\n\t\t\t\tcallback(err);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Get camera\n\t\t\t\tvar cam = Camera.get(el);\n\t\t\t\tif( options.start ){\n\t\t\t\t\tcam.start(callback);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(null, cam);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\n\t\tel.style.width\t= _px(options.width);\n\t\tel.style.height\t= _px(options.height);\n\n\n\t\tif( api.html5 && html5 ){\n\t\t\t// Create video element\n\t\t\tvar video = document.createElement('video');\n\n\t\t\t// Set dimensions\n\t\t\tvideo.style.width\t= _px(options.width);\n\t\t\tvideo.style.height\t= _px(options.height);\n\n\t\t\t// Clean container\n\t\t\tif( window.jQuery ){\n\t\t\t\tjQuery(el).empty();\n\t\t\t} else {\n\t\t\t\tel.innerHTML = '';\n\t\t\t}\n\n\t\t\t// Add \"camera\" to container\n\t\t\tel.appendChild(video);\n\n\t\t\t// end\n\t\t\tdoneFn();\n\t\t}\n\t\telse {\n\t\t\tCamera.fallback(el, options, doneFn);\n\t\t}\n\t};\n\n\n\tCamera.fallback = function (el, options, callback){\n\t\tcallback('not_support_camera');\n\t};\n\n\n\t/**\n\t * @class\tFileAPI.Camera.Shot\n\t */\n\tvar Shot = function (video){\n\t\tvar canvas\t= video.nodeName ? api.Image.toCanvas(video) : video;\n\t\tvar shot\t= api.Image(canvas);\n\t\tshot.type\t= 'image/png';\n\t\tshot.width\t= canvas.width;\n\t\tshot.height\t= canvas.height;\n\t\tshot.size\t= canvas.width * canvas.height * 4;\n\t\treturn\tshot;\n\t};\n\n\n\t/**\n\t * Add \"px\" postfix, if value is a number\n\t *\n\t * @private\n\t * @param\t{*}  val\n\t * @return\t{String}\n\t */\n\tfunction _px(val){\n\t\treturn\tval >= 0 ? val + 'px' : val;\n\t}\n\n\n\t/**\n\t * @private\n\t * @param\t{HTMLVideoElement} video\n\t * @return\t{Boolean}\n\t */\n\tfunction _detectVideoSignal(video){\n\t\tvar canvas = document.createElement('canvas'), ctx, res = false;\n\t\ttry {\n\t\t\tctx = canvas.getContext('2d');\n\t\t\tctx.drawImage(video, 0, 0, 1, 1);\n\t\t\tres = ctx.getImageData(0, 0, 1, 1).data[4] != 255;\n\t\t}\n\t\tcatch( e ){}\n\t\treturn\tres;\n\t}\n\n\n\t// @export\n\tCamera.Shot\t= Shot;\n\tapi.Camera\t= Camera;\n})(window, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, ActiveXObject, FileAPI */\n(function (window, jQuery, api) {\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, location = window.location\n\t\t, navigator = window.navigator\n\t\t, _each = api.each\n\t;\n\n\n\tapi.support.flash = (function (){\n\t\tvar mime = navigator.mimeTypes, has = false;\n\n\t\tif( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){\n\t\t\thas\t= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);\n\t\t}\n\t\telse {\n\t\t\ttry {\n\t\t\t\thas\t= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));\n\t\t\t}\n\t\t\tcatch(er){\n\t\t\t\tapi.log('Flash -- does not supported.');\n\t\t\t}\n\t\t}\n\n\t\tif( has && /^file:/i.test(location) ){\n\t\t\tapi.log('[warn] Flash does not work on `file:` protocol.');\n\t\t}\n\n\t\treturn\thas;\n\t})();\n\n\n\t   api.support.flash\n\t&& (0\n\t\t|| !api.html5 || !api.support.html5\n\t\t|| (api.cors && !api.support.cors)\n\t\t|| (api.media && !api.support.media)\n\t)\n\t&& (function (){\n\t\tvar\n\t\t\t  _attr  = api.uid()\n\t\t\t, _retry = 0\n\t\t\t, _files = {}\n\t\t\t, _rhttp = /^https?:/i\n\n\t\t\t, flash = {\n\t\t\t\t_fn: {},\n\n\n\t\t\t\t/**\n\t\t\t\t * Publish flash-object\n\t\t\t\t *\n\t\t\t\t * @param {HTMLElement} el\n\t\t\t\t * @param {String} id\n\t\t\t\t * @param {Object} [opts]\n\t\t\t\t */\n\t\t\t\tpublish: function (el, id, opts){\n\t\t\t\t\topts = opts || {};\n\t\t\t\t\tel.innerHTML = _makeFlashHTML({\n\t\t\t\t\t\tid: id\n\t\t\t\t\t\t, src: _getUrl(api.flashUrl, 'r=' + api.version)\n//\t\t\t\t\t\t, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')\n\t\t\t\t\t\t, wmode: opts.camera ? '' : 'transparent'\n\t\t\t\t\t\t, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')\n\t\t\t\t\t\t+ '&flashId='+ id\n\t\t\t\t\t\t+ '&storeKey='+ navigator.userAgent.match(/\\d/ig).join('') +'_'+ api.version\n\t\t\t\t\t\t+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))\n\t\t\t\t\t\t+ '&timeout='+api.flashAbortTimeout\n\t\t\t\t\t\t+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')\n\t\t\t\t\t\t+ '&debug='+(api.debug?\"1\":\"\")\n\t\t\t\t\t}, opts);\n\t\t\t\t},\n\n\n\t\t\t\t/**\n\t\t\t\t * Initialization & preload flash object\n\t\t\t\t */\n\t\t\t\tinit: function (){\n\t\t\t\t\tvar child = document.body && document.body.firstChild;\n\n\t\t\t\t\tif( child ){\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tif( child.nodeType == 1 ){\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.state: awaiting');\n\n\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\n\t\t\t\t\t\t\t\tdummy.id = '_' + _attr;\n\n\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t  top: 1\n\t\t\t\t\t\t\t\t\t, right: 1\n\t\t\t\t\t\t\t\t\t, width: 5\n\t\t\t\t\t\t\t\t\t, height: 5\n\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tchild.parentNode.insertBefore(dummy, child);\n\t\t\t\t\t\t\t\tflash.publish(dummy, _attr);\n\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile( child = child.nextSibling );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( _retry < 10 ){\n\t\t\t\t\t\tsetTimeout(flash.init, ++_retry*50);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tready: function (){\n\t\t\t\t\tapi.log('FlashAPI.state: ready');\n\n\t\t\t\t\tflash.ready = api.F;\n\t\t\t\t\tflash.isReady = true;\n\t\t\t\t\tflash.patch();\n\t\t\t\t\tflash.patchCamera && flash.patchCamera();\n\t\t\t\t\tapi.event.on(document, 'mouseover', flash.mouseover);\n\t\t\t\t\tapi.event.on(document, 'click', function (evt){\n\t\t\t\t\t\tif( flash.mouseover(evt) ){\n\t\t\t\t\t\t\tevt.preventDefault\n\t\t\t\t\t\t\t\t? evt.preventDefault()\n\t\t\t\t\t\t\t\t: (evt.returnValue = true)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\n\n\t\t\t\tgetEl: function (){\n\t\t\t\t\treturn\tdocument.getElementById('_'+_attr);\n\t\t\t\t},\n\n\n\t\t\t\tgetWrapper: function (node){\n\t\t\t\t\tdo {\n\t\t\t\t\t\tif( /js-fileapi-wrapper/.test(node.className) ){\n\t\t\t\t\t\t\treturn\tnode;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twhile( (node = node.parentNode) && (node !== document.body) );\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tdisableMouseover: false,\n\n\t\t\t\tmouseover: function (evt){\n\t\t\t\t\tif (!flash.disableMouseover) {\n\t\t\t\t\t\tvar target = api.event.fix(evt).target;\n\t\n\t\t\t\t\t\tif( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  state = target.getAttribute(_attr)\n\t\t\t\t\t\t\t\t, wrapper = flash.getWrapper(target)\n\t\t\t\t\t\t\t;\n\t\n\t\t\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\t\t\t// check state:\n\t\t\t\t\t\t\t\t//   i — published\n\t\t\t\t\t\t\t\t//   i — initialization\n\t\t\t\t\t\t\t\t//   r — ready\n\t\t\t\t\t\t\t\tif( state == 'i' || state == 'r' ){\n\t\t\t\t\t\t\t\t\t// publish fail\n\t\t\t\t\t\t\t\t\treturn\tfalse;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse if( state != 'p' ){\n\t\t\t\t\t\t\t\t\t// set \"init\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'i');\n\t\n\t\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\t\n\t\t\t\t\t\t\t\t\tif( !wrapper ){\n\t\t\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t\t  top:    0\n\t\t\t\t\t\t\t\t\t\t, left:   0\n\t\t\t\t\t\t\t\t\t\t, width:  target.offsetWidth\n\t\t\t\t\t\t\t\t\t\t, height: target.offsetHeight\n\t\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t\t\t\twrapper.appendChild(dummy);\n\t\t\t\t\t\t\t\t\tflash.publish(dummy, api.uid());\n\t\n\t\t\t\t\t\t\t\t\t// set \"publish\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'p');\n\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( wrapper ){\n\t\t\t\t\t\t\t\t// Use one flash element\n\t\t\t\t\t\t\t\tvar box = _getDimensions(wrapper);\n\t\t\t\t\t\t\t\t_css(flash.getEl(), box);\n\t\n\t\t\t\t\t\t\t\t// Set current input\n\t\t\t\t\t\t\t\tflash.curInp = target;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( !/object|embed/i.test(target.nodeName) ){\n\t\t\t\t\t\t\t_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tonEvent: function (evt){\n\t\t\t\t\tvar type = evt.type;\n\t\t\t\t\t\n\t\t\t\t\tif( type == 'ready' ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// set \"ready\" state\n\t\t\t\t\t\t\tflash.getInput(evt.flashId).setAttribute(_attr, 'r');\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tflash.ready();\n\t\t\t\t\t\tsetTimeout(function (){ flash.mouseenter(evt); }, 50);\n\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'ping' ){\n\t\t\t\t\t\tapi.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'log' ){\n\t\t\t\t\t\tapi.log('(flash -> js).log:', evt.target);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type in flash ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\tapi.log('FlashAPI.event.'+evt.type+':', evt);\n\t\t\t\t\t\t\tflash[type](evt);\n\t\t\t\t\t\t}, 1);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tmouseDown: function(evt) {\n\t\t\t\t\tflash.disableMouseover = true;\n\t\t\t\t},\n\t\t\t\tcancel: function(evt) {\n\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t},\n\t\t\t\tmouseenter: function (evt){\n\t\t\t\t\tvar node = flash.getInput(evt.flashId);\n\n\t\t\t\t\tif( node ){\n\t\t\t\t\t\t// Set multiple mode\n\t\t\t\t\t\tflash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);\n\n\n\t\t\t\t\t\t// Set files filter\n\t\t\t\t\t\tvar accept = [], exts = {};\n\n\t\t\t\t\t\t_each((node.getAttribute('accept') || '').split(/,\\s*/), function (mime){\n\t\t\t\t\t\t\tapi.accept[mime] && _each(api.accept[mime].split(' '), function (ext){\n\t\t\t\t\t\t\t\texts[ext] = 1;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t_each(exts, function (i, ext){\n\t\t\t\t\t\t\taccept.push( ext );\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tflash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tget: function (id){\n\t\t\t\t\treturn\tdocument[id] || window[id] || document.embeds[id];\n\t\t\t\t},\n\n\n\t\t\t\tgetInput: function (id){\n\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar node = flash.getWrapper(flash.get(id));\n\t\t\t\t\t\t\tif( node ){\n\t\t\t\t\t\t\t\treturn node.getElementsByTagName('input')[0];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\tapi.log('[err] Can not find \"input\" by flashId:', id, e);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn\tflash.curInp;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tselect: function (evt){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  inp = flash.getInput(evt.flashId)\n\t\t\t\t\t\t\t, uid = api.uid(inp)\n\t\t\t\t\t\t\t, files = evt.target.files\n\t\t\t\t\t\t\t, event\n\t\t\t\t\t\t;\n\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\tapi.checkFileObj(file);\n\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t_files[uid] = files;\n\t\n\t\t\t\t\t\tif( document.createEvent ){\n\t\t\t\t\t\t\tevent = document.createEvent('Event');\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tevent.initEvent('change', true, true);\n\t\t\t\t\t\t\tinp.dispatchEvent(event);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( jQuery ){\n\t\t\t\t\t\t\tjQuery(inp).trigger({ type: 'change', files: files });\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tevent = document.createEventObject();\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tinp.fireEvent('onchange', event);\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tinterval: null,\n\t\t\t\tcmd: function (id, name, data, last) {\n\t\t\t\t\tif (flash.uploadInProgress && flash.readInProgress) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tflash.cmd(id, name, data, last);\n\t\t\t\t\t\t}, 100);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.cmdFn(id, name, data, last);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tcmdFn: function(id, name, data, last) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tapi.log('(js -> flash).'+name+':', data);\n\t\t\t\t\t\treturn flash.get(id.flashId || id).cmd(name, data);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('(js -> flash).onError:', e);\n\t\t\t\t\t\tif( !last ){\n\t\t\t\t\t\t\t// try again\n\t\t\t\t\t\t\tsetTimeout(function (){ flash.cmd(id, name, data, true); }, 50);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tpatch: function (){\n\t\t\t\t\tapi.flashEngine = true;\n\n\t\t\t\t\t// FileAPI\n\t\t\t\t\t_inherit(api, {\n\t\t\t\t\t\treadAsDataURL: function (file, callback){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64');\n\t\t\t\t\t\t\t\tflash.readInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(file, 'readAsBase64', {\n\t\t\t\t\t\t\t\t\tid: file.id,\n\t\t\t\t\t\t\t\t\tcallback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tflash.readInProgress = false;\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64:', err);\n\n\t\t\t\t\t\t\t\t\t\tcallback({\n\t\t\t\t\t\t\t\t\t\t\t  type: err ? 'error' : 'load'\n\t\t\t\t\t\t\t\t\t\t\t, error: err\n\t\t\t\t\t\t\t\t\t\t\t, result: 'data:'+ file.type +';base64,'+ base64\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\treadAsText: function (file, encoding, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.log('[warn] FlashAPI.readAsText not supported `encoding` param');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback = encoding;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tevt.result = window.atob(evt.result.split(';base64,')[1]);\n\t\t\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\t\t\tevt.error = err.toString();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback(evt);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\n\t\t\t\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];\n\n\n\t\t\t\t\t\t\tif( !files ){\n\t\t\t\t\t\t\t\t// Файлов нету, вызываем родительский метод\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tif( filter ){\n\t\t\t\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t\t\t\t\tfiles\t= api.filter(files, function (file){ return filter.test(file.name); });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn\tfiles;\n\t\t\t\t\t\t},\n\n\n\t\t\t\t\t\tgetInfo: function (file, fn){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( file.isShot ){\n\t\t\t\t\t\t\t\tfn(null, file.info = {\n\t\t\t\t\t\t\t\t\twidth: file.width,\n\t\t\t\t\t\t\t\t\theight: file.height\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( !file.__info ){\n\t\t\t\t\t\t\t\t\tvar defer = file.__info = api.defer();\n\n//\t\t\t\t\t\t\t\t\tflash.cmd(file, 'getFileInfo', {\n//\t\t\t\t\t\t\t\t\t\t  id: file.id\n//\t\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, info){\n//\t\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n//\t\t\t\t\t\t\t\t\t\t\tdefer.resolve(err, file.info = info);\n//\t\t\t\t\t\t\t\t\t\t})\n//\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tdefer.resolve(null, file.info = null);\n\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfile.__info.then(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.Image\n\t\t\t\t\tapi.support.transform = true;\n\t\t\t\t\tapi.Image && _inherit(api.Image.prototype, {\n\t\t\t\t\t\tget: function (fn, scaleMode){\n\t\t\t\t\t\t\tthis.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit\n\t\t\t\t\t\t\treturn this.parent(fn);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_load: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._load:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar _this = this;\n\t\t\t\t\t\t\t\tapi.getInfo(file, function (err){\n\t\t\t\t\t\t\t\t\tfn.call(_this, err, file);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_apply: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar m = this.getMatrix(file.info), doneFn = fn;\n\n\t\t\t\t\t\t\t\tflash.cmd(file, 'imageTransform', {\n\t\t\t\t\t\t\t\t\t  id: file.id\n\t\t\t\t\t\t\t\t\t, matrix: m\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply.callback:', err);\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\tdoneFn(err);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){\n\t\t\t\t\t\t\t\t\t\t\t_makeFlashImage({\n\t\t\t\t\t\t\t\t\t\t\t\t  width:\t(m.deg % 180) ? m.dh : m.dw\n\t\t\t\t\t\t\t\t\t\t\t\t, height:\t(m.deg % 180) ? m.dw : m.dh\n\t\t\t\t\t\t\t\t\t\t\t\t, scale:\tm.scaleMode\n\t\t\t\t\t\t\t\t\t\t\t}, base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\tif( m.filter ){\n\t\t\t\t\t\t\t\t\t\t\t\tdoneFn = function (err, img){\n\t\t\t\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tapi.Image.applyFilter(img, m.filter, function (){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err, this.canvas);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tapi.newImage('data:'+ file.type +';base64,'+ base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  file = this.file\n\t\t\t\t\t\t\t\t, info = file.info\n\t\t\t\t\t\t\t\t, matrix = this.getMatrix(info)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image.toData');\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\t\t\t\t\tmatrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfn.call(this, !file.info, {\n\t\t\t\t\t\t\t\t\t  id:\t\tfile.id\n\t\t\t\t\t\t\t\t\t, flashId:\tfile.flashId\n\t\t\t\t\t\t\t\t\t, name:\t\tfile.name\n\t\t\t\t\t\t\t\t\t, type:\t\tfile.type\n\t\t\t\t\t\t\t\t\t, matrix:\tmatrix\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\tapi.Image && _inherit(api.Image, {\n\t\t\t\t\t\tfromDataURL: function (dataURL, size, callback){\n\t\t\t\t\t\t\tif( !api.support.dataURI || dataURL.length > 3e4 ){\n\t\t\t\t\t\t\t\t_makeFlashImage(\n\t\t\t\t\t\t\t\t\t  api.extend({ scale: 'exactFit' }, size)\n\t\t\t\t\t\t\t\t\t, dataURL.replace(/^data:[^,]+,/, '')\n\t\t\t\t\t\t\t\t\t, function (err, el){ callback(el); }\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tthis.parent(dataURL, size, callback);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// FileAPI.Form\n\t\t\t\t\t_inherit(api.Form.prototype, {\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar items = this.items, i = items.length;\n\n\t\t\t\t\t\t\tfor( ; i--; ){\n\t\t\t\t\t\t\t\tif( items[i].file && _isHtmlFile(items[i].blob) ){\n\t\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.log('FlashAPI.Form.toData');\n\t\t\t\t\t\t\tfn(items);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.XHR\n\t\t\t\t\t_inherit(api.XHR.prototype, {\n\t\t\t\t\t\t_send: function (options, formData){\n\t\t\t\t\t\t\tif(\n\t\t\t\t\t\t\t\t   formData.nodeName\n\t\t\t\t\t\t\t\t|| formData.append && api.support.html5\n\t\t\t\t\t\t\t\t|| api.isArray(formData) && (typeof formData[0] === 'string')\n\t\t\t\t\t\t\t){\n\t\t\t\t\t\t\t\t// HTML5, Multipart or IFrame\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  data = {}\n\t\t\t\t\t\t\t\t, files = {}\n\t\t\t\t\t\t\t\t, _this = this\n\t\t\t\t\t\t\t\t, flashId\n\t\t\t\t\t\t\t\t, fileId\n\t\t\t\t\t\t\t;\n\n\t\t\t\t\t\t\t_each(formData, function (item){\n\t\t\t\t\t\t\t\tif( item.file ){\n\t\t\t\t\t\t\t\t\tfiles[item.name] = item = _getFileDescr(item.blob);\n\t\t\t\t\t\t\t\t\tfileId  = item.id;\n\t\t\t\t\t\t\t\t\tflashId = item.flashId;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tdata[item.name] = item.blob;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tif( !fileId ){\n\t\t\t\t\t\t\t\tflashId = _attr;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif( !flashId ){\n\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI._send: flashId -- undefined');\n\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_this.xhr = {\n\t\t\t\t\t\t\t\theaders: {},\n\t\t\t\t\t\t\t\tabort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },\n\t\t\t\t\t\t\t\tgetResponseHeader: function (name){ return this.headers[name]; },\n\t\t\t\t\t\t\t\tgetAllResponseHeaders: function (){ return this.headers; }\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tvar queue = api.queue(function (){\n\t\t\t\t\t\t\t\tflash.uploadInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(flashId, 'upload', {\n\t\t\t\t\t\t\t\t\t  url: _getUrl(options.url.replace(/([a-z]+)=(\\?)&?/i, ''))\n\t\t\t\t\t\t\t\t\t, data: data\n\t\t\t\t\t\t\t\t\t, files: fileId ? files : null\n\t\t\t\t\t\t\t\t\t, headers: options.headers || {}\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function upload(evt){\n\t\t\t\t\t\t\t\t\t\tvar type = evt.type, result = evt.result;\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.upload.'+type);\n\n\t\t\t\t\t\t\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t\t\t\t\t\t\tevt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme\n\t\t\t\t\t\t\t\t\t\t\tevt.lengthComputable = true;\n\t\t\t\t\t\t\t\t\t\t\toptions.progress(evt);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'complete' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\n\t\t\t\t\t\t\t\t\t\t\tif( typeof result == 'string' ){\n\t\t\t\t\t\t\t\t\t\t\t\t_this.responseText\t= result.replace(/%22/g, \"\\\"\").replace(/%5c/g, \"\\\\\").replace(/%26/g, \"&\").replace(/%25/g, \"%\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 200);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'abort' || type == 'error' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 0, evt.message);\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\n\n\t\t\t\t\t\t\t// #2174: FileReference.load() call while FileReference.upload() or vice versa\n\t\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\t\t\tapi.getInfo(file, queue.next);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tqueue.check();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t;\n\n\n\t\tfunction _makeFlashHTML(opts){\n\t\t\treturn ('<object id=\"#id#\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\">'\n\t\t\t\t+ '<param name=\"movie\" value=\"#src#\" />'\n\t\t\t\t+ '<param name=\"flashvars\" value=\"#flashvars#\" />'\n\t\t\t\t+ '<param name=\"swliveconnect\" value=\"true\" />'\n\t\t\t\t+ '<param name=\"allowscriptaccess\" value=\"always\" />'\n\t\t\t\t+ '<param name=\"allownetworking\" value=\"all\" />'\n\t\t\t\t+ '<param name=\"menu\" value=\"false\" />'\n\t\t\t\t+ '<param name=\"wmode\" value=\"#wmode#\" />'\n\t\t\t\t+ '<embed flashvars=\"#flashvars#\" swliveconnect=\"true\" allownetworking=\"all\" allowscriptaccess=\"always\" name=\"#id#\" src=\"#src#\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\" menu=\"false\" wmode=\"transparent\" type=\"application/x-shockwave-flash\"></embed>'\n\t\t\t\t+ '</object>').replace(/#(\\w+)#/ig, function (a, name){ return opts[name]; })\n\t\t\t;\n\t\t}\n\n\n\t\tfunction _css(el, css){\n\t\t\tif( el && el.style ){\n\t\t\t\tvar key, val;\n\t\t\t\tfor( key in css ){\n\t\t\t\t\tval = css[key];\n\t\t\t\t\tif( typeof val == 'number' ){\n\t\t\t\t\t\tval += 'px';\n\t\t\t\t\t}\n\t\t\t\t\ttry { el.style[key] = val; } catch (e) {}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t}\n\t\t}\n\n\n\t\tfunction _inherit(obj, methods){\n\t\t\t_each(methods, function (fn, name){\n\t\t\t\tvar prev = obj[name];\n\t\t\t\tobj[name] = function (){\n\t\t\t\t\tthis.parent = prev;\n\t\t\t\t\treturn fn.apply(this, arguments);\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\n\t\tfunction _isHtmlFile(file){\n\t\t\treturn\tfile && !file.flashId;\n\t\t}\n\n\t\tfunction _wrap(fn){\n\t\t\tvar id = fn.wid = api.uid();\n\t\t\tflash._fn[id] = fn;\n\t\t\treturn\t'FileAPI.Flash._fn.'+id;\n\t\t}\n\n\n\t\tfunction _unwrap(fn){\n\t\t\ttry {\n\t\t\t\tflash._fn[fn.wid] = null;\n\t\t\t\tdelete\tflash._fn[fn.wid];\n\t\t\t}\n\t\t\tcatch(e){}\n\t\t}\n\n\n\t\tfunction _getUrl(url, params){\n\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\tif( /^\\.\\//.test(url) || '/' != url.charAt(0) ){\n\t\t\t\t\tvar path = location.pathname;\n\t\t\t\t\tpath = path.substr(0, path.lastIndexOf('/'));\n\t\t\t\t\turl = (path +'/'+ url).replace('/./', '/');\n\t\t\t\t}\n\n\t\t\t\tif( '//' != url.substr(0, 2) ){\n\t\t\t\t\turl = '//' + location.host + url;\n\t\t\t\t}\n\n\t\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\t\turl = location.protocol + url;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( params ){\n\t\t\t\turl += (/\\?/.test(url) ? '&' : '?') + params;\n\t\t\t}\n\n\t\t\treturn\turl;\n\t\t}\n\n\n\t\tfunction _makeFlashImage(opts, base64, fn){\n\t\t\tvar\n\t\t\t\t  key\n\t\t\t\t, flashId = api.uid()\n\t\t\t\t, el = document.createElement('div')\n\t\t\t\t, attempts = 10\n\t\t\t;\n\n\t\t\tfor( key in opts ){\n\t\t\t\tel.setAttribute(key, opts[key]);\n\t\t\t\tel[key] = opts[key];\n\t\t\t}\n\n\t\t\t_css(el, opts);\n\n\t\t\topts.width\t= '100%';\n\t\t\topts.height\t= '100%';\n\n\t\t\tel.innerHTML = _makeFlashHTML(api.extend({\n\t\t\t\t  id: flashId\n\t\t\t\t, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())\n\t\t\t\t, wmode: 'opaque'\n\t\t\t\t, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){\n\t\t\t\t\t_unwrap(_);\n\t\t\t\t\tif( --attempts > 0 ){\n\t\t\t\t\t\t_setImage();\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t})\n\t\t\t}, opts));\n\n\t\t\tfunction _setImage(){\n\t\t\t\ttry {\n\t\t\t\t\t// Get flash-object by id\n\t\t\t\t\tvar img = flash.get(flashId);\n\t\t\t\t\timg.setImage(base64);\n\t\t\t\t} catch (e){\n\t\t\t\t\tapi.log('[err] FlashAPI.Preview.setImage -- can not set \"base64\":', e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfn(false, el);\n\t\t\tel = null;\n\t\t}\n\n\n\t\tfunction _getFileDescr(file){\n\t\t\treturn\t{\n\t\t\t\t  id: file.id\n\t\t\t\t, name: file.name\n\t\t\t\t, matrix: file.matrix\n\t\t\t\t, flashId: file.flashId\n\t\t\t};\n\t\t}\n\n\n\t\tfunction _getDimensions(el){\n\t\t\tvar\n\t\t\t\t  box = el.getBoundingClientRect()\n\t\t\t\t, body = document.body\n\t\t\t\t, docEl = (el && el.ownerDocument).documentElement\n\t\t\t;\n\t\t\t\n\t\t\tfunction getOffset(obj) {\n\t\t\t    var left, top;\n\t\t\t    left = top = 0;\n\t\t\t    if (obj.offsetParent) {\n\t\t\t        do {\n\t\t\t            left += obj.offsetLeft;\n\t\t\t            top  += obj.offsetTop;\n\t\t\t        } while (obj = obj.offsetParent);\n\t\t\t    }\n\t\t\t    return {\n\t\t\t        left : left,\n\t\t\t        top : top\n\t\t\t    };\n\t\t\t};\n\t\t\t\n\t\t\treturn {\n\t\t\t\t  top:\t\tgetOffset(el).top\n\t\t\t\t, left:\t\tgetOffset(el).left\n\t\t\t\t, width:\tel.offsetWidth\n\t\t\t\t, height:\tel.offsetHeight\n\t\t\t};\n\t\t}\n\n\t\t// @export\n\t\tapi.Flash = flash;\n\n\n\t\t// Check dataURI support\n\t\tapi.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){\n\t\t\tapi.support.dataURI = !(img.width != 1 || img.height != 1);\n\t\t\tflash.init();\n\t\t});\n\t})();\n})(window, window.jQuery, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, FileAPI */\n(function (window, jQuery, api) {\n    \"use strict\";\n\n    var _each = api.each,\n        _cameraQueue = [];\n\n\n    if (api.support.flash && (api.media && !api.support.media)) {\n        (function () {\n\n            function _wrap(fn) {\n                var id = fn.wid = api.uid();\n                api.Flash._fn[id] = fn;\n                return 'FileAPI.Flash._fn.' + id;\n            }\n\n\n            function _unwrap(fn) {\n                try {\n                    api.Flash._fn[fn.wid] = null;\n                    delete api.Flash._fn[fn.wid];\n                } catch (e) {\n                }\n            }\n\n            var flash = api.Flash;\n            api.extend(api.Flash, {\n\n                patchCamera: function () {\n                    api.Camera.fallback = function (el, options, callback) {\n                        var camId = api.uid();\n                        api.log('FlashAPI.Camera.publish: ' + camId);\n                        flash.publish(el, camId, api.extend(options, {\n                            camera: true,\n                            onEvent: _wrap(function _(evt) {\n                                if (evt.type === 'camera') {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);\n                                        callback(evt.error);\n                                    } else {\n                                        api.log('FlashAPI.Camera.publish.success: ' + camId);\n                                        callback(null);\n                                    }\n                                }\n                            })\n                        }));\n                    };\n                    // Run\n                    _each(_cameraQueue, function (args) {\n                        api.Camera.fallback.apply(api.Camera, args);\n                    });\n                    _cameraQueue = [];\n\n\n                    // FileAPI.Camera:proto\n                    api.extend(api.Camera.prototype, {\n                        _id: function () {\n                            return this.video.id;\n                        },\n\n                        start: function (callback) {\n                            var _this = this;\n                            flash.cmd(this._id(), 'camera.on', {\n                                callback: _wrap(function _(evt) {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.camera.on.error: ' + evt.error);\n                                        callback(evt.error, _this);\n                                    } else {\n                                        api.log('FlashAPI.camera.on.success: ' + _this._id());\n                                        _this._active = true;\n                                        callback(null, _this);\n                                    }\n                                })\n                            });\n                        },\n\n                        stop: function () {\n                            this._active = false;\n                            flash.cmd(this._id(), 'camera.off');\n                        },\n\n                        shot: function () {\n                            api.log('FlashAPI.Camera.shot:', this._id());\n\n                            var shot = api.Flash.cmd(this._id(), 'shot', {});\n                            shot.type = 'image/png';\n                            shot.flashId = this._id();\n                            shot.isShot = true;\n\n                            return new api.Camera.Shot(shot);\n                        }\n                    });\n                }\n            });\n\n            api.Camera.fallback = function () {\n                _cameraQueue.push(arguments);\n            };\n\n        }());\n    }\n}(window, window.jQuery, FileAPI));\nif( typeof define === \"function\" && define.amd ){ define(\"FileAPI\", [], function (){ return FileAPI; }); }\n"
  },
  {
    "path": "dist/ng-file-upload-all.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * FileAPI Flash shim for old browsers not supporting FormData\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\n(function () {\n  /** @namespace FileAPI.noContentTimeout */\n\n  function patchXHR(fnName, newFn) {\n    window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);\n  }\n\n  function redefineProp(xhr, prop, fn) {\n    try {\n      Object.defineProperty(xhr, prop, {get: fn});\n    } catch (e) {/*ignore*/\n    }\n  }\n\n  if (!window.FileAPI) {\n    window.FileAPI = {};\n  }\n\n  if (!window.XMLHttpRequest) {\n    throw 'AJAX is not supported. XMLHttpRequest is not defined.';\n  }\n\n  FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;\n  if (FileAPI.shouldLoad) {\n    var initializeUploadListener = function (xhr) {\n      if (!xhr.__listeners) {\n        if (!xhr.upload) xhr.upload = {};\n        xhr.__listeners = [];\n        var origAddEventListener = xhr.upload.addEventListener;\n        xhr.upload.addEventListener = function (t, fn) {\n          xhr.__listeners[t] = fn;\n          if (origAddEventListener) origAddEventListener.apply(this, arguments);\n        };\n      }\n    };\n\n    patchXHR('open', function (orig) {\n      return function (m, url, b) {\n        initializeUploadListener(this);\n        this.__url = url;\n        try {\n          orig.apply(this, [m, url, b]);\n        } catch (e) {\n          if (e.message.indexOf('Access is denied') > -1) {\n            this.__origError = e;\n            orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);\n          }\n        }\n      };\n    });\n\n    patchXHR('getResponseHeader', function (orig) {\n      return function (h) {\n        return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));\n      };\n    });\n\n    patchXHR('getAllResponseHeaders', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('abort', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('setRequestHeader', function (orig) {\n      return function (header, value) {\n        if (header === '__setXHR_') {\n          initializeUploadListener(this);\n          var val = value(this);\n          // fix for angular < 1.2.0\n          if (val instanceof Function) {\n            val(this);\n          }\n        } else {\n          this.__requestHeaders = this.__requestHeaders || {};\n          this.__requestHeaders[header] = value;\n          orig.apply(this, arguments);\n        }\n      };\n    });\n\n    patchXHR('send', function (orig) {\n      return function () {\n        var xhr = this;\n        if (arguments[0] && arguments[0].__isFileAPIShim) {\n          var formData = arguments[0];\n          var config = {\n            url: xhr.__url,\n            jsonp: false, //removes the callback form param\n            cache: true, //removes the ?fileapiXXX in the url\n            complete: function (err, fileApiXHR) {\n              if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {\n                // this error seems to be fine the file is being uploaded properly.\n                err = null;\n              }\n              xhr.__completed = true;\n              if (!err && xhr.__listeners.load)\n                xhr.__listeners.load({\n                  type: 'load',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (!err && xhr.__listeners.loadend)\n                xhr.__listeners.loadend({\n                  type: 'loadend',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (err === 'abort' && xhr.__listeners.abort)\n                xhr.__listeners.abort({\n                  type: 'abort',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {\n                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;\n              });\n              if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {\n                return fileApiXHR.statusText;\n              });\n              redefineProp(xhr, 'readyState', function () {\n                return 4;\n              });\n              if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {\n                return fileApiXHR.response;\n              });\n              var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);\n              redefineProp(xhr, 'responseText', function () {\n                return resp;\n              });\n              redefineProp(xhr, 'response', function () {\n                return resp;\n              });\n              if (err) redefineProp(xhr, 'err', function () {\n                return err;\n              });\n              xhr.__fileApiXHR = fileApiXHR;\n              if (xhr.onreadystatechange) xhr.onreadystatechange();\n              if (xhr.onload) xhr.onload();\n            },\n            progress: function (e) {\n              e.target = xhr;\n              if (xhr.__listeners.progress) xhr.__listeners.progress(e);\n              xhr.__total = e.total;\n              xhr.__loaded = e.loaded;\n              if (e.total === e.loaded) {\n                // fix flash issue that doesn't call complete if there is no response text from the server\n                var _this = this;\n                setTimeout(function () {\n                  if (!xhr.__completed) {\n                    xhr.getAllResponseHeaders = function () {\n                    };\n                    _this.complete(null, {status: 204, statusText: 'No Content'});\n                  }\n                }, FileAPI.noContentTimeout || 10000);\n              }\n            },\n            headers: xhr.__requestHeaders\n          };\n          config.data = {};\n          config.files = {};\n          for (var i = 0; i < formData.data.length; i++) {\n            var item = formData.data[i];\n            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {\n              config.files[item.key] = item.val;\n            } else {\n              config.data[item.key] = item.val;\n            }\n          }\n\n          setTimeout(function () {\n            if (!FileAPI.hasFlash) {\n              throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n            }\n            xhr.__fileApiXHR = FileAPI.upload(config);\n          }, 1);\n        } else {\n          if (this.__origError) {\n            throw this.__origError;\n          }\n          orig.apply(xhr, arguments);\n        }\n      };\n    });\n    window.XMLHttpRequest.__isFileAPIShim = true;\n    window.FormData = FormData = function () {\n      return {\n        append: function (key, val, name) {\n          if (val.__isFileAPIBlobShim) {\n            val = val.data[0];\n          }\n          this.data.push({\n            key: key,\n            val: val,\n            name: name\n          });\n        },\n        data: [],\n        __isFileAPIShim: true\n      };\n    };\n\n    window.Blob = Blob = function (b) {\n      return {\n        data: b,\n        __isFileAPIBlobShim: true\n      };\n    };\n  }\n\n})();\n\n(function () {\n  /** @namespace FileAPI.forceLoad */\n  /** @namespace window.FileAPI.jsUrl */\n  /** @namespace window.FileAPI.jsPath */\n\n  function isInputTypeFile(elem) {\n    return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';\n  }\n\n  function hasFlash() {\n    try {\n      var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n      if (fo) return true;\n    } catch (e) {\n      if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;\n    }\n    return false;\n  }\n\n  function getOffset(obj) {\n    var left = 0, top = 0;\n\n    if (window.jQuery) {\n      return jQuery(obj).offset();\n    }\n\n    if (obj.offsetParent) {\n      do {\n        left += (obj.offsetLeft - obj.scrollLeft);\n        top += (obj.offsetTop - obj.scrollTop);\n        obj = obj.offsetParent;\n      } while (obj);\n    }\n    return {\n      left: left,\n      top: top\n    };\n  }\n\n  if (FileAPI.shouldLoad) {\n    FileAPI.hasFlash = hasFlash();\n\n    //load FileAPI\n    if (FileAPI.forceLoad) {\n      FileAPI.html5 = false;\n    }\n\n    if (!FileAPI.upload) {\n      var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;\n      if (window.FileAPI.jsUrl) {\n        jsUrl = window.FileAPI.jsUrl;\n      } else if (window.FileAPI.jsPath) {\n        basePath = window.FileAPI.jsPath;\n      } else {\n        for (i = 0; i < allScripts.length; i++) {\n          src = allScripts[i].src;\n          index = src.search(/\\/ng\\-file\\-upload[\\-a-zA-z0-9\\.]*\\.js/);\n          if (index > -1) {\n            basePath = src.substring(0, index + 1);\n            break;\n          }\n        }\n      }\n\n      if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;\n      script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');\n      document.getElementsByTagName('head')[0].appendChild(script);\n    }\n\n    FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {\n      if (!hasFlash()) {\n        throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n      }\n      var fixInputStyle = function () {\n        var label = fileElem.parent();\n        if (elem.attr('disabled')) {\n          if (label) label.removeClass('js-fileapi-wrapper');\n        } else {\n          if (!fileElem.attr('__ngf_flash_')) {\n            fileElem.unbind('change');\n            fileElem.unbind('click');\n            fileElem.bind('change', function (evt) {\n              fileApiChangeFn.apply(this, [evt]);\n              changeFn.apply(this, [evt]);\n            });\n            fileElem.attr('__ngf_flash_', 'true');\n          }\n          label.addClass('js-fileapi-wrapper');\n          if (!isInputTypeFile(elem)) {\n            label.css('position', 'absolute')\n              .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')\n              .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))\n              .css('overflow', 'hidden').css('z-index', '900000')\n              .css('visibility', 'visible');\n            fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('position', 'absolute').css('top', '0px').css('left', '0px');\n          }\n        }\n      };\n\n      elem.bind('mouseenter', fixInputStyle);\n\n      var fileApiChangeFn = function (evt) {\n        var files = FileAPI.getFiles(evt);\n        //just a double check for #233\n        for (var i = 0; i < files.length; i++) {\n          if (files[i].size === undefined) files[i].size = 0;\n          if (files[i].name === undefined) files[i].name = 'file';\n          if (files[i].type === undefined) files[i].type = 'undefined';\n        }\n        if (!evt.target) {\n          evt.target = {};\n        }\n        evt.target.files = files;\n        // if evt.target.files is not writable use helper field\n        if (evt.target.files !== files) {\n          evt.__files_ = files;\n        }\n        (evt.__files_ || evt.target.files).item = function (i) {\n          return (evt.__files_ || evt.target.files)[i] || null;\n        };\n      };\n    };\n\n    FileAPI.disableFileInput = function (elem, disable) {\n      if (disable) {\n        elem.removeClass('js-fileapi-wrapper');\n      } else {\n        elem.addClass('js-fileapi-wrapper');\n      }\n    };\n  }\n})();\n\nif (!window.FileReader) {\n  window.FileReader = function () {\n    var _this = this, loadStarted = false;\n    this.listeners = {};\n    this.addEventListener = function (type, fn) {\n      _this.listeners[type] = _this.listeners[type] || [];\n      _this.listeners[type].push(fn);\n    };\n    this.removeEventListener = function (type, fn) {\n      if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);\n    };\n    this.dispatchEvent = function (evt) {\n      var list = _this.listeners[evt.type];\n      if (list) {\n        for (var i = 0; i < list.length; i++) {\n          list[i].call(_this, evt);\n        }\n      }\n    };\n    this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;\n\n    var constructEvent = function (type, evt) {\n      var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};\n      if (evt.result != null) e.target.result = evt.result;\n      return e;\n    };\n    var listener = function (evt) {\n      if (!loadStarted) {\n        loadStarted = true;\n        if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));\n      }\n      var e;\n      if (evt.type === 'load') {\n        if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));\n        e = constructEvent('load', evt);\n        if (_this.onload) _this.onload(e);\n        _this.dispatchEvent(e);\n      } else if (evt.type === 'progress') {\n        e = constructEvent('progress', evt);\n        if (_this.onprogress) _this.onprogress(e);\n        _this.dispatchEvent(e);\n      } else {\n        e = constructEvent('error', evt);\n        if (_this.onerror) _this.onerror(e);\n        _this.dispatchEvent(e);\n      }\n    };\n    this.readAsDataURL = function (file) {\n      FileAPI.readAsDataURL(file, listener);\n    };\n    this.readAsText = function (file) {\n      FileAPI.readAsText(file, listener);\n    };\n  };\n}\n\n/**!\n * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\nif (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {\n  window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {\n    return function (header, value) {\n      if (header === '__setXHR_') {\n        var val = value(this);\n        // fix for angular < 1.2.0\n        if (val instanceof Function) {\n          val(this);\n        }\n      } else {\n        orig.apply(this, arguments);\n      }\n    };\n  })(window.XMLHttpRequest.prototype.setRequestHeader);\n}\n\nvar ngFileUpload = angular.module('ngFileUpload', []);\n\nngFileUpload.version = '12.2.13';\n\nngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {\n  var upload = this;\n  upload.promisesCount = 0;\n\n  this.isResumeSupported = function () {\n    return window.Blob && window.Blob.prototype.slice;\n  };\n\n  var resumeSupported = this.isResumeSupported();\n\n  function sendHttp(config) {\n    config.method = config.method || 'POST';\n    config.headers = config.headers || {};\n\n    var deferred = config._deferred = config._deferred || $q.defer();\n    var promise = deferred.promise;\n\n    function notifyProgress(e) {\n      if (deferred.notify) {\n        deferred.notify(e);\n      }\n      if (promise.progressFunc) {\n        $timeout(function () {\n          promise.progressFunc(e);\n        });\n      }\n    }\n\n    function getNotifyEvent(n) {\n      if (config._start != null && resumeSupported) {\n        return {\n          loaded: n.loaded + config._start,\n          total: (config._file && config._file.size) || n.total,\n          type: n.type, config: config,\n          lengthComputable: true, target: n.target\n        };\n      } else {\n        return n;\n      }\n    }\n\n    if (!config.disableProgress) {\n      config.headers.__setXHR_ = function () {\n        return function (xhr) {\n          if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;\n          config.__XHR = xhr;\n          if (config.xhrFn) config.xhrFn(xhr);\n          xhr.upload.addEventListener('progress', function (e) {\n            e.config = config;\n            notifyProgress(getNotifyEvent(e));\n          }, false);\n          //fix for firefox not firing upload progress end, also IE8-9\n          xhr.upload.addEventListener('load', function (e) {\n            if (e.lengthComputable) {\n              e.config = config;\n              notifyProgress(getNotifyEvent(e));\n            }\n          }, false);\n        };\n      };\n    }\n\n    function uploadWithAngular() {\n      $http(config).then(function (r) {\n          if (resumeSupported && config._chunkSize && !config._finished && config._file) {\n            var fileSize = config._file && config._file.size || 0;\n            notifyProgress({\n                loaded: Math.min(config._end, fileSize),\n                total: fileSize,\n                config: config,\n                type: 'progress'\n              }\n            );\n            upload.upload(config, true);\n          } else {\n            if (config._finished) delete config._finished;\n            deferred.resolve(r);\n          }\n        }, function (e) {\n          deferred.reject(e);\n        }, function (n) {\n          deferred.notify(n);\n        }\n      );\n    }\n\n    if (!resumeSupported) {\n      uploadWithAngular();\n    } else if (config._chunkSize && config._end && !config._finished) {\n      config._start = config._end;\n      config._end += config._chunkSize;\n      uploadWithAngular();\n    } else if (config.resumeSizeUrl) {\n      $http.get(config.resumeSizeUrl).then(function (resp) {\n        if (config.resumeSizeResponseReader) {\n          config._start = config.resumeSizeResponseReader(resp.data);\n        } else {\n          config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());\n        }\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else if (config.resumeSize) {\n      config.resumeSize().then(function (size) {\n        config._start = size;\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else {\n      if (config._chunkSize) {\n        config._start = 0;\n        config._end = config._start + config._chunkSize;\n      }\n      uploadWithAngular();\n    }\n\n\n    promise.success = function (fn) {\n      promise.then(function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.error = function (fn) {\n      promise.then(null, function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.progress = function (fn) {\n      promise.progressFunc = fn;\n      promise.then(null, null, function (n) {\n        fn(n);\n      });\n      return promise;\n    };\n    promise.abort = promise.pause = function () {\n      if (config.__XHR) {\n        $timeout(function () {\n          config.__XHR.abort();\n        });\n      }\n      return promise;\n    };\n    promise.xhr = function (fn) {\n      config.xhrFn = (function (origXhrFn) {\n        return function () {\n          if (origXhrFn) origXhrFn.apply(promise, arguments);\n          fn.apply(promise, arguments);\n        };\n      })(config.xhrFn);\n      return promise;\n    };\n\n    upload.promisesCount++;\n    if (promise['finally'] && promise['finally'] instanceof Function) {\n      promise['finally'](function () {\n        upload.promisesCount--;\n      });\n    }\n    return promise;\n  }\n\n  this.isUploadInProgress = function () {\n    return upload.promisesCount > 0;\n  };\n\n  this.rename = function (file, name) {\n    file.ngfName = name;\n    return file;\n  };\n\n  this.jsonBlob = function (val) {\n    if (val != null && !angular.isString(val)) {\n      val = JSON.stringify(val);\n    }\n    var blob = new window.Blob([val], {type: 'application/json'});\n    blob._ngfBlob = true;\n    return blob;\n  };\n\n  this.json = function (val) {\n    return angular.toJson(val);\n  };\n\n  function copy(obj) {\n    var clone = {};\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        clone[key] = obj[key];\n      }\n    }\n    return clone;\n  }\n\n  this.isFile = function (file) {\n    return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));\n  };\n\n  this.upload = function (config, internal) {\n    function toResumeFile(file, formData) {\n      if (file._ngfBlob) return file;\n      config._file = config._file || file;\n      if (config._start != null && resumeSupported) {\n        if (config._end && config._end >= file.size) {\n          config._finished = true;\n          config._end = file.size;\n        }\n        var slice = file.slice(config._start, config._end || file.size);\n        slice.name = file.name;\n        slice.ngfName = file.ngfName;\n        if (config._chunkSize) {\n          formData.append('_chunkSize', config._chunkSize);\n          formData.append('_currentChunkSize', config._end - config._start);\n          formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));\n          formData.append('_totalSize', config._file.size);\n        }\n        return slice;\n      }\n      return file;\n    }\n\n    function addFieldToFormData(formData, val, key) {\n      if (val !== undefined) {\n        if (angular.isDate(val)) {\n          val = val.toISOString();\n        }\n        if (angular.isString(val)) {\n          formData.append(key, val);\n        } else if (upload.isFile(val)) {\n          var file = toResumeFile(val, formData);\n          var split = key.split(',');\n          if (split[1]) {\n            file.ngfName = split[1].replace(/^\\s+|\\s+$/g, '');\n            key = split[0];\n          }\n          config._fileKey = config._fileKey || key;\n          formData.append(key, file, file.ngfName || file.name);\n        } else {\n          if (angular.isObject(val)) {\n            if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;\n\n            val.$$ngfCircularDetection = true;\n            try {\n              for (var k in val) {\n                if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {\n                  var objectKey = config.objectKey == null ? '[i]' : config.objectKey;\n                  if (val.length && parseInt(k) > -1) {\n                    objectKey = config.arrayKey == null ? objectKey : config.arrayKey;\n                  }\n                  addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));\n                }\n              }\n            } finally {\n              delete val.$$ngfCircularDetection;\n            }\n          } else {\n            formData.append(key, val);\n          }\n        }\n      }\n    }\n\n    function digestConfig() {\n      config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n      config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n      config.headers = config.headers || {};\n      config.headers['Content-Type'] = undefined;\n      config.transformRequest = config.transformRequest ?\n        (angular.isArray(config.transformRequest) ?\n          config.transformRequest : [config.transformRequest]) : [];\n      config.transformRequest.push(function (data) {\n        var formData = new window.FormData(), key;\n        data = data || config.fields || {};\n        if (config.file) {\n          data.file = config.file;\n        }\n        for (key in data) {\n          if (data.hasOwnProperty(key)) {\n            var val = data[key];\n            if (config.formDataAppender) {\n              config.formDataAppender(formData, key, val);\n            } else {\n              addFieldToFormData(formData, val, key);\n            }\n          }\n        }\n\n        return formData;\n      });\n    }\n\n    if (!internal) config = copy(config);\n    if (!config._isDigested) {\n      config._isDigested = true;\n      digestConfig();\n    }\n\n    return sendHttp(config);\n  };\n\n  this.http = function (config) {\n    config = copy(config);\n    config.transformRequest = config.transformRequest || function (data) {\n        if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {\n          return data;\n        }\n        return $http.defaults.transformRequest[0].apply(this, arguments);\n      };\n    config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n    config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n    return sendHttp(config);\n  };\n\n  this.translateScalars = function (str) {\n    if (angular.isString(str)) {\n      if (str.search(/kb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1024);\n      } else if (str.search(/mb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1048576);\n      } else if (str.search(/gb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1073741824);\n      } else if (str.search(/b/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/s/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/m/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 60);\n      } else if (str.search(/h/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 3600);\n      }\n    }\n    return str;\n  };\n\n  this.urlToBlob = function(url) {\n    var defer = $q.defer();\n    $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {\n      var arrayBufferView = new Uint8Array(resp.data);\n      var type = resp.headers('content-type') || 'image/WebP';\n      var blob = new window.Blob([arrayBufferView], {type: type});\n      var matches = url.match(/.*\\/(.+?)(\\?.*)?$/);\n      if (matches.length > 1) {\n        blob.name = matches[1];\n      }\n      defer.resolve(blob);\n    }, function (e) {\n      defer.reject(e);\n    });\n    return defer.promise;\n  };\n\n  this.setDefaults = function (defaults) {\n    this.defaults = defaults || {};\n  };\n\n  this.defaults = {};\n  this.version = ngFileUpload.version;\n}\n\n]);\n\nngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {\n  var upload = UploadExif;\n  upload.getAttrWithDefaults = function (attr, name) {\n    if (attr[name] != null) return attr[name];\n    var def = upload.defaults[name];\n    return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));\n  };\n\n  upload.attrGetter = function (name, attr, scope, params) {\n    var attrVal = this.getAttrWithDefaults(attr, name);\n    if (scope) {\n      try {\n        if (params) {\n          return $parse(attrVal)(scope, params);\n        } else {\n          return $parse(attrVal)(scope);\n        }\n      } catch (e) {\n        // hangle string value without single qoute\n        if (name.search(/min|max|pattern/i)) {\n          return attrVal;\n        } else {\n          throw e;\n        }\n      }\n    } else {\n      return attrVal;\n    }\n  };\n\n  upload.shouldUpdateOn = function (type, attr, scope) {\n    var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);\n    if (modelOptions && modelOptions.updateOn) {\n      return modelOptions.updateOn.split(' ').indexOf(type) > -1;\n    }\n    return true;\n  };\n\n  upload.emptyPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.resolve.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.rejectPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.reject.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.happyPromise = function (promise, data) {\n    var d = $q.defer();\n    promise.then(function (result) {\n      d.resolve(result);\n    }, function (error) {\n      $timeout(function () {\n        throw error;\n      });\n      d.resolve(data);\n    });\n    return d.promise;\n  };\n\n  function applyExifRotations(files, attr, scope) {\n    var promises = [upload.emptyPromise()];\n    angular.forEach(files, function (f, i) {\n      if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {\n        promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {\n          files.splice(i, 1, fixedFile);\n        }));\n      }\n    });\n    return $q.all(promises);\n  }\n\n  function resizeFile(files, attr, scope, ngModel) {\n    var resizeVal = upload.attrGetter('ngfResize', attr, scope);\n    if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();\n    if (resizeVal instanceof Function) {\n      var defer = $q.defer();\n      return resizeVal(files).then(function (p) {\n        resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {\n          defer.resolve(r);\n        }, function (e) {\n          defer.reject(e);\n        });\n      }, function (e) {\n        defer.reject(e);\n      });\n    } else {\n      return resizeWithParams(resizeVal, files, attr, scope, ngModel);\n    }\n  }\n\n  function resizeWithParams(params, files, attr, scope, ngModel) {\n    var promises = [upload.emptyPromise()];\n\n    function handleFile(f, i) {\n      if (f.type.indexOf('image') === 0) {\n        if (params.pattern && !upload.validatePattern(f, params.pattern)) return;\n        params.resizeIf = function (width, height) {\n          return upload.attrGetter('ngfResizeIf', attr, scope,\n            {$width: width, $height: height, $file: f});\n        };\n        var promise = upload.resize(f, params);\n        promises.push(promise);\n        promise.then(function (resizedFile) {\n          files.splice(i, 1, resizedFile);\n        }, function (e) {\n          f.$error = 'resize';\n          (f.$errorMessages = (f.$errorMessages || {})).resize = true;\n          f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);\n          ngModel.$ngfValidations.push({name: 'resize', valid: false});\n          upload.applyModelValidation(ngModel, files);\n        });\n      }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n      handleFile(files[i], i);\n    }\n    return $q.all(promises);\n  }\n\n  upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {\n    function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {\n      attr.$$ngfPrevValidFiles = files;\n      attr.$$ngfPrevInvalidFiles = invalidFiles;\n      var file = files && files.length ? files[0] : null;\n      var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;\n\n      if (ngModel) {\n        upload.applyModelValidation(ngModel, files);\n        ngModel.$setViewValue(isSingleModel ? file : files);\n      }\n\n      if (fileChange) {\n        $parse(fileChange)(scope, {\n          $files: files,\n          $file: file,\n          $newFiles: newFiles,\n          $duplicateFiles: dupFiles,\n          $invalidFiles: invalidFiles,\n          $invalidFile: invalidFile,\n          $event: evt\n        });\n      }\n\n      var invalidModel = upload.attrGetter('ngfModelInvalid', attr);\n      if (invalidModel) {\n        $timeout(function () {\n          $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);\n        });\n      }\n      $timeout(function () {\n        // scope apply changes\n      });\n    }\n\n    var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,\n      invalids = [], valids = [];\n\n    function removeDuplicates() {\n      function equals(f1, f2) {\n        return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&\n          f1.type === f2.type;\n      }\n\n      function isInPrevFiles(f) {\n        var j;\n        for (j = 0; j < prevValidFiles.length; j++) {\n          if (equals(f, prevValidFiles[j])) {\n            return true;\n          }\n        }\n        for (j = 0; j < prevInvalidFiles.length; j++) {\n          if (equals(f, prevInvalidFiles[j])) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n      if (files) {\n        allNewFiles = [];\n        dupFiles = [];\n        for (var i = 0; i < files.length; i++) {\n          if (isInPrevFiles(files[i])) {\n            dupFiles.push(files[i]);\n          } else {\n            allNewFiles.push(files[i]);\n          }\n        }\n      }\n    }\n\n    function toArray(v) {\n      return angular.isArray(v) ? v : [v];\n    }\n\n    function resizeAndUpdate() {\n      function updateModel() {\n        $timeout(function () {\n          update(keep ? prevValidFiles.concat(valids) : valids,\n            keep ? prevInvalidFiles.concat(invalids) : invalids,\n            files, dupFiles, isSingleModel);\n        }, options && options.debounce ? options.debounce.change || options.debounce : 0);\n      }\n\n      var resizingFiles = validateAfterResize ? allNewFiles : valids;\n      resizeFile(resizingFiles, attr, scope, ngModel).then(function () {\n        if (validateAfterResize) {\n          upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n            .then(function (validationResult) {\n              valids = validationResult.validsFiles;\n              invalids = validationResult.invalidsFiles;\n              updateModel();\n            });\n        } else {\n          updateModel();\n        }\n      }, function () {\n        for (var i = 0; i < resizingFiles.length; i++) {\n          var f = resizingFiles[i];\n          if (f.$error === 'resize') {\n            var index = valids.indexOf(f);\n            if (index > -1) {\n              valids.splice(index, 1);\n              invalids.push(f);\n            }\n            updateModel();\n          }\n        }\n      });\n    }\n\n    prevValidFiles = attr.$$ngfPrevValidFiles || [];\n    prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];\n    if (ngModel && ngModel.$modelValue) {\n      prevValidFiles = toArray(ngModel.$modelValue);\n    }\n\n    var keep = upload.attrGetter('ngfKeep', attr, scope);\n    allNewFiles = (files || []).slice(0);\n    if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {\n      removeDuplicates(attr, scope);\n    }\n\n    var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);\n\n    if (keep && !allNewFiles.length) return;\n\n    upload.attrGetter('ngfBeforeModelChange', attr, scope, {\n      $files: files,\n      $file: files && files.length ? files[0] : null,\n      $newFiles: allNewFiles,\n      $duplicateFiles: dupFiles,\n      $event: evt\n    });\n\n    var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);\n\n    var options = upload.attrGetter('ngfModelOptions', attr, scope);\n    upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n      .then(function (validationResult) {\n      if (noDelay) {\n        update(allNewFiles, [], files, dupFiles, isSingleModel);\n      } else {\n        if ((!options || !options.allowInvalid) && !validateAfterResize) {\n          valids = validationResult.validFiles;\n          invalids = validationResult.invalidFiles;\n        } else {\n          valids = allNewFiles;\n        }\n        if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {\n          applyExifRotations(valids, attr, scope).then(function () {\n            resizeAndUpdate();\n          });\n        } else {\n          resizeAndUpdate();\n        }\n      }\n    });\n  };\n\n  return upload;\n}]);\n\nngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {\n  var generatedElems = [];\n\n  function isDelayedClickSupported(ua) {\n    // fix for android native browser < 4.4 and safari windows\n    var m = ua.match(/Android[^\\d]*(\\d+)\\.(\\d+)/);\n    if (m && m.length > 2) {\n      var v = Upload.defaults.androidFixMinorVersion || 4;\n      return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);\n    }\n\n    // safari on windows\n    return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);\n  }\n\n  function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {\n    /** @namespace attr.ngfSelect */\n    /** @namespace attr.ngfChange */\n    /** @namespace attr.ngModel */\n    /** @namespace attr.ngfModelOptions */\n    /** @namespace attr.ngfMultiple */\n    /** @namespace attr.ngfCapture */\n    /** @namespace attr.ngfValidate */\n    /** @namespace attr.ngfKeep */\n    var attrGetter = function (name, scope) {\n      return upload.attrGetter(name, attr, scope);\n    };\n\n    function isInputTypeFile() {\n      return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';\n    }\n\n    function fileChangeAttr() {\n      return attrGetter('ngfChange') || attrGetter('ngfSelect');\n    }\n\n    function changeFn(evt) {\n      if (upload.shouldUpdateOn('change', attr, scope)) {\n        var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];\n        /* Handle duplicate call in  IE11 */\n        if (!fileList) return;\n        for (var i = 0; i < fileList.length; i++) {\n          files.push(fileList[i]);\n        }\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(),\n          files.length ? files : null, evt);\n      }\n    }\n\n    upload.registerModelChangeValidator(ngModel, attr, scope);\n\n    var unwatches = [];\n    if (attrGetter('ngfMultiple')) {\n      unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {\n        fileElem.attr('multiple', attrGetter('ngfMultiple', scope));\n      }));\n    }\n    if (attrGetter('ngfCapture')) {\n      unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {\n        fileElem.attr('capture', attrGetter('ngfCapture', scope));\n      }));\n    }\n    if (attrGetter('ngfAccept')) {\n      unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {\n        fileElem.attr('accept', attrGetter('ngfAccept', scope));\n      }));\n    }\n    unwatches.push(attr.$observe('accept', function () {\n      fileElem.attr('accept', attrGetter('accept'));\n    }));\n    function bindAttrToFileInput(fileElem, label) {\n      function updateId(val) {\n        fileElem.attr('id', 'ngf-' + val);\n        label.attr('id', 'ngf-label-' + val);\n      }\n\n      for (var i = 0; i < elem[0].attributes.length; i++) {\n        var attribute = elem[0].attributes[i];\n        if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {\n          if (attribute.name === 'id') {\n            updateId(attribute.value);\n            unwatches.push(attr.$observe('id', updateId));\n          } else {\n            fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||\n            attribute.name === 'multiple')) ? attribute.name : attribute.value);\n          }\n        }\n      }\n    }\n\n    function createFileInput() {\n      if (isInputTypeFile()) {\n        return elem;\n      }\n\n      var fileElem = angular.element('<input type=\"file\">');\n\n      var label = angular.element('<label>upload</label>');\n      label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')\n        .css('width', '0px').css('height', '0px').css('border', 'none')\n        .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');\n      bindAttrToFileInput(fileElem, label);\n\n      generatedElems.push({el: elem, ref: label});\n\n      document.body.appendChild(label.append(fileElem)[0]);\n\n      return fileElem;\n    }\n\n    function clickHandler(evt) {\n      if (elem.attr('disabled')) return false;\n      if (attrGetter('ngfSelectDisabled', scope)) return;\n\n      var r = detectSwipe(evt);\n      // prevent the click if it is a swipe\n      if (r != null) return r;\n\n      resetModel(evt);\n\n      // fix for md when the element is removed from the DOM and added back #460\n      try {\n        if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {\n          generatedElems.push({el: elem, ref: fileElem.parent()});\n          document.body.appendChild(fileElem.parent()[0]);\n          fileElem.bind('change', changeFn);\n        }\n      } catch (e) {/*ignore*/\n      }\n\n      if (isDelayedClickSupported(navigator.userAgent)) {\n        setTimeout(function () {\n          fileElem[0].click();\n        }, 0);\n      } else {\n        fileElem[0].click();\n      }\n\n      return false;\n    }\n\n\n    var initialTouchStartY = 0;\n    var initialTouchStartX = 0;\n\n    function detectSwipe(evt) {\n      var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);\n      if (touches) {\n        if (evt.type === 'touchstart') {\n          initialTouchStartX = touches[0].clientX;\n          initialTouchStartY = touches[0].clientY;\n          return true; // don't block event default\n        } else {\n          // prevent scroll from triggering event\n          if (evt.type === 'touchend') {\n            var currentX = touches[0].clientX;\n            var currentY = touches[0].clientY;\n            if ((Math.abs(currentX - initialTouchStartX) > 20) ||\n              (Math.abs(currentY - initialTouchStartY) > 20)) {\n              evt.stopPropagation();\n              evt.preventDefault();\n              return false;\n            }\n          }\n          return true;\n        }\n      }\n    }\n\n    var fileElem = elem;\n\n    function resetModel(evt) {\n      if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {\n        fileElem.val(null);\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);\n      }\n    }\n\n    if (!isInputTypeFile()) {\n      fileElem = createFileInput();\n    }\n    fileElem.bind('change', changeFn);\n\n    if (!isInputTypeFile()) {\n      elem.bind('click touchstart touchend', clickHandler);\n    } else {\n      elem.bind('click', resetModel);\n    }\n\n    function ie10SameFileSelectFix(evt) {\n      if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {\n        if (!fileElem[0].parentNode) {\n          fileElem = null;\n          return;\n        }\n        evt.preventDefault();\n        evt.stopPropagation();\n        fileElem.unbind('click');\n        var clone = fileElem.clone();\n        fileElem.replaceWith(clone);\n        fileElem = clone;\n        fileElem.attr('__ngf_ie10_Fix_', 'true');\n        fileElem.bind('change', changeFn);\n        fileElem.bind('click', ie10SameFileSelectFix);\n        fileElem[0].click();\n        return false;\n      } else {\n        fileElem.removeAttr('__ngf_ie10_Fix_');\n      }\n    }\n\n    if (navigator.appVersion.indexOf('MSIE 10') !== -1) {\n      fileElem.bind('click', ie10SameFileSelectFix);\n    }\n\n    if (ngModel) ngModel.$formatters.push(function (val) {\n      if (val == null || val.length === 0) {\n        if (fileElem.val()) {\n          fileElem.val(null);\n        }\n      }\n      return val;\n    });\n\n    scope.$on('$destroy', function () {\n      if (!isInputTypeFile()) fileElem.parent().remove();\n      angular.forEach(unwatches, function (unwatch) {\n        unwatch();\n      });\n    });\n\n    $timeout(function () {\n      for (var i = 0; i < generatedElems.length; i++) {\n        var g = generatedElems[i];\n        if (!document.body.contains(g.el[0])) {\n          generatedElems.splice(i, 1);\n          g.ref.remove();\n        }\n      }\n    });\n\n    if (window.FileAPI && window.FileAPI.ngfFixIE) {\n      window.FileAPI.ngfFixIE(elem, fileElem, changeFn);\n    }\n  }\n\n  return {\n    restrict: 'AEC',\n    require: '?ngModel',\n    link: function (scope, elem, attr, ngModel) {\n      linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);\n    }\n  };\n}]);\n\n(function () {\n\n  ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {\n    var upload = UploadBase;\n    upload.base64DataUrl = function (file) {\n      if (angular.isArray(file)) {\n        var d = $q.defer(), count = 0;\n        angular.forEach(file, function (f) {\n          upload.dataUrl(f, true)['finally'](function () {\n            count++;\n            if (count === file.length) {\n              var urls = [];\n              angular.forEach(file, function (ff) {\n                urls.push(ff.$ngfDataUrl);\n              });\n              d.resolve(urls, file);\n            }\n          });\n        });\n        return d.promise;\n      } else {\n        return upload.dataUrl(file, true);\n      }\n    };\n    upload.dataUrl = function (file, disallowObjectUrl) {\n      if (!file) return upload.emptyPromise(file, file);\n      if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {\n        return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);\n      }\n      var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;\n      if (p) return p;\n\n      var deferred = $q.defer();\n      $timeout(function () {\n        if (window.FileReader && file &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {\n          //prefer URL.createObjectURL for handling refrences to files of all sizes\n          //since it doesn´t build a large string in memory\n          var URL = window.URL || window.webkitURL;\n          if (URL && URL.createObjectURL && !disallowObjectUrl) {\n            var url;\n            try {\n              url = URL.createObjectURL(file);\n            } catch (e) {\n              $timeout(function () {\n                file.$ngfBlobUrl = '';\n                deferred.reject();\n              });\n              return;\n            }\n            $timeout(function () {\n              file.$ngfBlobUrl = url;\n              if (url) {\n                deferred.resolve(url, file);\n                upload.blobUrls = upload.blobUrls || [];\n                upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;\n                upload.blobUrls.push({url: url, size: file.size});\n                upload.blobUrlsTotalSize += file.size || 0;\n                var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;\n                var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;\n                while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {\n                  var obj = upload.blobUrls.splice(0, 1)[0];\n                  URL.revokeObjectURL(obj.url);\n                  upload.blobUrlsTotalSize -= obj.size;\n                }\n              }\n            });\n          } else {\n            var fileReader = new FileReader();\n            fileReader.onload = function (e) {\n              $timeout(function () {\n                file.$ngfDataUrl = e.target.result;\n                deferred.resolve(e.target.result, file);\n                $timeout(function () {\n                  delete file.$ngfDataUrl;\n                }, 1000);\n              });\n            };\n            fileReader.onerror = function () {\n              $timeout(function () {\n                file.$ngfDataUrl = '';\n                deferred.reject();\n              });\n            };\n            fileReader.readAsDataURL(file);\n          }\n        } else {\n          $timeout(function () {\n            file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';\n            deferred.reject();\n          });\n        }\n      });\n\n      if (disallowObjectUrl) {\n        p = file.$$ngfDataUrlPromise = deferred.promise;\n      } else {\n        p = file.$$ngfBlobUrlPromise = deferred.promise;\n      }\n      p['finally'](function () {\n        delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];\n      });\n      return p;\n    };\n    return upload;\n  }]);\n\n  function getTagType(el) {\n    if (el.tagName.toLowerCase() === 'img') return 'image';\n    if (el.tagName.toLowerCase() === 'audio') return 'audio';\n    if (el.tagName.toLowerCase() === 'video') return 'video';\n    return /./;\n  }\n\n  function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {\n    function constructDataUrl(file) {\n      var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);\n      Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {\n        $timeout(function () {\n          var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;\n          if (isBackground) {\n            elem.css('background-image', 'url(\\'' + (src || '') + '\\')');\n          } else {\n            elem.attr('src', src);\n          }\n          if (src) {\n            elem.removeClass('ng-hide');\n          } else {\n            elem.addClass('ng-hide');\n          }\n        });\n      });\n    }\n\n    $timeout(function () {\n      var unwatch = scope.$watch(attr[directiveName], function (file) {\n        var size = resizeParams;\n        if (directiveName === 'ngfThumbnail') {\n          if (!size) {\n            size = {\n              width: elem[0].naturalWidth || elem[0].clientWidth,\n              height: elem[0].naturalHeight || elem[0].clientHeight\n            };\n          }\n          if (size.width === 0 && window.getComputedStyle) {\n            var style = getComputedStyle(elem[0]);\n            if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {\n              size = {\n                width: parseInt(style.width.slice(0, -2)),\n                height: parseInt(style.height.slice(0, -2))\n              };\n            }\n          }\n        }\n\n        if (angular.isString(file)) {\n          elem.removeClass('ng-hide');\n          if (isBackground) {\n            return elem.css('background-image', 'url(\\'' + file + '\\')');\n          } else {\n            return elem.attr('src', file);\n          }\n        }\n        if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&\n          (!isBackground || file.type.indexOf('image') === 0)) {\n          if (size && Upload.isResizeSupported()) {\n            size.resizeIf = function (width, height) {\n              return Upload.attrGetter('ngfResizeIf', attr, scope,\n                {$width: width, $height: height, $file: file});\n            };\n            Upload.resize(file, size).then(\n              function (f) {\n                constructDataUrl(f);\n              }, function (e) {\n                throw e;\n              }\n            );\n          } else {\n            constructDataUrl(file);\n          }\n        } else {\n          elem.addClass('ng-hide');\n        }\n      });\n\n      scope.$on('$destroy', function () {\n        unwatch();\n      });\n    });\n  }\n\n\n  /** @namespace attr.ngfSrc */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',\n          Upload.attrGetter('ngfResize', attr, scope), false);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfBackground */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',\n          Upload.attrGetter('ngfResize', attr, scope), true);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfThumbnail */\n  /** @namespace attr.ngfAsBackground */\n  /** @namespace attr.ngfSize */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        var size = Upload.attrGetter('ngfSize', attr, scope);\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,\n          Upload.attrGetter('ngfAsBackground', attr, scope));\n      }\n    };\n  }]);\n\n  ngFileUpload.config(['$compileProvider', function ($compileProvider) {\n    if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n    if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n  }]);\n\n  ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {\n    return function (file, disallowObjectUrl, trustedUrl) {\n      if (angular.isString(file)) {\n        return $sce.trustAsResourceUrl(file);\n      }\n      var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);\n      if (file && !src) {\n        if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {\n          file.$ngfDataUrlFilterInProgress = true;\n          UploadDataUrl.dataUrl(file, disallowObjectUrl);\n        }\n        return '';\n      }\n      if (file) delete file.$ngfDataUrlFilterInProgress;\n      return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';\n    };\n  }]);\n\n})();\n\nngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {\n  var upload = UploadDataUrl;\n\n  function globStringToRegex(str) {\n    var regexp = '', excludes = [];\n    if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {\n      regexp = str.substring(1, str.length - 1);\n    } else {\n      var split = str.split(',');\n      if (split.length > 1) {\n        for (var i = 0; i < split.length; i++) {\n          var r = globStringToRegex(split[i]);\n          if (r.regexp) {\n            regexp += '(' + r.regexp + ')';\n            if (i < split.length - 1) {\n              regexp += '|';\n            }\n          } else {\n            excludes = excludes.concat(r.excludes);\n          }\n        }\n      } else {\n        if (str.indexOf('!') === 0) {\n          excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');\n        } else {\n          if (str.indexOf('.') === 0) {\n            str = '*' + str;\n          }\n          regexp = '^' + str.replace(new RegExp('[.\\\\\\\\+*?\\\\[\\\\^\\\\]$(){}=!<>|:\\\\-]', 'g'), '\\\\$&') + '$';\n          regexp = regexp.replace(/\\\\\\*/g, '.*').replace(/\\\\\\?/g, '.');\n        }\n      }\n    }\n    return {regexp: regexp, excludes: excludes};\n  }\n\n  upload.validatePattern = function (file, val) {\n    if (!val) {\n      return true;\n    }\n    var pattern = globStringToRegex(val), valid = true;\n    if (pattern.regexp && pattern.regexp.length) {\n      var regexp = new RegExp(pattern.regexp, 'i');\n      valid = (file.type != null && regexp.test(file.type)) ||\n        (file.name != null && regexp.test(file.name));\n    }\n    var len = pattern.excludes.length;\n    while (len--) {\n      var exclude = new RegExp(pattern.excludes[len], 'i');\n      valid = valid && (file.type == null || exclude.test(file.type)) &&\n        (file.name == null || exclude.test(file.name));\n    }\n    return valid;\n  };\n\n  upload.ratioToFloat = function (val) {\n    var r = val.toString(), xIndex = r.search(/[x:]/i);\n    if (xIndex > -1) {\n      r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));\n    } else {\n      r = parseFloat(r);\n    }\n    return r;\n  };\n\n  upload.registerModelChangeValidator = function (ngModel, attr, scope) {\n    if (ngModel) {\n      ngModel.$formatters.push(function (files) {\n        if (ngModel.$dirty) {\n          var filesArray = files;\n          if (files && !angular.isArray(files)) {\n            filesArray = [files];\n          }\n          upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {\n            upload.applyModelValidation(ngModel, filesArray);\n          });\n        }\n        return files;\n      });\n    }\n  };\n\n  function markModelAsDirty(ngModel, files) {\n    if (files != null && !ngModel.$dirty) {\n      if (ngModel.$setDirty) {\n        ngModel.$setDirty();\n      } else {\n        ngModel.$dirty = true;\n      }\n    }\n  }\n\n  upload.applyModelValidation = function (ngModel, files) {\n    markModelAsDirty(ngModel, files);\n    angular.forEach(ngModel.$ngfValidations, function (validation) {\n      ngModel.$setValidity(validation.name, validation.valid);\n    });\n  };\n\n  upload.getValidationAttr = function (attr, scope, name, validationName, file) {\n    var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);\n    var val = upload.attrGetter(dName, attr, scope, {$file: file});\n    if (val == null) {\n      val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});\n      if (val) {\n        var split = (validationName || name).split('.');\n        val = val[split[0]];\n        if (split.length > 1) {\n          val = val && val[split[1]];\n        }\n      }\n    }\n    return val;\n  };\n\n  upload.validate = function (files, prevLength, ngModel, attr, scope) {\n    ngModel = ngModel || {};\n    ngModel.$ngfValidations = ngModel.$ngfValidations || [];\n\n    angular.forEach(ngModel.$ngfValidations, function (v) {\n      v.valid = true;\n    });\n\n    var attrGetter = function (name, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');\n    var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);\n\n    if (files == null || files.length === 0) {\n      return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});\n    }\n\n    files = files.length === undefined ? [files] : files.slice(0);\n    var invalidFiles = [];\n\n    function validateSync(name, validationName, fn) {\n      if (files) {\n        var i = files.length, valid = null;\n        while (i--) {\n          var file = files[i];\n          if (file) {\n            var val = upload.getValidationAttr(attr, scope, name, validationName, file);\n            if (val != null) {\n              if (!fn(file, val, i)) {\n                if (ignoredErrors.indexOf(name) === -1) {\n                  file.$error = name;\n                  (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n                  file.$errorParam = val;\n                  if (invalidFiles.indexOf(file) === -1) {\n                    invalidFiles.push(file);\n                  }\n                  if (!runAllValidation) {\n                    files.splice(i, 1);\n                  }\n                  valid = false;\n                } else {\n                  files.splice(i, 1);\n                }\n              }\n            }\n          }\n        }\n        if (valid !== null) {\n          ngModel.$ngfValidations.push({name: name, valid: valid});\n        }\n      }\n    }\n\n    validateSync('pattern', null, upload.validatePattern);\n    validateSync('minSize', 'size.min', function (file, val) {\n      return file.size + 0.1 >= upload.translateScalars(val);\n    });\n    validateSync('maxSize', 'size.max', function (file, val) {\n      return file.size - 0.1 <= upload.translateScalars(val);\n    });\n    var totalSize = 0;\n    validateSync('maxTotalSize', null, function (file, val) {\n      totalSize += file.size;\n      if (totalSize > upload.translateScalars(val)) {\n        files.splice(0, files.length);\n        return false;\n      }\n      return true;\n    });\n\n    validateSync('validateFn', null, function (file, r) {\n      return r === true || r === null || r === '';\n    });\n\n    if (!files.length) {\n      return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});\n    }\n\n    function validateAsync(name, validationName, type, asyncFn, fn) {\n      function resolveResult(defer, file, val) {\n        function resolveInternal(fn) {\n          if (fn()) {\n            if (ignoredErrors.indexOf(name) === -1) {\n              file.$error = name;\n              (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n              file.$errorParam = val;\n              if (invalidFiles.indexOf(file) === -1) {\n                invalidFiles.push(file);\n              }\n              if (!runAllValidation) {\n                var i = files.indexOf(file);\n                if (i > -1) files.splice(i, 1);\n              }\n              defer.resolve(false);\n            } else {\n              var j = files.indexOf(file);\n              if (j > -1) files.splice(j, 1);\n              defer.resolve(true);\n            }\n          } else {\n            defer.resolve(true);\n          }\n        }\n\n        if (val != null) {\n          asyncFn(file, val).then(function (d) {\n            resolveInternal(function () {\n              return !fn(d, val);\n            });\n          }, function () {\n            resolveInternal(function () {\n              return attrGetter('ngfValidateForce', {$file: file});\n            });\n          });\n        } else {\n          defer.resolve(true);\n        }\n      }\n\n      var promises = [upload.emptyPromise(true)];\n      if (files) {\n        files = files.length === undefined ? [files] : files;\n        angular.forEach(files, function (file) {\n          var defer = $q.defer();\n          promises.push(defer.promise);\n          if (type && (file.type == null || file.type.search(type) !== 0)) {\n            defer.resolve(true);\n            return;\n          }\n          if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {\n            upload.imageDimensions(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {\n            upload.mediaDuration(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDuration', {$file: file, $duration: d}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else {\n            resolveResult(defer, file,\n              upload.getValidationAttr(attr, scope, name, validationName, file));\n          }\n        });\n      }\n      var deffer = $q.defer();\n      $q.all(promises).then(function (values) {\n        var isValid = true;\n        for (var i = 0; i < values.length; i++) {\n          if (!values[i]) {\n            isValid = false;\n            break;\n          }\n        }\n        ngModel.$ngfValidations.push({name: name, valid: isValid});\n        deffer.resolve(isValid);\n      });\n      return deffer.promise;\n    }\n\n    var deffer = $q.defer();\n    var promises = [];\n\n    promises.push(validateAsync('maxHeight', 'height.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height <= val;\n      }));\n    promises.push(validateAsync('minHeight', 'height.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height >= val;\n      }));\n    promises.push(validateAsync('maxWidth', 'width.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width <= val;\n      }));\n    promises.push(validateAsync('minWidth', 'width.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width >= val;\n      }));\n    promises.push(validateAsync('dimensions', null, /image/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n    promises.push(validateAsync('ratio', null, /image/,\n      this.imageDimensions, function (d, val) {\n        var split = val.toString().split(','), valid = false;\n        for (var i = 0; i < split.length; i++) {\n          if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {\n            valid = true;\n          }\n        }\n        return valid;\n      }));\n    promises.push(validateAsync('maxRatio', 'ratio.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;\n      }));\n    promises.push(validateAsync('minRatio', 'ratio.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;\n      }));\n    promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d <= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d >= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('duration', null, /audio|video/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n\n    promises.push(validateAsync('validateAsyncFn', null, null,\n      function (file, val) {\n        return val;\n      }, function (r) {\n        return r === true || r === null || r === '';\n      }));\n\n    $q.all(promises).then(function () {\n\n      if (runAllValidation) {\n        for (var i = 0; i < files.length; i++) {\n          var file = files[i];\n          if (file.$error) {\n            files.splice(i--, 1);\n          }\n        }\n      }\n\n      runAllValidation = false;\n      validateSync('maxFiles', null, function (file, val, i) {\n        return prevLength + i < val;\n      });\n\n      deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});\n    });\n    return deffer.promise;\n  };\n\n  upload.imageDimensions = function (file) {\n    if (file.$ngfWidth && file.$ngfHeight) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});\n      });\n      return d.promise;\n    }\n    if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('image') !== 0) {\n        deferred.reject('not image');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var img = angular.element('<img>').attr('src', dataUrl)\n          .css('visibility', 'hidden').css('position', 'fixed')\n          .css('max-width', 'none !important').css('max-height', 'none !important');\n\n        function success() {\n          var width = img[0].naturalWidth || img[0].clientWidth;\n          var height = img[0].naturalHeight || img[0].clientHeight;\n          img.remove();\n          file.$ngfWidth = width;\n          file.$ngfHeight = height;\n          deferred.resolve({width: width, height: height});\n        }\n\n        function error() {\n          img.remove();\n          deferred.reject('load error');\n        }\n\n        img.on('load', success);\n        img.on('error', error);\n\n        var secondsCounter = 0;\n        function checkLoadErrorInCaseOfNoCallback() {\n          $timeout(function () {\n            if (img[0].parentNode) {\n              if (img[0].clientWidth) {\n                success();\n              } else if (secondsCounter++ > 10) {\n                error();\n              } else {\n                checkLoadErrorInCaseOfNoCallback();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadErrorInCaseOfNoCallback();\n\n        angular.element(document.getElementsByTagName('body')[0]).append(img);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDimensionPromise = deferred.promise;\n    file.$ngfDimensionPromise['finally'](function () {\n      delete file.$ngfDimensionPromise;\n    });\n    return file.$ngfDimensionPromise;\n  };\n\n  upload.mediaDuration = function (file) {\n    if (file.$ngfDuration) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve(file.$ngfDuration);\n      });\n      return d.promise;\n    }\n    if (file.$ngfDurationPromise) return file.$ngfDurationPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {\n        deferred.reject('not media');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')\n          .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');\n\n        function success() {\n          var duration = el[0].duration;\n          file.$ngfDuration = duration;\n          el.remove();\n          deferred.resolve(duration);\n        }\n\n        function error() {\n          el.remove();\n          deferred.reject('load error');\n        }\n\n        el.on('loadedmetadata', success);\n        el.on('error', error);\n        var count = 0;\n\n        function checkLoadError() {\n          $timeout(function () {\n            if (el[0].parentNode) {\n              if (el[0].duration) {\n                success();\n              } else if (count > 10) {\n                error();\n              } else {\n                checkLoadError();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadError();\n\n        angular.element(document.body).append(el);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDurationPromise = deferred.promise;\n    file.$ngfDurationPromise['finally'](function () {\n      delete file.$ngfDurationPromise;\n    });\n    return file.$ngfDurationPromise;\n  };\n  return upload;\n}\n]);\n\nngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {\n  var upload = UploadValidate;\n\n  /**\n   * Conserve aspect ratio of the original region. Useful when shrinking/enlarging\n   * images to fit into a certain area.\n   * Source:  http://stackoverflow.com/a/14731922\n   *\n   * @param {Number} srcWidth Source area width\n   * @param {Number} srcHeight Source area height\n   * @param {Number} maxWidth Nestable area maximum available width\n   * @param {Number} maxHeight Nestable area maximum available height\n   * @return {Object} { width, height }\n   */\n  var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {\n    var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :\n      Math.min(maxWidth / srcWidth, maxHeight / srcHeight);\n    return {\n      width: srcWidth * ratio, height: srcHeight * ratio,\n      marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight\n    };\n  };\n\n  // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89\n  var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {\n    var deferred = $q.defer();\n    var canvasElement = document.createElement('canvas');\n    var imageElement = document.createElement('img');\n    imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');\n    document.body.appendChild(imageElement);\n\n    imageElement.onload = function () {\n      var imgWidth = imageElement.width, imgHeight = imageElement.height;\n      imageElement.parentNode.removeChild(imageElement);\n      if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {\n        deferred.reject('resizeIf');\n        return;\n      }\n      try {\n        if (ratio) {\n          var ratioFloat = upload.ratioToFloat(ratio);\n          var imgRatio = imgWidth / imgHeight;\n          if (imgRatio < ratioFloat) {\n            width = imgWidth;\n            height = width / ratioFloat;\n          } else {\n            height = imgHeight;\n            width = height * ratioFloat;\n          }\n        }\n        if (!width) {\n          width = imgWidth;\n        }\n        if (!height) {\n          height = imgHeight;\n        }\n        var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);\n        canvasElement.width = Math.min(dimensions.width, width);\n        canvasElement.height = Math.min(dimensions.height, height);\n        var context = canvasElement.getContext('2d');\n        context.drawImage(imageElement,\n          Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),\n          dimensions.width, dimensions.height);\n        deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));\n      } catch (e) {\n        deferred.reject(e);\n      }\n    };\n    imageElement.onerror = function () {\n      imageElement.parentNode.removeChild(imageElement);\n      deferred.reject();\n    };\n    imageElement.src = imagen;\n    return deferred.promise;\n  };\n\n  upload.dataUrltoBlob = function (dataurl, name, origSize) {\n    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],\n      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n);\n    }\n    var blob = new window.Blob([u8arr], {type: mime});\n    blob.name = name;\n    blob.$ngfOrigSize = origSize;\n    return blob;\n  };\n\n  upload.isResizeSupported = function () {\n    var elem = document.createElement('canvas');\n    return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;\n  };\n\n  if (upload.isResizeSupported()) {\n    // add name getter to the blob constructor prototype\n    Object.defineProperty(window.Blob.prototype, 'name', {\n      get: function () {\n        return this.$ngfName;\n      },\n      set: function (v) {\n        this.$ngfName = v;\n      },\n      configurable: true\n    });\n  }\n\n  upload.resize = function (file, options) {\n    if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);\n\n    var deferred = $q.defer();\n    upload.dataUrl(file, true).then(function (url) {\n      resize(url, options.width, options.height, options.quality, options.type || file.type,\n        options.ratio, options.centerCrop, options.resizeIf)\n        .then(function (dataUrl) {\n          if (file.type === 'image/jpeg' && options.restoreExif !== false) {\n            try {\n              dataUrl = upload.restoreExif(url, dataUrl);\n            } catch (e) {\n              setTimeout(function () {throw e;}, 1);\n            }\n          }\n          try {\n            var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);\n            deferred.resolve(blob);\n          } catch (e) {\n            deferred.reject(e);\n          }\n        }, function (r) {\n          if (r === 'resizeIf') {\n            deferred.resolve(file);\n          }\n          deferred.reject(r);\n        });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  return upload;\n}]);\n\n(function () {\n  ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',\n    function ($parse, $timeout, $window, Upload, $http, $q) {\n      return {\n        restrict: 'AEC',\n        require: '?ngModel',\n        link: function (scope, elem, attr, ngModel) {\n          linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);\n        }\n      };\n    }]);\n\n  ngFileUpload.directive('ngfNoFileDrop', function () {\n    return function (scope, elem) {\n      if (dropAvailable()) elem.css('display', 'none');\n    };\n  });\n\n  ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {\n    return function (scope, elem, attr) {\n      if (dropAvailable()) {\n        var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));\n        $timeout(function () {\n          model(scope);\n          if (model.assign) {\n            model.assign(scope, true);\n          }\n        });\n      }\n    };\n  }]);\n\n  function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {\n    var available = dropAvailable();\n\n    var attrGetter = function (name, scope, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    if (attrGetter('dropAvailable')) {\n      $timeout(function () {\n        if (scope[attrGetter('dropAvailable')]) {\n          scope[attrGetter('dropAvailable')].value = available;\n        } else {\n          scope[attrGetter('dropAvailable')] = available;\n        }\n      });\n    }\n    if (!available) {\n      if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {\n        elem.css('display', 'none');\n      }\n      return;\n    }\n\n    function isDisabled() {\n      return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);\n    }\n\n    if (attrGetter('ngfSelect') == null) {\n      upload.registerModelChangeValidator(ngModel, attr, scope);\n    }\n\n    var leaveTimeout = null;\n    var stopPropagation = $parse(attrGetter('ngfStopPropagation'));\n    var dragOverDelay = 1;\n    var actualDragOverClass;\n\n    elem[0].addEventListener('dragover', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      // handling dragover events from the Chrome download bar\n      if (navigator.userAgent.indexOf('Chrome') > -1) {\n        var b = evt.dataTransfer.effectAllowed;\n        evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';\n      }\n      $timeout.cancel(leaveTimeout);\n      if (!actualDragOverClass) {\n        actualDragOverClass = 'C';\n        calculateDragOverClass(scope, attr, evt, function (clazz) {\n          actualDragOverClass = clazz;\n          elem.addClass(actualDragOverClass);\n          attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});\n        });\n      }\n    }, false);\n    elem[0].addEventListener('dragenter', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n    }, false);\n    elem[0].addEventListener('dragleave', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      leaveTimeout = $timeout(function () {\n        if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n        actualDragOverClass = null;\n        attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});\n      }, dragOverDelay || 100);\n    }, false);\n    elem[0].addEventListener('drop', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n      actualDragOverClass = null;\n      extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');\n    }, false);\n    elem[0].addEventListener('paste', function (evt) {\n      if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n        attrGetter('ngfEnableFirefoxPaste', scope)) {\n        evt.preventDefault();\n      }\n      if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;\n      extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');\n    }, false);\n\n    if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n      attrGetter('ngfEnableFirefoxPaste', scope)) {\n      elem.attr('contenteditable', true);\n      elem.on('keypress', function (e) {\n        if (!e.metaKey && !e.ctrlKey) {\n          e.preventDefault();\n        }\n      });\n    }\n\n    function extractFilesAndUpdateModel(source, evt, updateOnType) {\n      if (!source) return;\n      // html needs to be calculated on the same process otherwise the data will be wiped\n      // after promise resolve or setTimeout.\n      var html;\n      try {\n        html = source && source.getData && source.getData('text/html');\n      } catch (e) {/* Fix IE11 that throw error calling getData */\n      }\n      extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,\n        attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {\n        if (files.length) {\n          updateModel(files, evt);\n        } else {\n          extractFilesFromHtml(updateOnType, html).then(function (files) {\n            updateModel(files, evt);\n          });\n        }\n      });\n    }\n\n    function updateModel(files, evt) {\n      upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);\n    }\n\n    function extractFilesFromHtml(updateOn, html) {\n      if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);\n      var urls = [];\n      html.replace(/<(img src|img [^>]* src) *=\\\"([^\\\"]*)\\\"/gi, function (m, n, src) {\n        urls.push(src);\n      });\n      var promises = [], files = [];\n      if (urls.length) {\n        angular.forEach(urls, function (url) {\n          promises.push(upload.urlToBlob(url).then(function (blob) {\n            files.push(blob);\n          }));\n        });\n        var defer = $q.defer();\n        $q.all(promises).then(function () {\n          defer.resolve(files);\n        }, function (e) {\n          defer.reject(e);\n        });\n        return defer.promise;\n      }\n      return upload.emptyPromise();\n    }\n\n    function calculateDragOverClass(scope, attr, evt, callback) {\n      var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';\n      if (angular.isString(obj)) {\n        dClass = obj;\n      } else if (obj) {\n        if (obj.delay) dragOverDelay = obj.delay;\n        if (obj.accept || obj.reject) {\n          var items = evt.dataTransfer.items;\n          if (items == null || !items.length) {\n            dClass = obj.accept;\n          } else {\n            var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});\n            var len = items.length;\n            while (len--) {\n              if (!upload.validatePattern(items[len], pattern)) {\n                dClass = obj.reject;\n                break;\n              } else {\n                dClass = obj.accept;\n              }\n            }\n          }\n        }\n      }\n      callback(dClass);\n    }\n\n    function extractFiles(items, fileList, allowDir, multiple) {\n      var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');\n      if (maxFiles == null) {\n        maxFiles = Number.MAX_VALUE;\n      }\n      var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');\n      if (maxTotalSize == null) {\n        maxTotalSize = Number.MAX_VALUE;\n      }\n      var includeDir = attrGetter('ngfIncludeDir', scope);\n      var files = [], totalSize = 0;\n\n      function traverseFileTree(entry, path) {\n        var defer = $q.defer();\n        if (entry != null) {\n          if (entry.isDirectory) {\n            var promises = [upload.emptyPromise()];\n            if (includeDir) {\n              var file = {type: 'directory'};\n              file.name = file.path = (path || '') + entry.name;\n              files.push(file);\n            }\n            var dirReader = entry.createReader();\n            var entries = [];\n            var readEntries = function () {\n              dirReader.readEntries(function (results) {\n                try {\n                  if (!results.length) {\n                    angular.forEach(entries.slice(0), function (e) {\n                      if (files.length <= maxFiles && totalSize <= maxTotalSize) {\n                        promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));\n                      }\n                    });\n                    $q.all(promises).then(function () {\n                      defer.resolve();\n                    }, function (e) {\n                      defer.reject(e);\n                    });\n                  } else {\n                    entries = entries.concat(Array.prototype.slice.call(results || [], 0));\n                    readEntries();\n                  }\n                } catch (e) {\n                  defer.reject(e);\n                }\n              }, function (e) {\n                defer.reject(e);\n              });\n            };\n            readEntries();\n          } else {\n            entry.file(function (file) {\n              try {\n                file.path = (path ? path : '') + file.name;\n                if (includeDir) {\n                  file = upload.rename(file, file.path);\n                }\n                files.push(file);\n                totalSize += file.size;\n                defer.resolve();\n              } catch (e) {\n                defer.reject(e);\n              }\n            }, function (e) {\n              defer.reject(e);\n            });\n          }\n        }\n        return defer.promise;\n      }\n\n      var promises = [upload.emptyPromise()];\n\n      if (items && items.length > 0 && $window.location.protocol !== 'file:') {\n        for (var i = 0; i < items.length; i++) {\n          if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {\n            var entry = items[i].webkitGetAsEntry();\n            if (entry.isDirectory && !allowDir) {\n              continue;\n            }\n            if (entry != null) {\n              promises.push(traverseFileTree(entry));\n            }\n          } else {\n            var f = items[i].getAsFile();\n            if (f != null) {\n              files.push(f);\n              totalSize += f.size;\n            }\n          }\n          if (files.length > maxFiles || totalSize > maxTotalSize ||\n            (!multiple && files.length > 0)) break;\n        }\n      } else {\n        if (fileList != null) {\n          for (var j = 0; j < fileList.length; j++) {\n            var file = fileList.item(j);\n            if (file.type || file.size > 0) {\n              files.push(file);\n              totalSize += file.size;\n            }\n            if (files.length > maxFiles || totalSize > maxTotalSize ||\n              (!multiple && files.length > 0)) break;\n          }\n        }\n      }\n\n      var defer = $q.defer();\n      $q.all(promises).then(function () {\n        if (!multiple && !includeDir && files.length) {\n          var i = 0;\n          while (files[i] && files[i].type === 'directory') i++;\n          defer.resolve([files[i]]);\n        } else {\n          defer.resolve(files);\n        }\n      }, function (e) {\n        defer.reject(e);\n      });\n\n      return defer.promise;\n    }\n  }\n\n  function dropAvailable() {\n    var div = document.createElement('div');\n    return ('draggable' in div) && ('ondrop' in div) && !/Edge\\/12./i.test(navigator.userAgent);\n  }\n\n})();\n\n// customized version of https://github.com/exif-js/exif-js\nngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {\n  var upload = UploadResize;\n\n  upload.isExifSupported = function () {\n    return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();\n  };\n\n  function applyTransform(ctx, orientation, width, height) {\n    switch (orientation) {\n      case 2:\n        return ctx.transform(-1, 0, 0, 1, width, 0);\n      case 3:\n        return ctx.transform(-1, 0, 0, -1, width, height);\n      case 4:\n        return ctx.transform(1, 0, 0, -1, 0, height);\n      case 5:\n        return ctx.transform(0, 1, 1, 0, 0, 0);\n      case 6:\n        return ctx.transform(0, 1, -1, 0, height, 0);\n      case 7:\n        return ctx.transform(0, -1, -1, 0, height, width);\n      case 8:\n        return ctx.transform(0, -1, 1, 0, 0, width);\n    }\n  }\n\n  upload.readOrientation = function (file) {\n    var defer = $q.defer();\n    var reader = new FileReader();\n    var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;\n    reader.readAsArrayBuffer(slicedFile);\n    reader.onerror = function (e) {\n      return defer.reject(e);\n    };\n    reader.onload = function (e) {\n      var result = {orientation: 1};\n      var view = new DataView(this.result);\n      if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);\n\n      var length = view.byteLength,\n        offset = 2;\n      while (offset < length) {\n        var marker = view.getUint16(offset, false);\n        offset += 2;\n        if (marker === 0xFFE1) {\n          if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);\n\n          var little = view.getUint16(offset += 6, false) === 0x4949;\n          offset += view.getUint32(offset + 4, little);\n          var tags = view.getUint16(offset, little);\n          offset += 2;\n          for (var i = 0; i < tags; i++)\n            if (view.getUint16(offset + (i * 12), little) === 0x0112) {\n              var orientation = view.getUint16(offset + (i * 12) + 8, little);\n              if (orientation >= 2 && orientation <= 8) {\n                view.setUint16(offset + (i * 12) + 8, 1, little);\n                result.fixedArrayBuffer = e.target.result;\n              }\n              result.orientation = orientation;\n              return defer.resolve(result);\n            }\n        } else if ((marker & 0xFF00) !== 0xFF00) break;\n        else offset += view.getUint16(offset, false);\n      }\n      return defer.resolve(result);\n    };\n    return defer.promise;\n  };\n\n  function arrayBufferToBase64(buffer) {\n    var binary = '';\n    var bytes = new Uint8Array(buffer);\n    var len = bytes.byteLength;\n    for (var i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return window.btoa(binary);\n  }\n\n  upload.applyExifRotation = function (file) {\n    if (file.type.indexOf('image/jpeg') !== 0) {\n      return upload.emptyPromise(file);\n    }\n\n    var deferred = $q.defer();\n    upload.readOrientation(file).then(function (result) {\n      if (result.orientation < 2 || result.orientation > 8) {\n        return deferred.resolve(file);\n      }\n      upload.dataUrl(file, true).then(function (url) {\n        var canvas = document.createElement('canvas');\n        var img = document.createElement('img');\n\n        img.onload = function () {\n          try {\n            canvas.width = result.orientation > 4 ? img.height : img.width;\n            canvas.height = result.orientation > 4 ? img.width : img.height;\n            var ctx = canvas.getContext('2d');\n            applyTransform(ctx, result.orientation, img.width, img.height);\n            ctx.drawImage(img, 0, 0);\n            var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);\n            dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);\n            var blob = upload.dataUrltoBlob(dataUrl, file.name);\n            deferred.resolve(blob);\n          } catch (e) {\n            return deferred.reject(e);\n          }\n        };\n        img.onerror = function () {\n          deferred.reject();\n        };\n        img.src = url;\n      }, function (e) {\n        deferred.reject(e);\n      });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  upload.restoreExif = function (orig, resized) {\n    var ExifRestorer = {};\n\n    ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +\n      'QRSTUVWXYZabcdef' +\n      'ghijklmnopqrstuv' +\n      'wxyz0123456789+/' +\n      '=';\n\n    ExifRestorer.encode64 = function (input) {\n      var output = '',\n        chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0;\n\n      do {\n        chr1 = input[i++];\n        chr2 = input[i++];\n        chr3 = input[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n          enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n          enc4 = 64;\n        }\n\n        output = output +\n          this.KEY_STR.charAt(enc1) +\n          this.KEY_STR.charAt(enc2) +\n          this.KEY_STR.charAt(enc3) +\n          this.KEY_STR.charAt(enc4);\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n      } while (i < input.length);\n\n      return output;\n    };\n\n    ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {\n      if (origFileBase64.match('data:image/jpeg;base64,')) {\n        origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');\n      }\n\n      var rawImage = this.decode64(origFileBase64);\n      var segments = this.slice2Segments(rawImage);\n\n      var image = this.exifManipulation(resizedFileBase64, segments);\n\n      return 'data:image/jpeg;base64,' + this.encode64(image);\n    };\n\n\n    ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {\n      var exifArray = this.getExifArray(segments),\n        newImageArray = this.insertExif(resizedFileBase64, exifArray);\n      return new Uint8Array(newImageArray);\n    };\n\n\n    ExifRestorer.getExifArray = function (segments) {\n      var seg;\n      for (var x = 0; x < segments.length; x++) {\n        seg = segments[x];\n        if (seg[0] === 255 & seg[1] === 225) //(ff e1)\n        {\n          return seg;\n        }\n      }\n      return [];\n    };\n\n\n    ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {\n      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),\n        buf = this.decode64(imageData),\n        separatePoint = buf.indexOf(255, 3),\n        mae = buf.slice(0, separatePoint),\n        ato = buf.slice(separatePoint),\n        array = mae;\n\n      array = array.concat(exifArray);\n      array = array.concat(ato);\n      return array;\n    };\n\n\n    ExifRestorer.slice2Segments = function (rawImageArray) {\n      var head = 0,\n        segments = [];\n\n      while (1) {\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {\n          break;\n        }\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {\n          head += 2;\n        }\n        else {\n          var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n            endPoint = head + length + 2,\n            seg = rawImageArray.slice(head, endPoint);\n          segments.push(seg);\n          head = endPoint;\n        }\n        if (head > rawImageArray.length) {\n          break;\n        }\n      }\n\n      return segments;\n    };\n\n\n    ExifRestorer.decode64 = function (input) {\n      var chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0,\n        buf = [];\n\n      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n      var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n      if (base64test.exec(input)) {\n        console.log('There were invalid base64 characters in the input text.\\n' +\n          'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and \"=\"\\n' +\n          'Expect errors in decoding.');\n      }\n      input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, '');\n\n      do {\n        enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        buf.push(chr1);\n\n        if (enc3 !== 64) {\n          buf.push(chr2);\n        }\n        if (enc4 !== 64) {\n          buf.push(chr3);\n        }\n\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n\n      } while (i < input.length);\n\n      return buf;\n    };\n\n    return ExifRestorer.restore(orig, resized);  //<= EXIF\n  };\n\n  return upload;\n}]);\n\n"
  },
  {
    "path": "dist/ng-file-upload-shim.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * FileAPI Flash shim for old browsers not supporting FormData\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\n(function () {\n  /** @namespace FileAPI.noContentTimeout */\n\n  function patchXHR(fnName, newFn) {\n    window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);\n  }\n\n  function redefineProp(xhr, prop, fn) {\n    try {\n      Object.defineProperty(xhr, prop, {get: fn});\n    } catch (e) {/*ignore*/\n    }\n  }\n\n  if (!window.FileAPI) {\n    window.FileAPI = {};\n  }\n\n  if (!window.XMLHttpRequest) {\n    throw 'AJAX is not supported. XMLHttpRequest is not defined.';\n  }\n\n  FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;\n  if (FileAPI.shouldLoad) {\n    var initializeUploadListener = function (xhr) {\n      if (!xhr.__listeners) {\n        if (!xhr.upload) xhr.upload = {};\n        xhr.__listeners = [];\n        var origAddEventListener = xhr.upload.addEventListener;\n        xhr.upload.addEventListener = function (t, fn) {\n          xhr.__listeners[t] = fn;\n          if (origAddEventListener) origAddEventListener.apply(this, arguments);\n        };\n      }\n    };\n\n    patchXHR('open', function (orig) {\n      return function (m, url, b) {\n        initializeUploadListener(this);\n        this.__url = url;\n        try {\n          orig.apply(this, [m, url, b]);\n        } catch (e) {\n          if (e.message.indexOf('Access is denied') > -1) {\n            this.__origError = e;\n            orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);\n          }\n        }\n      };\n    });\n\n    patchXHR('getResponseHeader', function (orig) {\n      return function (h) {\n        return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));\n      };\n    });\n\n    patchXHR('getAllResponseHeaders', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('abort', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('setRequestHeader', function (orig) {\n      return function (header, value) {\n        if (header === '__setXHR_') {\n          initializeUploadListener(this);\n          var val = value(this);\n          // fix for angular < 1.2.0\n          if (val instanceof Function) {\n            val(this);\n          }\n        } else {\n          this.__requestHeaders = this.__requestHeaders || {};\n          this.__requestHeaders[header] = value;\n          orig.apply(this, arguments);\n        }\n      };\n    });\n\n    patchXHR('send', function (orig) {\n      return function () {\n        var xhr = this;\n        if (arguments[0] && arguments[0].__isFileAPIShim) {\n          var formData = arguments[0];\n          var config = {\n            url: xhr.__url,\n            jsonp: false, //removes the callback form param\n            cache: true, //removes the ?fileapiXXX in the url\n            complete: function (err, fileApiXHR) {\n              if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {\n                // this error seems to be fine the file is being uploaded properly.\n                err = null;\n              }\n              xhr.__completed = true;\n              if (!err && xhr.__listeners.load)\n                xhr.__listeners.load({\n                  type: 'load',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (!err && xhr.__listeners.loadend)\n                xhr.__listeners.loadend({\n                  type: 'loadend',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (err === 'abort' && xhr.__listeners.abort)\n                xhr.__listeners.abort({\n                  type: 'abort',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {\n                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;\n              });\n              if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {\n                return fileApiXHR.statusText;\n              });\n              redefineProp(xhr, 'readyState', function () {\n                return 4;\n              });\n              if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {\n                return fileApiXHR.response;\n              });\n              var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);\n              redefineProp(xhr, 'responseText', function () {\n                return resp;\n              });\n              redefineProp(xhr, 'response', function () {\n                return resp;\n              });\n              if (err) redefineProp(xhr, 'err', function () {\n                return err;\n              });\n              xhr.__fileApiXHR = fileApiXHR;\n              if (xhr.onreadystatechange) xhr.onreadystatechange();\n              if (xhr.onload) xhr.onload();\n            },\n            progress: function (e) {\n              e.target = xhr;\n              if (xhr.__listeners.progress) xhr.__listeners.progress(e);\n              xhr.__total = e.total;\n              xhr.__loaded = e.loaded;\n              if (e.total === e.loaded) {\n                // fix flash issue that doesn't call complete if there is no response text from the server\n                var _this = this;\n                setTimeout(function () {\n                  if (!xhr.__completed) {\n                    xhr.getAllResponseHeaders = function () {\n                    };\n                    _this.complete(null, {status: 204, statusText: 'No Content'});\n                  }\n                }, FileAPI.noContentTimeout || 10000);\n              }\n            },\n            headers: xhr.__requestHeaders\n          };\n          config.data = {};\n          config.files = {};\n          for (var i = 0; i < formData.data.length; i++) {\n            var item = formData.data[i];\n            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {\n              config.files[item.key] = item.val;\n            } else {\n              config.data[item.key] = item.val;\n            }\n          }\n\n          setTimeout(function () {\n            if (!FileAPI.hasFlash) {\n              throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n            }\n            xhr.__fileApiXHR = FileAPI.upload(config);\n          }, 1);\n        } else {\n          if (this.__origError) {\n            throw this.__origError;\n          }\n          orig.apply(xhr, arguments);\n        }\n      };\n    });\n    window.XMLHttpRequest.__isFileAPIShim = true;\n    window.FormData = FormData = function () {\n      return {\n        append: function (key, val, name) {\n          if (val.__isFileAPIBlobShim) {\n            val = val.data[0];\n          }\n          this.data.push({\n            key: key,\n            val: val,\n            name: name\n          });\n        },\n        data: [],\n        __isFileAPIShim: true\n      };\n    };\n\n    window.Blob = Blob = function (b) {\n      return {\n        data: b,\n        __isFileAPIBlobShim: true\n      };\n    };\n  }\n\n})();\n\n(function () {\n  /** @namespace FileAPI.forceLoad */\n  /** @namespace window.FileAPI.jsUrl */\n  /** @namespace window.FileAPI.jsPath */\n\n  function isInputTypeFile(elem) {\n    return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';\n  }\n\n  function hasFlash() {\n    try {\n      var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n      if (fo) return true;\n    } catch (e) {\n      if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;\n    }\n    return false;\n  }\n\n  function getOffset(obj) {\n    var left = 0, top = 0;\n\n    if (window.jQuery) {\n      return jQuery(obj).offset();\n    }\n\n    if (obj.offsetParent) {\n      do {\n        left += (obj.offsetLeft - obj.scrollLeft);\n        top += (obj.offsetTop - obj.scrollTop);\n        obj = obj.offsetParent;\n      } while (obj);\n    }\n    return {\n      left: left,\n      top: top\n    };\n  }\n\n  if (FileAPI.shouldLoad) {\n    FileAPI.hasFlash = hasFlash();\n\n    //load FileAPI\n    if (FileAPI.forceLoad) {\n      FileAPI.html5 = false;\n    }\n\n    if (!FileAPI.upload) {\n      var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;\n      if (window.FileAPI.jsUrl) {\n        jsUrl = window.FileAPI.jsUrl;\n      } else if (window.FileAPI.jsPath) {\n        basePath = window.FileAPI.jsPath;\n      } else {\n        for (i = 0; i < allScripts.length; i++) {\n          src = allScripts[i].src;\n          index = src.search(/\\/ng\\-file\\-upload[\\-a-zA-z0-9\\.]*\\.js/);\n          if (index > -1) {\n            basePath = src.substring(0, index + 1);\n            break;\n          }\n        }\n      }\n\n      if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;\n      script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');\n      document.getElementsByTagName('head')[0].appendChild(script);\n    }\n\n    FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {\n      if (!hasFlash()) {\n        throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n      }\n      var fixInputStyle = function () {\n        var label = fileElem.parent();\n        if (elem.attr('disabled')) {\n          if (label) label.removeClass('js-fileapi-wrapper');\n        } else {\n          if (!fileElem.attr('__ngf_flash_')) {\n            fileElem.unbind('change');\n            fileElem.unbind('click');\n            fileElem.bind('change', function (evt) {\n              fileApiChangeFn.apply(this, [evt]);\n              changeFn.apply(this, [evt]);\n            });\n            fileElem.attr('__ngf_flash_', 'true');\n          }\n          label.addClass('js-fileapi-wrapper');\n          if (!isInputTypeFile(elem)) {\n            label.css('position', 'absolute')\n              .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')\n              .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))\n              .css('overflow', 'hidden').css('z-index', '900000')\n              .css('visibility', 'visible');\n            fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('position', 'absolute').css('top', '0px').css('left', '0px');\n          }\n        }\n      };\n\n      elem.bind('mouseenter', fixInputStyle);\n\n      var fileApiChangeFn = function (evt) {\n        var files = FileAPI.getFiles(evt);\n        //just a double check for #233\n        for (var i = 0; i < files.length; i++) {\n          if (files[i].size === undefined) files[i].size = 0;\n          if (files[i].name === undefined) files[i].name = 'file';\n          if (files[i].type === undefined) files[i].type = 'undefined';\n        }\n        if (!evt.target) {\n          evt.target = {};\n        }\n        evt.target.files = files;\n        // if evt.target.files is not writable use helper field\n        if (evt.target.files !== files) {\n          evt.__files_ = files;\n        }\n        (evt.__files_ || evt.target.files).item = function (i) {\n          return (evt.__files_ || evt.target.files)[i] || null;\n        };\n      };\n    };\n\n    FileAPI.disableFileInput = function (elem, disable) {\n      if (disable) {\n        elem.removeClass('js-fileapi-wrapper');\n      } else {\n        elem.addClass('js-fileapi-wrapper');\n      }\n    };\n  }\n})();\n\nif (!window.FileReader) {\n  window.FileReader = function () {\n    var _this = this, loadStarted = false;\n    this.listeners = {};\n    this.addEventListener = function (type, fn) {\n      _this.listeners[type] = _this.listeners[type] || [];\n      _this.listeners[type].push(fn);\n    };\n    this.removeEventListener = function (type, fn) {\n      if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);\n    };\n    this.dispatchEvent = function (evt) {\n      var list = _this.listeners[evt.type];\n      if (list) {\n        for (var i = 0; i < list.length; i++) {\n          list[i].call(_this, evt);\n        }\n      }\n    };\n    this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;\n\n    var constructEvent = function (type, evt) {\n      var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};\n      if (evt.result != null) e.target.result = evt.result;\n      return e;\n    };\n    var listener = function (evt) {\n      if (!loadStarted) {\n        loadStarted = true;\n        if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));\n      }\n      var e;\n      if (evt.type === 'load') {\n        if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));\n        e = constructEvent('load', evt);\n        if (_this.onload) _this.onload(e);\n        _this.dispatchEvent(e);\n      } else if (evt.type === 'progress') {\n        e = constructEvent('progress', evt);\n        if (_this.onprogress) _this.onprogress(e);\n        _this.dispatchEvent(e);\n      } else {\n        e = constructEvent('error', evt);\n        if (_this.onerror) _this.onerror(e);\n        _this.dispatchEvent(e);\n      }\n    };\n    this.readAsDataURL = function (file) {\n      FileAPI.readAsDataURL(file, listener);\n    };\n    this.readAsText = function (file) {\n      FileAPI.readAsText(file, listener);\n    };\n  };\n}\n"
  },
  {
    "path": "dist/ng-file-upload.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * @author  Danial  <danial.farid@gmail.com>\n * @version 12.2.13\n */\n\nif (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {\n  window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {\n    return function (header, value) {\n      if (header === '__setXHR_') {\n        var val = value(this);\n        // fix for angular < 1.2.0\n        if (val instanceof Function) {\n          val(this);\n        }\n      } else {\n        orig.apply(this, arguments);\n      }\n    };\n  })(window.XMLHttpRequest.prototype.setRequestHeader);\n}\n\nvar ngFileUpload = angular.module('ngFileUpload', []);\n\nngFileUpload.version = '12.2.13';\n\nngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {\n  var upload = this;\n  upload.promisesCount = 0;\n\n  this.isResumeSupported = function () {\n    return window.Blob && window.Blob.prototype.slice;\n  };\n\n  var resumeSupported = this.isResumeSupported();\n\n  function sendHttp(config) {\n    config.method = config.method || 'POST';\n    config.headers = config.headers || {};\n\n    var deferred = config._deferred = config._deferred || $q.defer();\n    var promise = deferred.promise;\n\n    function notifyProgress(e) {\n      if (deferred.notify) {\n        deferred.notify(e);\n      }\n      if (promise.progressFunc) {\n        $timeout(function () {\n          promise.progressFunc(e);\n        });\n      }\n    }\n\n    function getNotifyEvent(n) {\n      if (config._start != null && resumeSupported) {\n        return {\n          loaded: n.loaded + config._start,\n          total: (config._file && config._file.size) || n.total,\n          type: n.type, config: config,\n          lengthComputable: true, target: n.target\n        };\n      } else {\n        return n;\n      }\n    }\n\n    if (!config.disableProgress) {\n      config.headers.__setXHR_ = function () {\n        return function (xhr) {\n          if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;\n          config.__XHR = xhr;\n          if (config.xhrFn) config.xhrFn(xhr);\n          xhr.upload.addEventListener('progress', function (e) {\n            e.config = config;\n            notifyProgress(getNotifyEvent(e));\n          }, false);\n          //fix for firefox not firing upload progress end, also IE8-9\n          xhr.upload.addEventListener('load', function (e) {\n            if (e.lengthComputable) {\n              e.config = config;\n              notifyProgress(getNotifyEvent(e));\n            }\n          }, false);\n        };\n      };\n    }\n\n    function uploadWithAngular() {\n      $http(config).then(function (r) {\n          if (resumeSupported && config._chunkSize && !config._finished && config._file) {\n            var fileSize = config._file && config._file.size || 0;\n            notifyProgress({\n                loaded: Math.min(config._end, fileSize),\n                total: fileSize,\n                config: config,\n                type: 'progress'\n              }\n            );\n            upload.upload(config, true);\n          } else {\n            if (config._finished) delete config._finished;\n            deferred.resolve(r);\n          }\n        }, function (e) {\n          deferred.reject(e);\n        }, function (n) {\n          deferred.notify(n);\n        }\n      );\n    }\n\n    if (!resumeSupported) {\n      uploadWithAngular();\n    } else if (config._chunkSize && config._end && !config._finished) {\n      config._start = config._end;\n      config._end += config._chunkSize;\n      uploadWithAngular();\n    } else if (config.resumeSizeUrl) {\n      $http.get(config.resumeSizeUrl).then(function (resp) {\n        if (config.resumeSizeResponseReader) {\n          config._start = config.resumeSizeResponseReader(resp.data);\n        } else {\n          config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());\n        }\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else if (config.resumeSize) {\n      config.resumeSize().then(function (size) {\n        config._start = size;\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else {\n      if (config._chunkSize) {\n        config._start = 0;\n        config._end = config._start + config._chunkSize;\n      }\n      uploadWithAngular();\n    }\n\n\n    promise.success = function (fn) {\n      promise.then(function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.error = function (fn) {\n      promise.then(null, function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.progress = function (fn) {\n      promise.progressFunc = fn;\n      promise.then(null, null, function (n) {\n        fn(n);\n      });\n      return promise;\n    };\n    promise.abort = promise.pause = function () {\n      if (config.__XHR) {\n        $timeout(function () {\n          config.__XHR.abort();\n        });\n      }\n      return promise;\n    };\n    promise.xhr = function (fn) {\n      config.xhrFn = (function (origXhrFn) {\n        return function () {\n          if (origXhrFn) origXhrFn.apply(promise, arguments);\n          fn.apply(promise, arguments);\n        };\n      })(config.xhrFn);\n      return promise;\n    };\n\n    upload.promisesCount++;\n    if (promise['finally'] && promise['finally'] instanceof Function) {\n      promise['finally'](function () {\n        upload.promisesCount--;\n      });\n    }\n    return promise;\n  }\n\n  this.isUploadInProgress = function () {\n    return upload.promisesCount > 0;\n  };\n\n  this.rename = function (file, name) {\n    file.ngfName = name;\n    return file;\n  };\n\n  this.jsonBlob = function (val) {\n    if (val != null && !angular.isString(val)) {\n      val = JSON.stringify(val);\n    }\n    var blob = new window.Blob([val], {type: 'application/json'});\n    blob._ngfBlob = true;\n    return blob;\n  };\n\n  this.json = function (val) {\n    return angular.toJson(val);\n  };\n\n  function copy(obj) {\n    var clone = {};\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        clone[key] = obj[key];\n      }\n    }\n    return clone;\n  }\n\n  this.isFile = function (file) {\n    return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));\n  };\n\n  this.upload = function (config, internal) {\n    function toResumeFile(file, formData) {\n      if (file._ngfBlob) return file;\n      config._file = config._file || file;\n      if (config._start != null && resumeSupported) {\n        if (config._end && config._end >= file.size) {\n          config._finished = true;\n          config._end = file.size;\n        }\n        var slice = file.slice(config._start, config._end || file.size);\n        slice.name = file.name;\n        slice.ngfName = file.ngfName;\n        if (config._chunkSize) {\n          formData.append('_chunkSize', config._chunkSize);\n          formData.append('_currentChunkSize', config._end - config._start);\n          formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));\n          formData.append('_totalSize', config._file.size);\n        }\n        return slice;\n      }\n      return file;\n    }\n\n    function addFieldToFormData(formData, val, key) {\n      if (val !== undefined) {\n        if (angular.isDate(val)) {\n          val = val.toISOString();\n        }\n        if (angular.isString(val)) {\n          formData.append(key, val);\n        } else if (upload.isFile(val)) {\n          var file = toResumeFile(val, formData);\n          var split = key.split(',');\n          if (split[1]) {\n            file.ngfName = split[1].replace(/^\\s+|\\s+$/g, '');\n            key = split[0];\n          }\n          config._fileKey = config._fileKey || key;\n          formData.append(key, file, file.ngfName || file.name);\n        } else {\n          if (angular.isObject(val)) {\n            if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;\n\n            val.$$ngfCircularDetection = true;\n            try {\n              for (var k in val) {\n                if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {\n                  var objectKey = config.objectKey == null ? '[i]' : config.objectKey;\n                  if (val.length && parseInt(k) > -1) {\n                    objectKey = config.arrayKey == null ? objectKey : config.arrayKey;\n                  }\n                  addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));\n                }\n              }\n            } finally {\n              delete val.$$ngfCircularDetection;\n            }\n          } else {\n            formData.append(key, val);\n          }\n        }\n      }\n    }\n\n    function digestConfig() {\n      config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n      config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n      config.headers = config.headers || {};\n      config.headers['Content-Type'] = undefined;\n      config.transformRequest = config.transformRequest ?\n        (angular.isArray(config.transformRequest) ?\n          config.transformRequest : [config.transformRequest]) : [];\n      config.transformRequest.push(function (data) {\n        var formData = new window.FormData(), key;\n        data = data || config.fields || {};\n        if (config.file) {\n          data.file = config.file;\n        }\n        for (key in data) {\n          if (data.hasOwnProperty(key)) {\n            var val = data[key];\n            if (config.formDataAppender) {\n              config.formDataAppender(formData, key, val);\n            } else {\n              addFieldToFormData(formData, val, key);\n            }\n          }\n        }\n\n        return formData;\n      });\n    }\n\n    if (!internal) config = copy(config);\n    if (!config._isDigested) {\n      config._isDigested = true;\n      digestConfig();\n    }\n\n    return sendHttp(config);\n  };\n\n  this.http = function (config) {\n    config = copy(config);\n    config.transformRequest = config.transformRequest || function (data) {\n        if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {\n          return data;\n        }\n        return $http.defaults.transformRequest[0].apply(this, arguments);\n      };\n    config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n    config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n    return sendHttp(config);\n  };\n\n  this.translateScalars = function (str) {\n    if (angular.isString(str)) {\n      if (str.search(/kb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1024);\n      } else if (str.search(/mb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1048576);\n      } else if (str.search(/gb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1073741824);\n      } else if (str.search(/b/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/s/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/m/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 60);\n      } else if (str.search(/h/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 3600);\n      }\n    }\n    return str;\n  };\n\n  this.urlToBlob = function(url) {\n    var defer = $q.defer();\n    $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {\n      var arrayBufferView = new Uint8Array(resp.data);\n      var type = resp.headers('content-type') || 'image/WebP';\n      var blob = new window.Blob([arrayBufferView], {type: type});\n      var matches = url.match(/.*\\/(.+?)(\\?.*)?$/);\n      if (matches.length > 1) {\n        blob.name = matches[1];\n      }\n      defer.resolve(blob);\n    }, function (e) {\n      defer.reject(e);\n    });\n    return defer.promise;\n  };\n\n  this.setDefaults = function (defaults) {\n    this.defaults = defaults || {};\n  };\n\n  this.defaults = {};\n  this.version = ngFileUpload.version;\n}\n\n]);\n\nngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {\n  var upload = UploadExif;\n  upload.getAttrWithDefaults = function (attr, name) {\n    if (attr[name] != null) return attr[name];\n    var def = upload.defaults[name];\n    return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));\n  };\n\n  upload.attrGetter = function (name, attr, scope, params) {\n    var attrVal = this.getAttrWithDefaults(attr, name);\n    if (scope) {\n      try {\n        if (params) {\n          return $parse(attrVal)(scope, params);\n        } else {\n          return $parse(attrVal)(scope);\n        }\n      } catch (e) {\n        // hangle string value without single qoute\n        if (name.search(/min|max|pattern/i)) {\n          return attrVal;\n        } else {\n          throw e;\n        }\n      }\n    } else {\n      return attrVal;\n    }\n  };\n\n  upload.shouldUpdateOn = function (type, attr, scope) {\n    var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);\n    if (modelOptions && modelOptions.updateOn) {\n      return modelOptions.updateOn.split(' ').indexOf(type) > -1;\n    }\n    return true;\n  };\n\n  upload.emptyPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.resolve.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.rejectPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.reject.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.happyPromise = function (promise, data) {\n    var d = $q.defer();\n    promise.then(function (result) {\n      d.resolve(result);\n    }, function (error) {\n      $timeout(function () {\n        throw error;\n      });\n      d.resolve(data);\n    });\n    return d.promise;\n  };\n\n  function applyExifRotations(files, attr, scope) {\n    var promises = [upload.emptyPromise()];\n    angular.forEach(files, function (f, i) {\n      if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {\n        promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {\n          files.splice(i, 1, fixedFile);\n        }));\n      }\n    });\n    return $q.all(promises);\n  }\n\n  function resizeFile(files, attr, scope, ngModel) {\n    var resizeVal = upload.attrGetter('ngfResize', attr, scope);\n    if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();\n    if (resizeVal instanceof Function) {\n      var defer = $q.defer();\n      return resizeVal(files).then(function (p) {\n        resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {\n          defer.resolve(r);\n        }, function (e) {\n          defer.reject(e);\n        });\n      }, function (e) {\n        defer.reject(e);\n      });\n    } else {\n      return resizeWithParams(resizeVal, files, attr, scope, ngModel);\n    }\n  }\n\n  function resizeWithParams(params, files, attr, scope, ngModel) {\n    var promises = [upload.emptyPromise()];\n\n    function handleFile(f, i) {\n      if (f.type.indexOf('image') === 0) {\n        if (params.pattern && !upload.validatePattern(f, params.pattern)) return;\n        params.resizeIf = function (width, height) {\n          return upload.attrGetter('ngfResizeIf', attr, scope,\n            {$width: width, $height: height, $file: f});\n        };\n        var promise = upload.resize(f, params);\n        promises.push(promise);\n        promise.then(function (resizedFile) {\n          files.splice(i, 1, resizedFile);\n        }, function (e) {\n          f.$error = 'resize';\n          (f.$errorMessages = (f.$errorMessages || {})).resize = true;\n          f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);\n          ngModel.$ngfValidations.push({name: 'resize', valid: false});\n          upload.applyModelValidation(ngModel, files);\n        });\n      }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n      handleFile(files[i], i);\n    }\n    return $q.all(promises);\n  }\n\n  upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {\n    function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {\n      attr.$$ngfPrevValidFiles = files;\n      attr.$$ngfPrevInvalidFiles = invalidFiles;\n      var file = files && files.length ? files[0] : null;\n      var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;\n\n      if (ngModel) {\n        upload.applyModelValidation(ngModel, files);\n        ngModel.$setViewValue(isSingleModel ? file : files);\n      }\n\n      if (fileChange) {\n        $parse(fileChange)(scope, {\n          $files: files,\n          $file: file,\n          $newFiles: newFiles,\n          $duplicateFiles: dupFiles,\n          $invalidFiles: invalidFiles,\n          $invalidFile: invalidFile,\n          $event: evt\n        });\n      }\n\n      var invalidModel = upload.attrGetter('ngfModelInvalid', attr);\n      if (invalidModel) {\n        $timeout(function () {\n          $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);\n        });\n      }\n      $timeout(function () {\n        // scope apply changes\n      });\n    }\n\n    var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,\n      invalids = [], valids = [];\n\n    function removeDuplicates() {\n      function equals(f1, f2) {\n        return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&\n          f1.type === f2.type;\n      }\n\n      function isInPrevFiles(f) {\n        var j;\n        for (j = 0; j < prevValidFiles.length; j++) {\n          if (equals(f, prevValidFiles[j])) {\n            return true;\n          }\n        }\n        for (j = 0; j < prevInvalidFiles.length; j++) {\n          if (equals(f, prevInvalidFiles[j])) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n      if (files) {\n        allNewFiles = [];\n        dupFiles = [];\n        for (var i = 0; i < files.length; i++) {\n          if (isInPrevFiles(files[i])) {\n            dupFiles.push(files[i]);\n          } else {\n            allNewFiles.push(files[i]);\n          }\n        }\n      }\n    }\n\n    function toArray(v) {\n      return angular.isArray(v) ? v : [v];\n    }\n\n    function resizeAndUpdate() {\n      function updateModel() {\n        $timeout(function () {\n          update(keep ? prevValidFiles.concat(valids) : valids,\n            keep ? prevInvalidFiles.concat(invalids) : invalids,\n            files, dupFiles, isSingleModel);\n        }, options && options.debounce ? options.debounce.change || options.debounce : 0);\n      }\n\n      var resizingFiles = validateAfterResize ? allNewFiles : valids;\n      resizeFile(resizingFiles, attr, scope, ngModel).then(function () {\n        if (validateAfterResize) {\n          upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n            .then(function (validationResult) {\n              valids = validationResult.validsFiles;\n              invalids = validationResult.invalidsFiles;\n              updateModel();\n            });\n        } else {\n          updateModel();\n        }\n      }, function () {\n        for (var i = 0; i < resizingFiles.length; i++) {\n          var f = resizingFiles[i];\n          if (f.$error === 'resize') {\n            var index = valids.indexOf(f);\n            if (index > -1) {\n              valids.splice(index, 1);\n              invalids.push(f);\n            }\n            updateModel();\n          }\n        }\n      });\n    }\n\n    prevValidFiles = attr.$$ngfPrevValidFiles || [];\n    prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];\n    if (ngModel && ngModel.$modelValue) {\n      prevValidFiles = toArray(ngModel.$modelValue);\n    }\n\n    var keep = upload.attrGetter('ngfKeep', attr, scope);\n    allNewFiles = (files || []).slice(0);\n    if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {\n      removeDuplicates(attr, scope);\n    }\n\n    var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);\n\n    if (keep && !allNewFiles.length) return;\n\n    upload.attrGetter('ngfBeforeModelChange', attr, scope, {\n      $files: files,\n      $file: files && files.length ? files[0] : null,\n      $newFiles: allNewFiles,\n      $duplicateFiles: dupFiles,\n      $event: evt\n    });\n\n    var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);\n\n    var options = upload.attrGetter('ngfModelOptions', attr, scope);\n    upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n      .then(function (validationResult) {\n      if (noDelay) {\n        update(allNewFiles, [], files, dupFiles, isSingleModel);\n      } else {\n        if ((!options || !options.allowInvalid) && !validateAfterResize) {\n          valids = validationResult.validFiles;\n          invalids = validationResult.invalidFiles;\n        } else {\n          valids = allNewFiles;\n        }\n        if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {\n          applyExifRotations(valids, attr, scope).then(function () {\n            resizeAndUpdate();\n          });\n        } else {\n          resizeAndUpdate();\n        }\n      }\n    });\n  };\n\n  return upload;\n}]);\n\nngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {\n  var generatedElems = [];\n\n  function isDelayedClickSupported(ua) {\n    // fix for android native browser < 4.4 and safari windows\n    var m = ua.match(/Android[^\\d]*(\\d+)\\.(\\d+)/);\n    if (m && m.length > 2) {\n      var v = Upload.defaults.androidFixMinorVersion || 4;\n      return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);\n    }\n\n    // safari on windows\n    return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);\n  }\n\n  function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {\n    /** @namespace attr.ngfSelect */\n    /** @namespace attr.ngfChange */\n    /** @namespace attr.ngModel */\n    /** @namespace attr.ngfModelOptions */\n    /** @namespace attr.ngfMultiple */\n    /** @namespace attr.ngfCapture */\n    /** @namespace attr.ngfValidate */\n    /** @namespace attr.ngfKeep */\n    var attrGetter = function (name, scope) {\n      return upload.attrGetter(name, attr, scope);\n    };\n\n    function isInputTypeFile() {\n      return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';\n    }\n\n    function fileChangeAttr() {\n      return attrGetter('ngfChange') || attrGetter('ngfSelect');\n    }\n\n    function changeFn(evt) {\n      if (upload.shouldUpdateOn('change', attr, scope)) {\n        var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];\n        /* Handle duplicate call in  IE11 */\n        if (!fileList) return;\n        for (var i = 0; i < fileList.length; i++) {\n          files.push(fileList[i]);\n        }\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(),\n          files.length ? files : null, evt);\n      }\n    }\n\n    upload.registerModelChangeValidator(ngModel, attr, scope);\n\n    var unwatches = [];\n    if (attrGetter('ngfMultiple')) {\n      unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {\n        fileElem.attr('multiple', attrGetter('ngfMultiple', scope));\n      }));\n    }\n    if (attrGetter('ngfCapture')) {\n      unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {\n        fileElem.attr('capture', attrGetter('ngfCapture', scope));\n      }));\n    }\n    if (attrGetter('ngfAccept')) {\n      unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {\n        fileElem.attr('accept', attrGetter('ngfAccept', scope));\n      }));\n    }\n    unwatches.push(attr.$observe('accept', function () {\n      fileElem.attr('accept', attrGetter('accept'));\n    }));\n    function bindAttrToFileInput(fileElem, label) {\n      function updateId(val) {\n        fileElem.attr('id', 'ngf-' + val);\n        label.attr('id', 'ngf-label-' + val);\n      }\n\n      for (var i = 0; i < elem[0].attributes.length; i++) {\n        var attribute = elem[0].attributes[i];\n        if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {\n          if (attribute.name === 'id') {\n            updateId(attribute.value);\n            unwatches.push(attr.$observe('id', updateId));\n          } else {\n            fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||\n            attribute.name === 'multiple')) ? attribute.name : attribute.value);\n          }\n        }\n      }\n    }\n\n    function createFileInput() {\n      if (isInputTypeFile()) {\n        return elem;\n      }\n\n      var fileElem = angular.element('<input type=\"file\">');\n\n      var label = angular.element('<label>upload</label>');\n      label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')\n        .css('width', '0px').css('height', '0px').css('border', 'none')\n        .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');\n      bindAttrToFileInput(fileElem, label);\n\n      generatedElems.push({el: elem, ref: label});\n\n      document.body.appendChild(label.append(fileElem)[0]);\n\n      return fileElem;\n    }\n\n    function clickHandler(evt) {\n      if (elem.attr('disabled')) return false;\n      if (attrGetter('ngfSelectDisabled', scope)) return;\n\n      var r = detectSwipe(evt);\n      // prevent the click if it is a swipe\n      if (r != null) return r;\n\n      resetModel(evt);\n\n      // fix for md when the element is removed from the DOM and added back #460\n      try {\n        if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {\n          generatedElems.push({el: elem, ref: fileElem.parent()});\n          document.body.appendChild(fileElem.parent()[0]);\n          fileElem.bind('change', changeFn);\n        }\n      } catch (e) {/*ignore*/\n      }\n\n      if (isDelayedClickSupported(navigator.userAgent)) {\n        setTimeout(function () {\n          fileElem[0].click();\n        }, 0);\n      } else {\n        fileElem[0].click();\n      }\n\n      return false;\n    }\n\n\n    var initialTouchStartY = 0;\n    var initialTouchStartX = 0;\n\n    function detectSwipe(evt) {\n      var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);\n      if (touches) {\n        if (evt.type === 'touchstart') {\n          initialTouchStartX = touches[0].clientX;\n          initialTouchStartY = touches[0].clientY;\n          return true; // don't block event default\n        } else {\n          // prevent scroll from triggering event\n          if (evt.type === 'touchend') {\n            var currentX = touches[0].clientX;\n            var currentY = touches[0].clientY;\n            if ((Math.abs(currentX - initialTouchStartX) > 20) ||\n              (Math.abs(currentY - initialTouchStartY) > 20)) {\n              evt.stopPropagation();\n              evt.preventDefault();\n              return false;\n            }\n          }\n          return true;\n        }\n      }\n    }\n\n    var fileElem = elem;\n\n    function resetModel(evt) {\n      if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {\n        fileElem.val(null);\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);\n      }\n    }\n\n    if (!isInputTypeFile()) {\n      fileElem = createFileInput();\n    }\n    fileElem.bind('change', changeFn);\n\n    if (!isInputTypeFile()) {\n      elem.bind('click touchstart touchend', clickHandler);\n    } else {\n      elem.bind('click', resetModel);\n    }\n\n    function ie10SameFileSelectFix(evt) {\n      if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {\n        if (!fileElem[0].parentNode) {\n          fileElem = null;\n          return;\n        }\n        evt.preventDefault();\n        evt.stopPropagation();\n        fileElem.unbind('click');\n        var clone = fileElem.clone();\n        fileElem.replaceWith(clone);\n        fileElem = clone;\n        fileElem.attr('__ngf_ie10_Fix_', 'true');\n        fileElem.bind('change', changeFn);\n        fileElem.bind('click', ie10SameFileSelectFix);\n        fileElem[0].click();\n        return false;\n      } else {\n        fileElem.removeAttr('__ngf_ie10_Fix_');\n      }\n    }\n\n    if (navigator.appVersion.indexOf('MSIE 10') !== -1) {\n      fileElem.bind('click', ie10SameFileSelectFix);\n    }\n\n    if (ngModel) ngModel.$formatters.push(function (val) {\n      if (val == null || val.length === 0) {\n        if (fileElem.val()) {\n          fileElem.val(null);\n        }\n      }\n      return val;\n    });\n\n    scope.$on('$destroy', function () {\n      if (!isInputTypeFile()) fileElem.parent().remove();\n      angular.forEach(unwatches, function (unwatch) {\n        unwatch();\n      });\n    });\n\n    $timeout(function () {\n      for (var i = 0; i < generatedElems.length; i++) {\n        var g = generatedElems[i];\n        if (!document.body.contains(g.el[0])) {\n          generatedElems.splice(i, 1);\n          g.ref.remove();\n        }\n      }\n    });\n\n    if (window.FileAPI && window.FileAPI.ngfFixIE) {\n      window.FileAPI.ngfFixIE(elem, fileElem, changeFn);\n    }\n  }\n\n  return {\n    restrict: 'AEC',\n    require: '?ngModel',\n    link: function (scope, elem, attr, ngModel) {\n      linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);\n    }\n  };\n}]);\n\n(function () {\n\n  ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {\n    var upload = UploadBase;\n    upload.base64DataUrl = function (file) {\n      if (angular.isArray(file)) {\n        var d = $q.defer(), count = 0;\n        angular.forEach(file, function (f) {\n          upload.dataUrl(f, true)['finally'](function () {\n            count++;\n            if (count === file.length) {\n              var urls = [];\n              angular.forEach(file, function (ff) {\n                urls.push(ff.$ngfDataUrl);\n              });\n              d.resolve(urls, file);\n            }\n          });\n        });\n        return d.promise;\n      } else {\n        return upload.dataUrl(file, true);\n      }\n    };\n    upload.dataUrl = function (file, disallowObjectUrl) {\n      if (!file) return upload.emptyPromise(file, file);\n      if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {\n        return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);\n      }\n      var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;\n      if (p) return p;\n\n      var deferred = $q.defer();\n      $timeout(function () {\n        if (window.FileReader && file &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {\n          //prefer URL.createObjectURL for handling refrences to files of all sizes\n          //since it doesn´t build a large string in memory\n          var URL = window.URL || window.webkitURL;\n          if (URL && URL.createObjectURL && !disallowObjectUrl) {\n            var url;\n            try {\n              url = URL.createObjectURL(file);\n            } catch (e) {\n              $timeout(function () {\n                file.$ngfBlobUrl = '';\n                deferred.reject();\n              });\n              return;\n            }\n            $timeout(function () {\n              file.$ngfBlobUrl = url;\n              if (url) {\n                deferred.resolve(url, file);\n                upload.blobUrls = upload.blobUrls || [];\n                upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;\n                upload.blobUrls.push({url: url, size: file.size});\n                upload.blobUrlsTotalSize += file.size || 0;\n                var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;\n                var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;\n                while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {\n                  var obj = upload.blobUrls.splice(0, 1)[0];\n                  URL.revokeObjectURL(obj.url);\n                  upload.blobUrlsTotalSize -= obj.size;\n                }\n              }\n            });\n          } else {\n            var fileReader = new FileReader();\n            fileReader.onload = function (e) {\n              $timeout(function () {\n                file.$ngfDataUrl = e.target.result;\n                deferred.resolve(e.target.result, file);\n                $timeout(function () {\n                  delete file.$ngfDataUrl;\n                }, 1000);\n              });\n            };\n            fileReader.onerror = function () {\n              $timeout(function () {\n                file.$ngfDataUrl = '';\n                deferred.reject();\n              });\n            };\n            fileReader.readAsDataURL(file);\n          }\n        } else {\n          $timeout(function () {\n            file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';\n            deferred.reject();\n          });\n        }\n      });\n\n      if (disallowObjectUrl) {\n        p = file.$$ngfDataUrlPromise = deferred.promise;\n      } else {\n        p = file.$$ngfBlobUrlPromise = deferred.promise;\n      }\n      p['finally'](function () {\n        delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];\n      });\n      return p;\n    };\n    return upload;\n  }]);\n\n  function getTagType(el) {\n    if (el.tagName.toLowerCase() === 'img') return 'image';\n    if (el.tagName.toLowerCase() === 'audio') return 'audio';\n    if (el.tagName.toLowerCase() === 'video') return 'video';\n    return /./;\n  }\n\n  function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {\n    function constructDataUrl(file) {\n      var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);\n      Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {\n        $timeout(function () {\n          var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;\n          if (isBackground) {\n            elem.css('background-image', 'url(\\'' + (src || '') + '\\')');\n          } else {\n            elem.attr('src', src);\n          }\n          if (src) {\n            elem.removeClass('ng-hide');\n          } else {\n            elem.addClass('ng-hide');\n          }\n        });\n      });\n    }\n\n    $timeout(function () {\n      var unwatch = scope.$watch(attr[directiveName], function (file) {\n        var size = resizeParams;\n        if (directiveName === 'ngfThumbnail') {\n          if (!size) {\n            size = {\n              width: elem[0].naturalWidth || elem[0].clientWidth,\n              height: elem[0].naturalHeight || elem[0].clientHeight\n            };\n          }\n          if (size.width === 0 && window.getComputedStyle) {\n            var style = getComputedStyle(elem[0]);\n            if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {\n              size = {\n                width: parseInt(style.width.slice(0, -2)),\n                height: parseInt(style.height.slice(0, -2))\n              };\n            }\n          }\n        }\n\n        if (angular.isString(file)) {\n          elem.removeClass('ng-hide');\n          if (isBackground) {\n            return elem.css('background-image', 'url(\\'' + file + '\\')');\n          } else {\n            return elem.attr('src', file);\n          }\n        }\n        if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&\n          (!isBackground || file.type.indexOf('image') === 0)) {\n          if (size && Upload.isResizeSupported()) {\n            size.resizeIf = function (width, height) {\n              return Upload.attrGetter('ngfResizeIf', attr, scope,\n                {$width: width, $height: height, $file: file});\n            };\n            Upload.resize(file, size).then(\n              function (f) {\n                constructDataUrl(f);\n              }, function (e) {\n                throw e;\n              }\n            );\n          } else {\n            constructDataUrl(file);\n          }\n        } else {\n          elem.addClass('ng-hide');\n        }\n      });\n\n      scope.$on('$destroy', function () {\n        unwatch();\n      });\n    });\n  }\n\n\n  /** @namespace attr.ngfSrc */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',\n          Upload.attrGetter('ngfResize', attr, scope), false);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfBackground */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',\n          Upload.attrGetter('ngfResize', attr, scope), true);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfThumbnail */\n  /** @namespace attr.ngfAsBackground */\n  /** @namespace attr.ngfSize */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        var size = Upload.attrGetter('ngfSize', attr, scope);\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,\n          Upload.attrGetter('ngfAsBackground', attr, scope));\n      }\n    };\n  }]);\n\n  ngFileUpload.config(['$compileProvider', function ($compileProvider) {\n    if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n    if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n  }]);\n\n  ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {\n    return function (file, disallowObjectUrl, trustedUrl) {\n      if (angular.isString(file)) {\n        return $sce.trustAsResourceUrl(file);\n      }\n      var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);\n      if (file && !src) {\n        if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {\n          file.$ngfDataUrlFilterInProgress = true;\n          UploadDataUrl.dataUrl(file, disallowObjectUrl);\n        }\n        return '';\n      }\n      if (file) delete file.$ngfDataUrlFilterInProgress;\n      return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';\n    };\n  }]);\n\n})();\n\nngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {\n  var upload = UploadDataUrl;\n\n  function globStringToRegex(str) {\n    var regexp = '', excludes = [];\n    if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {\n      regexp = str.substring(1, str.length - 1);\n    } else {\n      var split = str.split(',');\n      if (split.length > 1) {\n        for (var i = 0; i < split.length; i++) {\n          var r = globStringToRegex(split[i]);\n          if (r.regexp) {\n            regexp += '(' + r.regexp + ')';\n            if (i < split.length - 1) {\n              regexp += '|';\n            }\n          } else {\n            excludes = excludes.concat(r.excludes);\n          }\n        }\n      } else {\n        if (str.indexOf('!') === 0) {\n          excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');\n        } else {\n          if (str.indexOf('.') === 0) {\n            str = '*' + str;\n          }\n          regexp = '^' + str.replace(new RegExp('[.\\\\\\\\+*?\\\\[\\\\^\\\\]$(){}=!<>|:\\\\-]', 'g'), '\\\\$&') + '$';\n          regexp = regexp.replace(/\\\\\\*/g, '.*').replace(/\\\\\\?/g, '.');\n        }\n      }\n    }\n    return {regexp: regexp, excludes: excludes};\n  }\n\n  upload.validatePattern = function (file, val) {\n    if (!val) {\n      return true;\n    }\n    var pattern = globStringToRegex(val), valid = true;\n    if (pattern.regexp && pattern.regexp.length) {\n      var regexp = new RegExp(pattern.regexp, 'i');\n      valid = (file.type != null && regexp.test(file.type)) ||\n        (file.name != null && regexp.test(file.name));\n    }\n    var len = pattern.excludes.length;\n    while (len--) {\n      var exclude = new RegExp(pattern.excludes[len], 'i');\n      valid = valid && (file.type == null || exclude.test(file.type)) &&\n        (file.name == null || exclude.test(file.name));\n    }\n    return valid;\n  };\n\n  upload.ratioToFloat = function (val) {\n    var r = val.toString(), xIndex = r.search(/[x:]/i);\n    if (xIndex > -1) {\n      r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));\n    } else {\n      r = parseFloat(r);\n    }\n    return r;\n  };\n\n  upload.registerModelChangeValidator = function (ngModel, attr, scope) {\n    if (ngModel) {\n      ngModel.$formatters.push(function (files) {\n        if (ngModel.$dirty) {\n          var filesArray = files;\n          if (files && !angular.isArray(files)) {\n            filesArray = [files];\n          }\n          upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {\n            upload.applyModelValidation(ngModel, filesArray);\n          });\n        }\n        return files;\n      });\n    }\n  };\n\n  function markModelAsDirty(ngModel, files) {\n    if (files != null && !ngModel.$dirty) {\n      if (ngModel.$setDirty) {\n        ngModel.$setDirty();\n      } else {\n        ngModel.$dirty = true;\n      }\n    }\n  }\n\n  upload.applyModelValidation = function (ngModel, files) {\n    markModelAsDirty(ngModel, files);\n    angular.forEach(ngModel.$ngfValidations, function (validation) {\n      ngModel.$setValidity(validation.name, validation.valid);\n    });\n  };\n\n  upload.getValidationAttr = function (attr, scope, name, validationName, file) {\n    var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);\n    var val = upload.attrGetter(dName, attr, scope, {$file: file});\n    if (val == null) {\n      val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});\n      if (val) {\n        var split = (validationName || name).split('.');\n        val = val[split[0]];\n        if (split.length > 1) {\n          val = val && val[split[1]];\n        }\n      }\n    }\n    return val;\n  };\n\n  upload.validate = function (files, prevLength, ngModel, attr, scope) {\n    ngModel = ngModel || {};\n    ngModel.$ngfValidations = ngModel.$ngfValidations || [];\n\n    angular.forEach(ngModel.$ngfValidations, function (v) {\n      v.valid = true;\n    });\n\n    var attrGetter = function (name, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');\n    var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);\n\n    if (files == null || files.length === 0) {\n      return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});\n    }\n\n    files = files.length === undefined ? [files] : files.slice(0);\n    var invalidFiles = [];\n\n    function validateSync(name, validationName, fn) {\n      if (files) {\n        var i = files.length, valid = null;\n        while (i--) {\n          var file = files[i];\n          if (file) {\n            var val = upload.getValidationAttr(attr, scope, name, validationName, file);\n            if (val != null) {\n              if (!fn(file, val, i)) {\n                if (ignoredErrors.indexOf(name) === -1) {\n                  file.$error = name;\n                  (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n                  file.$errorParam = val;\n                  if (invalidFiles.indexOf(file) === -1) {\n                    invalidFiles.push(file);\n                  }\n                  if (!runAllValidation) {\n                    files.splice(i, 1);\n                  }\n                  valid = false;\n                } else {\n                  files.splice(i, 1);\n                }\n              }\n            }\n          }\n        }\n        if (valid !== null) {\n          ngModel.$ngfValidations.push({name: name, valid: valid});\n        }\n      }\n    }\n\n    validateSync('pattern', null, upload.validatePattern);\n    validateSync('minSize', 'size.min', function (file, val) {\n      return file.size + 0.1 >= upload.translateScalars(val);\n    });\n    validateSync('maxSize', 'size.max', function (file, val) {\n      return file.size - 0.1 <= upload.translateScalars(val);\n    });\n    var totalSize = 0;\n    validateSync('maxTotalSize', null, function (file, val) {\n      totalSize += file.size;\n      if (totalSize > upload.translateScalars(val)) {\n        files.splice(0, files.length);\n        return false;\n      }\n      return true;\n    });\n\n    validateSync('validateFn', null, function (file, r) {\n      return r === true || r === null || r === '';\n    });\n\n    if (!files.length) {\n      return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});\n    }\n\n    function validateAsync(name, validationName, type, asyncFn, fn) {\n      function resolveResult(defer, file, val) {\n        function resolveInternal(fn) {\n          if (fn()) {\n            if (ignoredErrors.indexOf(name) === -1) {\n              file.$error = name;\n              (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n              file.$errorParam = val;\n              if (invalidFiles.indexOf(file) === -1) {\n                invalidFiles.push(file);\n              }\n              if (!runAllValidation) {\n                var i = files.indexOf(file);\n                if (i > -1) files.splice(i, 1);\n              }\n              defer.resolve(false);\n            } else {\n              var j = files.indexOf(file);\n              if (j > -1) files.splice(j, 1);\n              defer.resolve(true);\n            }\n          } else {\n            defer.resolve(true);\n          }\n        }\n\n        if (val != null) {\n          asyncFn(file, val).then(function (d) {\n            resolveInternal(function () {\n              return !fn(d, val);\n            });\n          }, function () {\n            resolveInternal(function () {\n              return attrGetter('ngfValidateForce', {$file: file});\n            });\n          });\n        } else {\n          defer.resolve(true);\n        }\n      }\n\n      var promises = [upload.emptyPromise(true)];\n      if (files) {\n        files = files.length === undefined ? [files] : files;\n        angular.forEach(files, function (file) {\n          var defer = $q.defer();\n          promises.push(defer.promise);\n          if (type && (file.type == null || file.type.search(type) !== 0)) {\n            defer.resolve(true);\n            return;\n          }\n          if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {\n            upload.imageDimensions(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {\n            upload.mediaDuration(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDuration', {$file: file, $duration: d}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else {\n            resolveResult(defer, file,\n              upload.getValidationAttr(attr, scope, name, validationName, file));\n          }\n        });\n      }\n      var deffer = $q.defer();\n      $q.all(promises).then(function (values) {\n        var isValid = true;\n        for (var i = 0; i < values.length; i++) {\n          if (!values[i]) {\n            isValid = false;\n            break;\n          }\n        }\n        ngModel.$ngfValidations.push({name: name, valid: isValid});\n        deffer.resolve(isValid);\n      });\n      return deffer.promise;\n    }\n\n    var deffer = $q.defer();\n    var promises = [];\n\n    promises.push(validateAsync('maxHeight', 'height.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height <= val;\n      }));\n    promises.push(validateAsync('minHeight', 'height.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height >= val;\n      }));\n    promises.push(validateAsync('maxWidth', 'width.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width <= val;\n      }));\n    promises.push(validateAsync('minWidth', 'width.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width >= val;\n      }));\n    promises.push(validateAsync('dimensions', null, /image/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n    promises.push(validateAsync('ratio', null, /image/,\n      this.imageDimensions, function (d, val) {\n        var split = val.toString().split(','), valid = false;\n        for (var i = 0; i < split.length; i++) {\n          if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {\n            valid = true;\n          }\n        }\n        return valid;\n      }));\n    promises.push(validateAsync('maxRatio', 'ratio.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;\n      }));\n    promises.push(validateAsync('minRatio', 'ratio.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;\n      }));\n    promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d <= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d >= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('duration', null, /audio|video/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n\n    promises.push(validateAsync('validateAsyncFn', null, null,\n      function (file, val) {\n        return val;\n      }, function (r) {\n        return r === true || r === null || r === '';\n      }));\n\n    $q.all(promises).then(function () {\n\n      if (runAllValidation) {\n        for (var i = 0; i < files.length; i++) {\n          var file = files[i];\n          if (file.$error) {\n            files.splice(i--, 1);\n          }\n        }\n      }\n\n      runAllValidation = false;\n      validateSync('maxFiles', null, function (file, val, i) {\n        return prevLength + i < val;\n      });\n\n      deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});\n    });\n    return deffer.promise;\n  };\n\n  upload.imageDimensions = function (file) {\n    if (file.$ngfWidth && file.$ngfHeight) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});\n      });\n      return d.promise;\n    }\n    if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('image') !== 0) {\n        deferred.reject('not image');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var img = angular.element('<img>').attr('src', dataUrl)\n          .css('visibility', 'hidden').css('position', 'fixed')\n          .css('max-width', 'none !important').css('max-height', 'none !important');\n\n        function success() {\n          var width = img[0].naturalWidth || img[0].clientWidth;\n          var height = img[0].naturalHeight || img[0].clientHeight;\n          img.remove();\n          file.$ngfWidth = width;\n          file.$ngfHeight = height;\n          deferred.resolve({width: width, height: height});\n        }\n\n        function error() {\n          img.remove();\n          deferred.reject('load error');\n        }\n\n        img.on('load', success);\n        img.on('error', error);\n\n        var secondsCounter = 0;\n        function checkLoadErrorInCaseOfNoCallback() {\n          $timeout(function () {\n            if (img[0].parentNode) {\n              if (img[0].clientWidth) {\n                success();\n              } else if (secondsCounter++ > 10) {\n                error();\n              } else {\n                checkLoadErrorInCaseOfNoCallback();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadErrorInCaseOfNoCallback();\n\n        angular.element(document.getElementsByTagName('body')[0]).append(img);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDimensionPromise = deferred.promise;\n    file.$ngfDimensionPromise['finally'](function () {\n      delete file.$ngfDimensionPromise;\n    });\n    return file.$ngfDimensionPromise;\n  };\n\n  upload.mediaDuration = function (file) {\n    if (file.$ngfDuration) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve(file.$ngfDuration);\n      });\n      return d.promise;\n    }\n    if (file.$ngfDurationPromise) return file.$ngfDurationPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {\n        deferred.reject('not media');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')\n          .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');\n\n        function success() {\n          var duration = el[0].duration;\n          file.$ngfDuration = duration;\n          el.remove();\n          deferred.resolve(duration);\n        }\n\n        function error() {\n          el.remove();\n          deferred.reject('load error');\n        }\n\n        el.on('loadedmetadata', success);\n        el.on('error', error);\n        var count = 0;\n\n        function checkLoadError() {\n          $timeout(function () {\n            if (el[0].parentNode) {\n              if (el[0].duration) {\n                success();\n              } else if (count > 10) {\n                error();\n              } else {\n                checkLoadError();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadError();\n\n        angular.element(document.body).append(el);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDurationPromise = deferred.promise;\n    file.$ngfDurationPromise['finally'](function () {\n      delete file.$ngfDurationPromise;\n    });\n    return file.$ngfDurationPromise;\n  };\n  return upload;\n}\n]);\n\nngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {\n  var upload = UploadValidate;\n\n  /**\n   * Conserve aspect ratio of the original region. Useful when shrinking/enlarging\n   * images to fit into a certain area.\n   * Source:  http://stackoverflow.com/a/14731922\n   *\n   * @param {Number} srcWidth Source area width\n   * @param {Number} srcHeight Source area height\n   * @param {Number} maxWidth Nestable area maximum available width\n   * @param {Number} maxHeight Nestable area maximum available height\n   * @return {Object} { width, height }\n   */\n  var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {\n    var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :\n      Math.min(maxWidth / srcWidth, maxHeight / srcHeight);\n    return {\n      width: srcWidth * ratio, height: srcHeight * ratio,\n      marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight\n    };\n  };\n\n  // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89\n  var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {\n    var deferred = $q.defer();\n    var canvasElement = document.createElement('canvas');\n    var imageElement = document.createElement('img');\n    imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');\n    document.body.appendChild(imageElement);\n\n    imageElement.onload = function () {\n      var imgWidth = imageElement.width, imgHeight = imageElement.height;\n      imageElement.parentNode.removeChild(imageElement);\n      if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {\n        deferred.reject('resizeIf');\n        return;\n      }\n      try {\n        if (ratio) {\n          var ratioFloat = upload.ratioToFloat(ratio);\n          var imgRatio = imgWidth / imgHeight;\n          if (imgRatio < ratioFloat) {\n            width = imgWidth;\n            height = width / ratioFloat;\n          } else {\n            height = imgHeight;\n            width = height * ratioFloat;\n          }\n        }\n        if (!width) {\n          width = imgWidth;\n        }\n        if (!height) {\n          height = imgHeight;\n        }\n        var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);\n        canvasElement.width = Math.min(dimensions.width, width);\n        canvasElement.height = Math.min(dimensions.height, height);\n        var context = canvasElement.getContext('2d');\n        context.drawImage(imageElement,\n          Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),\n          dimensions.width, dimensions.height);\n        deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));\n      } catch (e) {\n        deferred.reject(e);\n      }\n    };\n    imageElement.onerror = function () {\n      imageElement.parentNode.removeChild(imageElement);\n      deferred.reject();\n    };\n    imageElement.src = imagen;\n    return deferred.promise;\n  };\n\n  upload.dataUrltoBlob = function (dataurl, name, origSize) {\n    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],\n      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n);\n    }\n    var blob = new window.Blob([u8arr], {type: mime});\n    blob.name = name;\n    blob.$ngfOrigSize = origSize;\n    return blob;\n  };\n\n  upload.isResizeSupported = function () {\n    var elem = document.createElement('canvas');\n    return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;\n  };\n\n  if (upload.isResizeSupported()) {\n    // add name getter to the blob constructor prototype\n    Object.defineProperty(window.Blob.prototype, 'name', {\n      get: function () {\n        return this.$ngfName;\n      },\n      set: function (v) {\n        this.$ngfName = v;\n      },\n      configurable: true\n    });\n  }\n\n  upload.resize = function (file, options) {\n    if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);\n\n    var deferred = $q.defer();\n    upload.dataUrl(file, true).then(function (url) {\n      resize(url, options.width, options.height, options.quality, options.type || file.type,\n        options.ratio, options.centerCrop, options.resizeIf)\n        .then(function (dataUrl) {\n          if (file.type === 'image/jpeg' && options.restoreExif !== false) {\n            try {\n              dataUrl = upload.restoreExif(url, dataUrl);\n            } catch (e) {\n              setTimeout(function () {throw e;}, 1);\n            }\n          }\n          try {\n            var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);\n            deferred.resolve(blob);\n          } catch (e) {\n            deferred.reject(e);\n          }\n        }, function (r) {\n          if (r === 'resizeIf') {\n            deferred.resolve(file);\n          }\n          deferred.reject(r);\n        });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  return upload;\n}]);\n\n(function () {\n  ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',\n    function ($parse, $timeout, $window, Upload, $http, $q) {\n      return {\n        restrict: 'AEC',\n        require: '?ngModel',\n        link: function (scope, elem, attr, ngModel) {\n          linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);\n        }\n      };\n    }]);\n\n  ngFileUpload.directive('ngfNoFileDrop', function () {\n    return function (scope, elem) {\n      if (dropAvailable()) elem.css('display', 'none');\n    };\n  });\n\n  ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {\n    return function (scope, elem, attr) {\n      if (dropAvailable()) {\n        var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));\n        $timeout(function () {\n          model(scope);\n          if (model.assign) {\n            model.assign(scope, true);\n          }\n        });\n      }\n    };\n  }]);\n\n  function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {\n    var available = dropAvailable();\n\n    var attrGetter = function (name, scope, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    if (attrGetter('dropAvailable')) {\n      $timeout(function () {\n        if (scope[attrGetter('dropAvailable')]) {\n          scope[attrGetter('dropAvailable')].value = available;\n        } else {\n          scope[attrGetter('dropAvailable')] = available;\n        }\n      });\n    }\n    if (!available) {\n      if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {\n        elem.css('display', 'none');\n      }\n      return;\n    }\n\n    function isDisabled() {\n      return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);\n    }\n\n    if (attrGetter('ngfSelect') == null) {\n      upload.registerModelChangeValidator(ngModel, attr, scope);\n    }\n\n    var leaveTimeout = null;\n    var stopPropagation = $parse(attrGetter('ngfStopPropagation'));\n    var dragOverDelay = 1;\n    var actualDragOverClass;\n\n    elem[0].addEventListener('dragover', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      // handling dragover events from the Chrome download bar\n      if (navigator.userAgent.indexOf('Chrome') > -1) {\n        var b = evt.dataTransfer.effectAllowed;\n        evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';\n      }\n      $timeout.cancel(leaveTimeout);\n      if (!actualDragOverClass) {\n        actualDragOverClass = 'C';\n        calculateDragOverClass(scope, attr, evt, function (clazz) {\n          actualDragOverClass = clazz;\n          elem.addClass(actualDragOverClass);\n          attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});\n        });\n      }\n    }, false);\n    elem[0].addEventListener('dragenter', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n    }, false);\n    elem[0].addEventListener('dragleave', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      leaveTimeout = $timeout(function () {\n        if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n        actualDragOverClass = null;\n        attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});\n      }, dragOverDelay || 100);\n    }, false);\n    elem[0].addEventListener('drop', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n      actualDragOverClass = null;\n      extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');\n    }, false);\n    elem[0].addEventListener('paste', function (evt) {\n      if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n        attrGetter('ngfEnableFirefoxPaste', scope)) {\n        evt.preventDefault();\n      }\n      if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;\n      extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');\n    }, false);\n\n    if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n      attrGetter('ngfEnableFirefoxPaste', scope)) {\n      elem.attr('contenteditable', true);\n      elem.on('keypress', function (e) {\n        if (!e.metaKey && !e.ctrlKey) {\n          e.preventDefault();\n        }\n      });\n    }\n\n    function extractFilesAndUpdateModel(source, evt, updateOnType) {\n      if (!source) return;\n      // html needs to be calculated on the same process otherwise the data will be wiped\n      // after promise resolve or setTimeout.\n      var html;\n      try {\n        html = source && source.getData && source.getData('text/html');\n      } catch (e) {/* Fix IE11 that throw error calling getData */\n      }\n      extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,\n        attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {\n        if (files.length) {\n          updateModel(files, evt);\n        } else {\n          extractFilesFromHtml(updateOnType, html).then(function (files) {\n            updateModel(files, evt);\n          });\n        }\n      });\n    }\n\n    function updateModel(files, evt) {\n      upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);\n    }\n\n    function extractFilesFromHtml(updateOn, html) {\n      if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);\n      var urls = [];\n      html.replace(/<(img src|img [^>]* src) *=\\\"([^\\\"]*)\\\"/gi, function (m, n, src) {\n        urls.push(src);\n      });\n      var promises = [], files = [];\n      if (urls.length) {\n        angular.forEach(urls, function (url) {\n          promises.push(upload.urlToBlob(url).then(function (blob) {\n            files.push(blob);\n          }));\n        });\n        var defer = $q.defer();\n        $q.all(promises).then(function () {\n          defer.resolve(files);\n        }, function (e) {\n          defer.reject(e);\n        });\n        return defer.promise;\n      }\n      return upload.emptyPromise();\n    }\n\n    function calculateDragOverClass(scope, attr, evt, callback) {\n      var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';\n      if (angular.isString(obj)) {\n        dClass = obj;\n      } else if (obj) {\n        if (obj.delay) dragOverDelay = obj.delay;\n        if (obj.accept || obj.reject) {\n          var items = evt.dataTransfer.items;\n          if (items == null || !items.length) {\n            dClass = obj.accept;\n          } else {\n            var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});\n            var len = items.length;\n            while (len--) {\n              if (!upload.validatePattern(items[len], pattern)) {\n                dClass = obj.reject;\n                break;\n              } else {\n                dClass = obj.accept;\n              }\n            }\n          }\n        }\n      }\n      callback(dClass);\n    }\n\n    function extractFiles(items, fileList, allowDir, multiple) {\n      var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');\n      if (maxFiles == null) {\n        maxFiles = Number.MAX_VALUE;\n      }\n      var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');\n      if (maxTotalSize == null) {\n        maxTotalSize = Number.MAX_VALUE;\n      }\n      var includeDir = attrGetter('ngfIncludeDir', scope);\n      var files = [], totalSize = 0;\n\n      function traverseFileTree(entry, path) {\n        var defer = $q.defer();\n        if (entry != null) {\n          if (entry.isDirectory) {\n            var promises = [upload.emptyPromise()];\n            if (includeDir) {\n              var file = {type: 'directory'};\n              file.name = file.path = (path || '') + entry.name;\n              files.push(file);\n            }\n            var dirReader = entry.createReader();\n            var entries = [];\n            var readEntries = function () {\n              dirReader.readEntries(function (results) {\n                try {\n                  if (!results.length) {\n                    angular.forEach(entries.slice(0), function (e) {\n                      if (files.length <= maxFiles && totalSize <= maxTotalSize) {\n                        promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));\n                      }\n                    });\n                    $q.all(promises).then(function () {\n                      defer.resolve();\n                    }, function (e) {\n                      defer.reject(e);\n                    });\n                  } else {\n                    entries = entries.concat(Array.prototype.slice.call(results || [], 0));\n                    readEntries();\n                  }\n                } catch (e) {\n                  defer.reject(e);\n                }\n              }, function (e) {\n                defer.reject(e);\n              });\n            };\n            readEntries();\n          } else {\n            entry.file(function (file) {\n              try {\n                file.path = (path ? path : '') + file.name;\n                if (includeDir) {\n                  file = upload.rename(file, file.path);\n                }\n                files.push(file);\n                totalSize += file.size;\n                defer.resolve();\n              } catch (e) {\n                defer.reject(e);\n              }\n            }, function (e) {\n              defer.reject(e);\n            });\n          }\n        }\n        return defer.promise;\n      }\n\n      var promises = [upload.emptyPromise()];\n\n      if (items && items.length > 0 && $window.location.protocol !== 'file:') {\n        for (var i = 0; i < items.length; i++) {\n          if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {\n            var entry = items[i].webkitGetAsEntry();\n            if (entry.isDirectory && !allowDir) {\n              continue;\n            }\n            if (entry != null) {\n              promises.push(traverseFileTree(entry));\n            }\n          } else {\n            var f = items[i].getAsFile();\n            if (f != null) {\n              files.push(f);\n              totalSize += f.size;\n            }\n          }\n          if (files.length > maxFiles || totalSize > maxTotalSize ||\n            (!multiple && files.length > 0)) break;\n        }\n      } else {\n        if (fileList != null) {\n          for (var j = 0; j < fileList.length; j++) {\n            var file = fileList.item(j);\n            if (file.type || file.size > 0) {\n              files.push(file);\n              totalSize += file.size;\n            }\n            if (files.length > maxFiles || totalSize > maxTotalSize ||\n              (!multiple && files.length > 0)) break;\n          }\n        }\n      }\n\n      var defer = $q.defer();\n      $q.all(promises).then(function () {\n        if (!multiple && !includeDir && files.length) {\n          var i = 0;\n          while (files[i] && files[i].type === 'directory') i++;\n          defer.resolve([files[i]]);\n        } else {\n          defer.resolve(files);\n        }\n      }, function (e) {\n        defer.reject(e);\n      });\n\n      return defer.promise;\n    }\n  }\n\n  function dropAvailable() {\n    var div = document.createElement('div');\n    return ('draggable' in div) && ('ondrop' in div) && !/Edge\\/12./i.test(navigator.userAgent);\n  }\n\n})();\n\n// customized version of https://github.com/exif-js/exif-js\nngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {\n  var upload = UploadResize;\n\n  upload.isExifSupported = function () {\n    return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();\n  };\n\n  function applyTransform(ctx, orientation, width, height) {\n    switch (orientation) {\n      case 2:\n        return ctx.transform(-1, 0, 0, 1, width, 0);\n      case 3:\n        return ctx.transform(-1, 0, 0, -1, width, height);\n      case 4:\n        return ctx.transform(1, 0, 0, -1, 0, height);\n      case 5:\n        return ctx.transform(0, 1, 1, 0, 0, 0);\n      case 6:\n        return ctx.transform(0, 1, -1, 0, height, 0);\n      case 7:\n        return ctx.transform(0, -1, -1, 0, height, width);\n      case 8:\n        return ctx.transform(0, -1, 1, 0, 0, width);\n    }\n  }\n\n  upload.readOrientation = function (file) {\n    var defer = $q.defer();\n    var reader = new FileReader();\n    var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;\n    reader.readAsArrayBuffer(slicedFile);\n    reader.onerror = function (e) {\n      return defer.reject(e);\n    };\n    reader.onload = function (e) {\n      var result = {orientation: 1};\n      var view = new DataView(this.result);\n      if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);\n\n      var length = view.byteLength,\n        offset = 2;\n      while (offset < length) {\n        var marker = view.getUint16(offset, false);\n        offset += 2;\n        if (marker === 0xFFE1) {\n          if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);\n\n          var little = view.getUint16(offset += 6, false) === 0x4949;\n          offset += view.getUint32(offset + 4, little);\n          var tags = view.getUint16(offset, little);\n          offset += 2;\n          for (var i = 0; i < tags; i++)\n            if (view.getUint16(offset + (i * 12), little) === 0x0112) {\n              var orientation = view.getUint16(offset + (i * 12) + 8, little);\n              if (orientation >= 2 && orientation <= 8) {\n                view.setUint16(offset + (i * 12) + 8, 1, little);\n                result.fixedArrayBuffer = e.target.result;\n              }\n              result.orientation = orientation;\n              return defer.resolve(result);\n            }\n        } else if ((marker & 0xFF00) !== 0xFF00) break;\n        else offset += view.getUint16(offset, false);\n      }\n      return defer.resolve(result);\n    };\n    return defer.promise;\n  };\n\n  function arrayBufferToBase64(buffer) {\n    var binary = '';\n    var bytes = new Uint8Array(buffer);\n    var len = bytes.byteLength;\n    for (var i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return window.btoa(binary);\n  }\n\n  upload.applyExifRotation = function (file) {\n    if (file.type.indexOf('image/jpeg') !== 0) {\n      return upload.emptyPromise(file);\n    }\n\n    var deferred = $q.defer();\n    upload.readOrientation(file).then(function (result) {\n      if (result.orientation < 2 || result.orientation > 8) {\n        return deferred.resolve(file);\n      }\n      upload.dataUrl(file, true).then(function (url) {\n        var canvas = document.createElement('canvas');\n        var img = document.createElement('img');\n\n        img.onload = function () {\n          try {\n            canvas.width = result.orientation > 4 ? img.height : img.width;\n            canvas.height = result.orientation > 4 ? img.width : img.height;\n            var ctx = canvas.getContext('2d');\n            applyTransform(ctx, result.orientation, img.width, img.height);\n            ctx.drawImage(img, 0, 0);\n            var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);\n            dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);\n            var blob = upload.dataUrltoBlob(dataUrl, file.name);\n            deferred.resolve(blob);\n          } catch (e) {\n            return deferred.reject(e);\n          }\n        };\n        img.onerror = function () {\n          deferred.reject();\n        };\n        img.src = url;\n      }, function (e) {\n        deferred.reject(e);\n      });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  upload.restoreExif = function (orig, resized) {\n    var ExifRestorer = {};\n\n    ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +\n      'QRSTUVWXYZabcdef' +\n      'ghijklmnopqrstuv' +\n      'wxyz0123456789+/' +\n      '=';\n\n    ExifRestorer.encode64 = function (input) {\n      var output = '',\n        chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0;\n\n      do {\n        chr1 = input[i++];\n        chr2 = input[i++];\n        chr3 = input[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n          enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n          enc4 = 64;\n        }\n\n        output = output +\n          this.KEY_STR.charAt(enc1) +\n          this.KEY_STR.charAt(enc2) +\n          this.KEY_STR.charAt(enc3) +\n          this.KEY_STR.charAt(enc4);\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n      } while (i < input.length);\n\n      return output;\n    };\n\n    ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {\n      if (origFileBase64.match('data:image/jpeg;base64,')) {\n        origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');\n      }\n\n      var rawImage = this.decode64(origFileBase64);\n      var segments = this.slice2Segments(rawImage);\n\n      var image = this.exifManipulation(resizedFileBase64, segments);\n\n      return 'data:image/jpeg;base64,' + this.encode64(image);\n    };\n\n\n    ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {\n      var exifArray = this.getExifArray(segments),\n        newImageArray = this.insertExif(resizedFileBase64, exifArray);\n      return new Uint8Array(newImageArray);\n    };\n\n\n    ExifRestorer.getExifArray = function (segments) {\n      var seg;\n      for (var x = 0; x < segments.length; x++) {\n        seg = segments[x];\n        if (seg[0] === 255 & seg[1] === 225) //(ff e1)\n        {\n          return seg;\n        }\n      }\n      return [];\n    };\n\n\n    ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {\n      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),\n        buf = this.decode64(imageData),\n        separatePoint = buf.indexOf(255, 3),\n        mae = buf.slice(0, separatePoint),\n        ato = buf.slice(separatePoint),\n        array = mae;\n\n      array = array.concat(exifArray);\n      array = array.concat(ato);\n      return array;\n    };\n\n\n    ExifRestorer.slice2Segments = function (rawImageArray) {\n      var head = 0,\n        segments = [];\n\n      while (1) {\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {\n          break;\n        }\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {\n          head += 2;\n        }\n        else {\n          var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n            endPoint = head + length + 2,\n            seg = rawImageArray.slice(head, endPoint);\n          segments.push(seg);\n          head = endPoint;\n        }\n        if (head > rawImageArray.length) {\n          break;\n        }\n      }\n\n      return segments;\n    };\n\n\n    ExifRestorer.decode64 = function (input) {\n      var chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0,\n        buf = [];\n\n      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n      var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n      if (base64test.exec(input)) {\n        console.log('There were invalid base64 characters in the input text.\\n' +\n          'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and \"=\"\\n' +\n          'Expect errors in decoding.');\n      }\n      input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, '');\n\n      do {\n        enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        buf.push(chr1);\n\n        if (enc3 !== 64) {\n          buf.push(chr2);\n        }\n        if (enc4 !== 64) {\n          buf.push(chr3);\n        }\n\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n\n      } while (i < input.length);\n\n      return buf;\n    };\n\n    return ExifRestorer.restore(orig, resized);  //<= EXIF\n  };\n\n  return upload;\n}]);\n\n"
  },
  {
    "path": "index.js",
    "content": "require('./dist/ng-file-upload-all');\nmodule.exports = 'ngFileUpload';"
  },
  {
    "path": "nuget/Package.nuspec",
    "content": "<?xml version=\"1.0\"?>\n<package >\n  <metadata>\n    <id>angular-file-upload</id>\n    <title>Angular file upload</title>\n    <version>12.2.13</version>\n    <authors>Danial Farid, Georgios Diamantopoulos (nuget package)</authors>\n    <owners>Danial Farid</owners>\n    <licenseUrl>https://github.com/danialfarid/ng-file-upload/blob/master/LICENSE</licenseUrl>\n    <projectUrl>https://github.com/danialfarid/ng-file-upload</projectUrl>\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\n    <description>Light-weight HTML5 and cross-browser AngularJS directives for file upload, progress, abort, drag and drop</description>\n    <tags>angularjs upload</tags>\n    <dependencies>\n    </dependencies>\n  </metadata>\n</package>\n"
  },
  {
    "path": "nuget/build.bat",
    "content": "NuGet Update -self\n\nrmdir /s /q content\nmkdir content\nmkdir content\\scripts\ncopy ..\\dist\\* content\\scripts\ndel angular-file-upload.*\n\nNuGet Pack Package.nuspec\n\nfor %%f in (angular-file-upload.*) do (\n\tNuGet Push %%f\n\trmdir /s /q content\n\tdel %%f\n)\n\n"
  },
  {
    "path": "nuget/nuget.sh",
    "content": "#!/bin/sh\n# add a simple 'nuget' command to Mac OS X under Mono\n# get NuGet.exe binary from http://nuget.codeplex.com/releases/view/58939\n# get Microsoft.Build.dll from a Windows .NET 4.0 installation\n# copy to /usr/local/bin and Robert is your father's brother....\n#\nPATH=/usr/local/bin:$PATH\nmono --runtime=v4.0 /usr/local/bin/NuGet.exe $*\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ng-file-upload\",\n  \"version\": \"12.2.13\",\n  \"devDependencies\": {\n    \"grunt\": \"^0.4.5\",\n    \"grunt-contrib-concat\": \"^0.5.1\",\n    \"grunt-contrib-clean\": \"^0.6.0\",\n    \"grunt-contrib-jshint\": \"^0.11.0\",\n    \"grunt-contrib-uglify\": \"^0.8.0\",\n    \"grunt-contrib-watch\": \"^0.6.1\",\n    \"grunt-contrib-copy\": \"~0.4.1\",\n    \"grunt-text-replace\": \"~0.3.12\",\n    \"grunt-git\": \"~0.2.14\",\n    \"grunt-serve\": \"^0.1.6\",\n    \"jshint-stylish\": \"^1.0.0\",\n    \"load-grunt-tasks\": \"^3.1.0\"\n  },\n  \"description\": \"An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers\",\n  \"files\": [\n    \"index.js\",\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/danialfarid/ng-file-upload.git\"\n  },\n  \"keywords\": [\n    \"angularjs\",\n    \"ng-file-upload\",\n    \"file-upload\",\n    \"javascript\"\n  ],\n  \"author\": \"danial.farid@gmail.com\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/danialfarid/ng-file-upload/issues\"\n  },\n  \"homepage\": \"https://github.com/danialfarid/ng-file-upload\"\n}\n"
  },
  {
    "path": "release.sh",
    "content": "echo version: $2\necho message: $1\n\ngrunt\ngit add .\ngit add -u .\ngit commit -am \"$1\"\ngit pull\ngit push\ncd ../angular-file-upload-shim-bower\ngit add .\ngit add -u .\ngit commit -am \"$2\"\ngit pull\ngit push\ncd ../angular-file-upload-bower\ngit add .\ngit add -u .\ngit commit -am \"$2\"\ngit pull\ngit push \n\n\nAPI_JSON=$(printf '{\"tag_name\": \"%s\",\"target_commitish\": \"master\",\"name\": \"Version %s\",\"body\": \"%s\",\"draft\": false,\"prerelease\": false}' $2 $2 \"$1\")\n\necho commit json: $API_JSON\n\ncurl --data \"$API_JSON\" https://api.github.com/repos/danialfarid/ng-file-upload/releases?access_token=$3\n\ncurl --data \"$API_JSON\" https://api.github.com/repos/danialfarid/ng-file-upload-shim-bower/releases?access_token=$3\n\ncurl --data \"$API_JSON\" https://api.github.com/repos/danialfarid/ng-file-upload-bower/releases?access_token=$3\n\ncd ../ng-file-upload\nnpm publish\n\ncd ../angular-file-upload-bower\nmeteor publish\n\ncd ../ng-file-upload\n"
  },
  {
    "path": "src/FileAPI.js",
    "content": "/*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git\n * 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.\n */\n\n/*\n * JavaScript Canvas to Blob 2.0.5\n * https://github.com/blueimp/JavaScript-Canvas-to-Blob\n *\n * Copyright 2012, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n *\n * Based on stackoverflow user Stoive's code snippet:\n * http://stackoverflow.com/q/4998908\n */\n\n/*jslint nomen: true, regexp: true */\n/*global window, atob, Blob, ArrayBuffer, Uint8Array */\n\n(function (window) {\n    'use strict';\n    var CanvasPrototype = window.HTMLCanvasElement &&\n            window.HTMLCanvasElement.prototype,\n        hasBlobConstructor = window.Blob && (function () {\n            try {\n                return Boolean(new Blob());\n            } catch (e) {\n                return false;\n            }\n        }()),\n        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&\n            (function () {\n                try {\n                    return new Blob([new Uint8Array(100)]).size === 100;\n                } catch (e) {\n                    return false;\n                }\n            }()),\n        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||\n            window.MozBlobBuilder || window.MSBlobBuilder,\n        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&\n            window.ArrayBuffer && window.Uint8Array && function (dataURI) {\n                var byteString,\n                    arrayBuffer,\n                    intArray,\n                    i,\n                    mimeString,\n                    bb;\n                if (dataURI.split(',')[0].indexOf('base64') >= 0) {\n                    // Convert base64 to raw binary data held in a string:\n                    byteString = atob(dataURI.split(',')[1]);\n                } else {\n                    // Convert base64/URLEncoded data component to raw binary data:\n                    byteString = decodeURIComponent(dataURI.split(',')[1]);\n                }\n                // Write the bytes of the string to an ArrayBuffer:\n                arrayBuffer = new ArrayBuffer(byteString.length);\n                intArray = new Uint8Array(arrayBuffer);\n                for (i = 0; i < byteString.length; i += 1) {\n                    intArray[i] = byteString.charCodeAt(i);\n                }\n                // Separate out the mime component:\n                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];\n                // Write the ArrayBuffer (or ArrayBufferView) to a blob:\n                if (hasBlobConstructor) {\n                    return new Blob(\n                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],\n                        {type: mimeString}\n                    );\n                }\n                bb = new BlobBuilder();\n                bb.append(arrayBuffer);\n                return bb.getBlob(mimeString);\n            };\n    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {\n        if (CanvasPrototype.mozGetAsFile) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {\n                    callback(dataURLtoBlob(this.toDataURL(type, quality)));\n                } else {\n                    callback(this.mozGetAsFile('blob', type));\n                }\n            };\n        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {\n            CanvasPrototype.toBlob = function (callback, type, quality) {\n                callback(dataURLtoBlob(this.toDataURL(type, quality)));\n            };\n        }\n    }\n    window.dataURLtoBlob = dataURLtoBlob;\n})(window);\n\n/*jslint evil: true */\n/*global window, URL, webkitURL, ActiveXObject */\n\n(function (window, undef){\n\t'use strict';\n\n\tvar\n\t\tgid = 1,\n\t\tnoop = function (){},\n\n\t\tdocument = window.document,\n\t\tdoctype = document.doctype || {},\n\t\tuserAgent = window.navigator.userAgent,\n\n\t\t// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48\n\t\tapiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),\n\n\t\tBlob = window.Blob,\n\t\tFile = window.File,\n\t\tFileReader = window.FileReader,\n\t\tFormData = window.FormData,\n\n\n\t\tXMLHttpRequest = window.XMLHttpRequest,\n\t\tjQuery = window.jQuery,\n\n\t\thtml5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))\n\t\t\t\t&& !(/safari\\//i.test(userAgent) && !/chrome\\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25\n\n\t\tcors = html5 && ('withCredentials' in (new XMLHttpRequest)),\n\n\t\tchunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),\n\n\t\t// https://github.com/blueimp/JavaScript-Canvas-to-Blob\n\t\tdataURLtoBlob = window.dataURLtoBlob,\n\n\n\t\t_rimg = /img/i,\n\t\t_rcanvas = /canvas/i,\n\t\t_rimgcanvas = /img|canvas/i,\n\t\t_rinput = /input/i,\n\t\t_rdata = /^data:[^,]+,/,\n\n\t\t_toString = {}.toString,\n\n\n\t\tMath = window.Math,\n\n\t\t_SIZE_CONST = function (pow){\n\t\t\tpow = new window.Number(Math.pow(1024, pow));\n\t\t\tpow.from = function (sz){ return Math.round(sz * this); };\n\t\t\treturn\tpow;\n\t\t},\n\n\t\t_elEvents = {}, // element event listeners\n\t\t_infoReader = [], // list of file info processors\n\n\t\t_readerEvents = 'abort progress error load loadend',\n\t\t_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),\n\n\t\tcurrentTarget = 'currentTarget', // for minimize\n\t\tpreventDefault = 'preventDefault', // and this too\n\n\t\t_isArray = function (ar) {\n\t\t\treturn\tar && ('length' in ar);\n\t\t},\n\n\t\t/**\n\t\t * Iterate over a object or array\n\t\t */\n\t\t_each = function (obj, fn, ctx){\n\t\t\tif( obj ){\n\t\t\t\tif( _isArray(obj) ){\n\t\t\t\t\tfor( var i = 0, n = obj.length; i < n; i++ ){\n\t\t\t\t\t\tif( i in obj ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[i], i, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfor( var key in obj ){\n\t\t\t\t\t\tif( obj.hasOwnProperty(key) ){\n\t\t\t\t\t\t\tfn.call(ctx, obj[key], key, obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Merge the contents of two or more objects together into the first object\n\t\t */\n\t\t_extend = function (dst){\n\t\t\tvar args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };\n\t\t\tfor( ; i < args.length; i++ ){\n\t\t\t\t_each(args[i], _ext);\n\t\t\t}\n\t\t\treturn  dst;\n\t\t},\n\n\t\t/**\n\t\t * Add event listener\n\t\t */\n\t\t_on = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el);\n\n\t\t\t\tif( !_elEvents[uid] ){\n\t\t\t\t\t_elEvents[uid] = {};\n\t\t\t\t}\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.add(el, type, fn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif( !_elEvents[uid][type] ){\n\t\t\t\t\t\t\t_elEvents[uid][type] = [];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t_elEvents[uid][type].push(fn);\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.addEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.attachEvent ){ el.attachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = fn; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t/**\n\t\t * Remove event listener\n\t\t */\n\t\t_off = function (el, type, fn){\n\t\t\tif( el ){\n\t\t\t\tvar uid = api.uid(el), events = _elEvents[uid] || {};\n\n\t\t\t\tvar isFileReader = (FileReader && el) && (el instanceof FileReader);\n\t\t\t\t_each(type.split(/\\s+/), function (type){\n\t\t\t\t\tif( jQuery && !isFileReader){\n\t\t\t\t\t\tjQuery.event.remove(el, type, fn);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar fns = events[type] || [], i = fns.length;\n\n\t\t\t\t\t\twhile( i-- ){\n\t\t\t\t\t\t\tif( fns[i] === fn ){\n\t\t\t\t\t\t\t\tfns.splice(i, 1);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( el.addEventListener ){ el.removeEventListener(type, fn, false); }\n\t\t\t\t\t\telse if( el.detachEvent ){ el.detachEvent('on'+type, fn); }\n\t\t\t\t\t\telse { el['on'+type] = null; }\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\n\t\t_one = function(el, type, fn){\n\t\t\t_on(el, type, function _(evt){\n\t\t\t\t_off(el, type, _);\n\t\t\t\tfn(evt);\n\t\t\t});\n\t\t},\n\n\n\t\t_fixEvent = function (evt){\n\t\t\tif( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }\n\t\t\tif( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }\n\t\t\treturn  evt;\n\t\t},\n\n\n\t\t_supportInputAttr = function (attr){\n\t\t\tvar input = document.createElement('input');\n\t\t\tinput.setAttribute('type', \"file\");\n\t\t\treturn attr in input;\n\t\t},\n\n\t\t/**\n\t\t * FileAPI (core object)\n\t\t */\n\t\tapi = {\n\t\t\tversion: '2.0.7',\n\n\t\t\tcors: false,\n\t\t\thtml5: true,\n\t\t\tmedia: false,\n\t\t\tformData: true,\n\t\t\tmultiPassResize: true,\n\n\t\t\tdebug: false,\n\t\t\tpingUrl: false,\n\t\t\tmultiFlash: false,\n\t\t\tflashAbortTimeout: 0,\n\t\t\twithCredentials: true,\n\n\t\t\tstaticPath: './dist/',\n\n\t\t\tflashUrl: 0, // @default: './FileAPI.flash.swf'\n\t\t\tflashImageUrl: 0, // @default: './FileAPI.flash.image.swf'\n\n\t\t\tpostNameConcat: function (name, idx){\n\t\t\t\treturn\tname + (idx != null ? '['+ idx +']' : '');\n\t\t\t},\n\n\t\t\text2mime: {\n\t\t\t\t  jpg:\t'image/jpeg'\n\t\t\t\t, tif:\t'image/tiff'\n\t\t\t\t, txt:\t'text/plain'\n\t\t\t},\n\n\t\t\t// Fallback for flash\n\t\t\taccept: {\n\t\t\t\t  'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'\n\t\t\t\t, 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'\n\t\t\t\t, 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'\n\t\t\t},\n\n\t\t\tuploadRetry : 0,\n\t\t\tnetworkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down\n\n\t\t\tchunkSize : 0,\n\t\t\tchunkUploadRetry : 0,\n\t\t\tchunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down\n\n\t\t\tKB: _SIZE_CONST(1),\n\t\t\tMB: _SIZE_CONST(2),\n\t\t\tGB: _SIZE_CONST(3),\n\t\t\tTB: _SIZE_CONST(4),\n\n\t\t\tEMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',\n\n\t\t\texpando: 'fileapi' + (new Date).getTime(),\n\n\t\t\tuid: function (obj){\n\t\t\t\treturn\tobj\n\t\t\t\t\t? (obj[api.expando] = obj[api.expando] || api.uid())\n\t\t\t\t\t: (++gid, api.expando + gid)\n\t\t\t\t;\n\t\t\t},\n\n\t\t\tlog: function (){\n\t\t\t\t// ngf fix for IE8 #1071\n\t\t\t\tif( api.debug && api._supportConsoleLog ){\n\t\t\t\t\tif( api._supportConsoleLogApply ){\n\t\t\t\t\t\tconsole.log.apply(console, arguments);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tconsole.log([].join.call(arguments, ' '));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Create new image\n\t\t\t *\n\t\t\t * @param {String} [src]\n\t\t\t * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element\n\t\t\t * @returns {HTMLElement}\n\t\t\t */\n\t\t\tnewImage: function (src, fn){\n\t\t\t\tvar img = document.createElement('img');\n\t\t\t\tif( fn ){\n\t\t\t\t\tapi.event.one(img, 'error load', function (evt){\n\t\t\t\t\t\tfn(evt.type == 'error', img);\n\t\t\t\t\t\timg = null;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\timg.src = src;\n\t\t\t\treturn\timg;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Get XHR\n\t\t\t * @returns {XMLHttpRequest}\n\t\t\t */\n\t\t\tgetXHR: function (){\n\t\t\t\tvar xhr;\n\n\t\t\t\tif( XMLHttpRequest ){\n\t\t\t\t\txhr = new XMLHttpRequest;\n\t\t\t\t}\n\t\t\t\telse if( window.ActiveXObject ){\n\t\t\t\t\ttry {\n\t\t\t\t\t\txhr = new ActiveXObject('MSXML2.XMLHttp.3.0');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\txhr = new ActiveXObject('Microsoft.XMLHTTP');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tisArray: _isArray,\n\n\t\t\tsupport: {\n\t\t\t\tdnd:     cors && ('ondrop' in document.createElement('div')),\n\t\t\t\tcors:    cors,\n\t\t\t\thtml5:   html5,\n\t\t\t\tchunked: chunked,\n\t\t\t\tdataURI: true,\n\t\t\t\taccept:   _supportInputAttr('accept'),\n\t\t\t\tmultiple: _supportInputAttr('multiple')\n\t\t\t},\n\n\t\t\tevent: {\n\t\t\t\t  on: _on\n\t\t\t\t, off: _off\n\t\t\t\t, one: _one\n\t\t\t\t, fix: _fixEvent\n\t\t\t},\n\n\n\t\t\tthrottle: function(fn, delay) {\n\t\t\t\tvar id, args;\n\n\t\t\t\treturn function _throttle(){\n\t\t\t\t\targs = arguments;\n\n\t\t\t\t\tif( !id ){\n\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\tid = setTimeout(function (){\n\t\t\t\t\t\t\tid = 0;\n\t\t\t\t\t\t\tfn.apply(window, args);\n\t\t\t\t\t\t}, delay);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\n\t\t\tF: function (){},\n\n\n\t\t\tparseJSON: function (str){\n\t\t\t\tvar json;\n\t\t\t\tif( window.JSON && JSON.parse ){\n\t\t\t\t\tjson = JSON.parse(str);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tjson = (new Function('return ('+str.replace(/([\\r\\n])/g, '\\\\$1')+');'))();\n\t\t\t\t}\n\t\t\t\treturn json;\n\t\t\t},\n\n\n\t\t\ttrim: function (str){\n\t\t\t\tstr = String(str);\n\t\t\t\treturn\tstr.trim ? str.trim() : str.replace(/^\\s+|\\s+$/g, '');\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Simple Defer\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\tdefer: function (){\n\t\t\t\tvar\n\t\t\t\t\t  list = []\n\t\t\t\t\t, result\n\t\t\t\t\t, error\n\t\t\t\t\t, defer = {\n\t\t\t\t\t\tresolve: function (err, res){\n\t\t\t\t\t\t\tdefer.resolve = noop;\n\t\t\t\t\t\t\terror\t= err || false;\n\t\t\t\t\t\t\tresult\t= res;\n\n\t\t\t\t\t\t\twhile( res = list.shift() ){\n\t\t\t\t\t\t\t\tres(error, result);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tthen: function (fn){\n\t\t\t\t\t\t\tif( error !== undef ){\n\t\t\t\t\t\t\t\tfn(error, result);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlist.push(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\treturn\tdefer;\n\t\t\t},\n\n\t\t\tqueue: function (fn){\n\t\t\t\tvar\n\t\t\t\t\t  _idx = 0\n\t\t\t\t\t, _length = 0\n\t\t\t\t\t, _fail = false\n\t\t\t\t\t, _end = false\n\t\t\t\t\t, queue = {\n\t\t\t\t\t\tinc: function (){\n\t\t\t\t\t\t\t_length++;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tnext: function (){\n\t\t\t\t\t\t\t_idx++;\n\t\t\t\t\t\t\tsetTimeout(queue.check, 0);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tcheck: function (){\n\t\t\t\t\t\t\t(_idx >= _length) && !_fail && queue.end();\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tisFail: function (){\n\t\t\t\t\t\t\treturn _fail;\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tfail: function (){\n\t\t\t\t\t\t\t!_fail && fn(_fail = true);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\tend: function (){\n\t\t\t\t\t\t\tif( !_end ){\n\t\t\t\t\t\t\t\t_end = true;\n\t\t\t\t\t\t\t\tfn();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t;\n\t\t\t\treturn queue;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * For each object\n\t\t\t *\n\t\t\t * @param\t{Object|Array}\tobj\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{*}\t\t\t\t[ctx]\n\t\t\t */\n\t\t\teach: _each,\n\n\n\t\t\t/**\n\t\t\t * Async for\n\t\t\t * @param {Array} array\n\t\t\t * @param {Function} callback\n\t\t\t */\n\t\t\tafor: function (array, callback){\n\t\t\t\tvar i = 0, n = array.length;\n\n\t\t\t\tif( _isArray(array) && n-- ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tcallback(n != i && _next, array[i], i++);\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Merge the contents of two or more objects together into the first object\n\t\t\t *\n\t\t\t * @param\t{Object}\tdst\n\t\t\t * @return\t{Object}\n\t\t\t */\n\t\t\textend: _extend,\n\n\n\t\t\t/**\n\t\t\t * Is file?\n\t\t\t * @param  {File}  file\n\t\t\t * @return {Boolean}\n\t\t\t */\n\t\t\tisFile: function (file){\n\t\t\t\treturn _toString.call(file) === '[object File]';\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is blob?\n\t\t\t * @param   {Blob}  blob\n\t\t\t * @returns {Boolean}\n\t\t\t */\n\t\t\tisBlob: function (blob) {\n\t\t\t\treturn this.isFile(blob) || (_toString.call(blob) === '[object Blob]');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Is canvas element\n\t\t\t *\n\t\t\t * @param\t{HTMLElement}\tel\n\t\t\t * @return\t{Boolean}\n\t\t\t */\n\t\t\tisCanvas: function (el){\n\t\t\t\treturn\tel && _rcanvas.test(el.nodeName);\n\t\t\t},\n\n\n\t\t\tgetFilesFilter: function (filter){\n\t\t\t\tfilter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');\n\t\t\t\treturn\tfilter ? new RegExp('('+ filter.replace(/\\./g, '\\\\.').replace(/,/g, '|') +')$', 'i') : /./;\n\t\t\t},\n\n\n\n\t\t\t/**\n\t\t\t * Read as DataURL\n\t\t\t *\n\t\t\t * @param {File|Element} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsDataURL: function (file, fn){\n\t\t\t\tif( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', api.toDataURL(file));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_readAs(file, fn, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as Binary string\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsBinaryString: function (file, fn){\n\t\t\t\tif( _hasSupportReadAs('BinaryString') ){\n\t\t\t\t\t_readAs(file, fn, 'BinaryString');\n\t\t\t\t} else {\n\t\t\t\t\t// Hello IE10!\n\t\t\t\t\t_readAs(file, function (evt){\n\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// dataURL -> binaryString\n\t\t\t\t\t\t\t\tevt.result = api.toBinaryString(evt.result);\n\t\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\tevt.message = e.toString();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfn(evt);\n\t\t\t\t\t}, 'DataURL');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as ArrayBuffer\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\treadAsArrayBuffer: function(file, fn){\n\t\t\t\t_readAs(file, fn, 'ArrayBuffer');\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read as text\n\t\t\t *\n\t\t\t * @param {File} file\n\t\t\t * @param {String} encoding\n\t\t\t * @param {Function} [fn]\n\t\t\t */\n\t\t\treadAsText: function(file, encoding, fn){\n\t\t\t\tif( !fn ){\n\t\t\t\t\tfn\t= encoding;\n\t\t\t\t\tencoding = 'utf-8';\n\t\t\t\t}\n\n\t\t\t\t_readAs(file, fn, 'Text', encoding);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Convert image or canvas to DataURL\n\t\t\t *\n\t\t\t * @param   {Element}  el      Image or Canvas element\n\t\t\t * @param   {String}   [type]  mime-type\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoDataURL: function (el, type){\n\t\t\t\tif( typeof el == 'string' ){\n\t\t\t\t\treturn  el;\n\t\t\t\t}\n\t\t\t\telse if( el.toDataURL ){\n\t\t\t\t\treturn  el.toDataURL(type || 'image/png');\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Canvert string, image or canvas to binary string\n\t\t\t *\n\t\t\t * @param   {String|Element} val\n\t\t\t * @return  {String}\n\t\t\t */\n\t\t\ttoBinaryString: function (val){\n\t\t\t\treturn  window.atob(api.toDataURL(val).replace(_rdata, ''));\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Read file or DataURL as ImageElement\n\t\t\t *\n\t\t\t * @param\t{File|String}\tfile\n\t\t\t * @param\t{Function}\t\tfn\n\t\t\t * @param\t{Boolean}\t\t[progress]\n\t\t\t */\n\t\t\treadAsImage: function (file, fn, progress){\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\tif( apiURL ){\n\t\t\t\t\t\t/** @namespace apiURL.createObjectURL */\n\t\t\t\t\t\tvar data = apiURL.createObjectURL(file);\n\t\t\t\t\t\tif( data === undef ){\n\t\t\t\t\t\t\t_emit(file, fn, 'error');\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tapi.readAsImage(data, fn, progress);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\tapi.readAsImage(evt.result, fn, progress);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( progress || evt.type == 'error' ){\n\t\t\t\t\t\t\t\t_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( api.isCanvas(file) ){\n\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t}\n\t\t\t\telse if( _rimg.test(file.nodeName) ){\n\t\t\t\t\tif( file.complete ){\n\t\t\t\t\t\t_emit(file, fn, 'load', file);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar events = 'error abort load';\n\t\t\t\t\t\t_one(file, events, function _fn(evt){\n\t\t\t\t\t\t\tif( evt.type == 'load' && apiURL ){\n\t\t\t\t\t\t\t\t/** @namespace apiURL.revokeObjectURL */\n\t\t\t\t\t\t\t\tapiURL.revokeObjectURL(file.src);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_off(file, events, _fn);\n\t\t\t\t\t\t\t_emit(file, fn, evt, file);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( file.iframe ){\n\t\t\t\t\t_emit(file, fn, { type: 'error' });\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Created image\n\t\t\t\t\tvar img = api.newImage(file.dataURL || file);\n\t\t\t\t\tapi.readAsImage(img, fn, progress);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Make file by name\n\t\t\t *\n\t\t\t * @param\t{String}\tname\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tcheckFileObj: function (name){\n\t\t\t\tvar file = {}, accept = api.accept;\n\n\t\t\t\tif( typeof name == 'object' ){\n\t\t\t\t\tfile = name;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfile.name = (name + '').split(/\\\\|\\//g).pop();\n\t\t\t\t}\n\n\t\t\t\tif( file.type == null ){\n\t\t\t\t\tfile.type = file.name.split('.').pop();\n\t\t\t\t}\n\n\t\t\t\t_each(accept, function (ext, type){\n\t\t\t\t\text = new RegExp(ext.replace(/\\s/g, '|'), 'i');\n\t\t\t\t\tif( ext.test(file.type) || api.ext2mime[file.type] ){\n\t\t\t\t\t\tfile.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn\tfile;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get drop files\n\t\t\t *\n\t\t\t * @param\t{Event}\tevt\n\t\t\t * @param\t{Function} callback\n\t\t\t */\n\t\t\tgetDropFiles: function (evt, callback){\n\t\t\t\tvar\n\t\t\t\t\t  files = []\n\t\t\t\t\t, dataTransfer = _getDataTransfer(evt)\n\t\t\t\t\t, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])\n\t\t\t\t\t, queue = api.queue(function (){ callback(files); })\n\t\t\t\t;\n\n\t\t\t\t_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){\n\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( entrySupport ){\n\t\t\t\t\t\t\t_readEntryAsFiles(item, function (err, entryFiles){\n\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\tapi.log('[err] getDropFiles:', err);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfiles.push.apply(files, entryFiles);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t_isRegularFile(item, function (yes){\n\t\t\t\t\t\t\t\tyes && files.push(item);\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcatch( err ){\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\tapi.log('[err] getDropFiles: ', err);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get file list\n\t\t\t *\n\t\t\t * @param\t{HTMLInputElement|Event}\tinput\n\t\t\t * @param\t{String|Function}\t[filter]\n\t\t\t * @param\t{Function}\t\t\t[callback]\n\t\t\t * @return\t{Array|Null}\n\t\t\t */\n\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\tvar files = [];\n\n\t\t\t\tif( callback ){\n\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tif( input.jquery ){\n\t\t\t\t\t// jQuery object\n\t\t\t\t\tinput.each(function (){\n\t\t\t\t\t\tfiles = files.concat(api.getFiles(this));\n\t\t\t\t\t});\n\t\t\t\t\tinput\t= files;\n\t\t\t\t\tfiles\t= [];\n\t\t\t\t}\n\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t}\n\n\t\t\t\tif( input.originalEvent ){\n\t\t\t\t\t// jQuery event\n\t\t\t\t\tinput = _fixEvent(input.originalEvent);\n\t\t\t\t}\n\t\t\t\telse if( input.srcElement ){\n\t\t\t\t\t// IE Event\n\t\t\t\t\tinput = _fixEvent(input);\n\t\t\t\t}\n\n\n\t\t\t\tif( input.dataTransfer ){\n\t\t\t\t\t// Drag'n'Drop\n\t\t\t\t\tinput = input.dataTransfer;\n\t\t\t\t}\n\t\t\t\telse if( input.target ){\n\t\t\t\t\t// Event\n\t\t\t\t\tinput = input.target;\n\t\t\t\t}\n\n\t\t\t\tif( input.files ){\n\t\t\t\t\t// Input[type=\"file\"]\n\t\t\t\t\tfiles = input.files;\n\n\t\t\t\t\tif( !html5 ){\n\t\t\t\t\t\t// Partial support for file api\n\t\t\t\t\t\tfiles[0].blob\t= input;\n\t\t\t\t\t\tfiles[0].iframe\t= true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( !html5 && isInputFile(input) ){\n\t\t\t\t\tif( api.trim(input.value) ){\n\t\t\t\t\t\tfiles = [api.checkFileObj(input.value)];\n\t\t\t\t\t\tfiles[0].blob   = input;\n\t\t\t\t\t\tfiles[0].iframe = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( _isArray(input) ){\n\t\t\t\t\tfiles\t= input;\n\t\t\t\t}\n\n\t\t\t\treturn\tapi.filter(files, function (file){ return !filter || filter.test(file.name); });\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get total file size\n\t\t\t * @param\t{Array}\tfiles\n\t\t\t * @return\t{Number}\n\t\t\t */\n\t\t\tgetTotalSize: function (files){\n\t\t\t\tvar size = 0, i = files && files.length;\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tsize += files[i].size;\n\t\t\t\t}\n\t\t\t\treturn\tsize;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Get image information\n\t\t\t *\n\t\t\t * @param\t{File}\t\tfile\n\t\t\t * @param\t{Function}\tfn\n\t\t\t */\n\t\t\tgetInfo: function (file, fn){\n\t\t\t\tvar info = {}, readers = _infoReader.concat();\n\n\t\t\t\tif( api.isFile(file) ){\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tvar reader = readers.shift();\n\t\t\t\t\t\tif( reader ){\n\t\t\t\t\t\t\tif( reader.test(file.type) ){\n\t\t\t\t\t\t\t\treader(file, function (err, res){\n\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t_extend(info, res);\n\t\t\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tfn(false, info);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfn('not_support_info', info);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Add information reader\n\t\t\t *\n\t\t\t * @param {RegExp} mime\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\taddInfoReader: function (mime, fn){\n\t\t\t\tfn.test = function (type){ return mime.test(type); };\n\t\t\t\t_infoReader.push(fn);\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter of array\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tinput\n\t\t\t * @param\t{Function}\tfn\n\t\t\t * @return\t{Array}\n\t\t\t */\n\t\t\tfilter: function (input, fn){\n\t\t\t\tvar result = [], i = 0, n = input.length, val;\n\n\t\t\t\tfor( ; i < n; i++ ){\n\t\t\t\t\tif( i in input ){\n\t\t\t\t\t\tval = input[i];\n\t\t\t\t\t\tif( fn.call(val, val, i, input) ){\n\t\t\t\t\t\t\tresult.push(val);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn\tresult;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Filter files\n\t\t\t *\n\t\t\t * @param\t{Array}\t\tfiles\n\t\t\t * @param\t{Function}\teachFn\n\t\t\t * @param\t{Function}\tresultFn\n\t\t\t */\n\t\t\tfilterFiles: function (files, eachFn, resultFn){\n\t\t\t\tif( files.length ){\n\t\t\t\t\t// HTML5 or Flash\n\t\t\t\t\tvar queue = files.concat(), file, result = [], deleted = [];\n\n\t\t\t\t\t(function _next(){\n\t\t\t\t\t\tif( queue.length ){\n\t\t\t\t\t\t\tfile = queue.shift();\n\t\t\t\t\t\t\tapi.getInfo(file, function (err, info){\n\t\t\t\t\t\t\t\t(eachFn(file, err ? false : info) ? result : deleted).push(file);\n\t\t\t\t\t\t\t\t_next();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresultFn(result, deleted);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tresultFn([], files);\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\tupload: function (options){\n\t\t\t\toptions = _extend({\n\t\t\t\t\t  jsonp: 'callback'\n\t\t\t\t\t, prepare: api.F\n\t\t\t\t\t, beforeupload: api.F\n\t\t\t\t\t, upload: api.F\n\t\t\t\t\t, fileupload: api.F\n\t\t\t\t\t, fileprogress: api.F\n\t\t\t\t\t, filecomplete: api.F\n\t\t\t\t\t, progress: api.F\n\t\t\t\t\t, complete: api.F\n\t\t\t\t\t, pause: api.F\n\t\t\t\t\t, imageOriginal: true\n\t\t\t\t\t, chunkSize: api.chunkSize\n\t\t\t\t\t, chunkUploadRetry: api.chunkUploadRetry\n\t\t\t\t\t, uploadRetry: api.uploadRetry\n\t\t\t\t}, options);\n\n\n\t\t\t\tif( options.imageAutoOrientation && !options.imageTransform ){\n\t\t\t\t\toptions.imageTransform = { rotate: 'auto' };\n\t\t\t\t}\n\n\n\t\t\t\tvar\n\t\t\t\t\t  proxyXHR = new api.XHR(options)\n\t\t\t\t\t, dataArray = this._getFilesDataArray(options.files)\n\t\t\t\t\t, _this = this\n\t\t\t\t\t, _total = 0\n\t\t\t\t\t, _loaded = 0\n\t\t\t\t\t, _nextFile\n\t\t\t\t\t, _complete = false\n\t\t\t\t;\n\n\n\t\t\t\t// calc total size\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\t_total += data.size;\n\t\t\t\t});\n\n\t\t\t\t// Array of files\n\t\t\t\tproxyXHR.files = [];\n\t\t\t\t_each(dataArray, function (data){\n\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t});\n\n\t\t\t\t// Set upload status props\n\t\t\t\tproxyXHR.total\t= _total;\n\t\t\t\tproxyXHR.loaded\t= 0;\n\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t// emit \"beforeupload\"  event\n\t\t\t\toptions.beforeupload(proxyXHR, options);\n\n\t\t\t\t// Upload by file\n\t\t\t\t_nextFile = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  data = dataArray.shift()\n\t\t\t\t\t\t, _file = data && data.file\n\t\t\t\t\t\t, _fileLoaded = false\n\t\t\t\t\t\t, _fileOptions = _simpleClone(options)\n\t\t\t\t\t;\n\n\t\t\t\t\tproxyXHR.filesLeft = dataArray.length;\n\n\t\t\t\t\tif( _file && _file.name === api.expando ){\n\t\t\t\t\t\t_file = null;\n\t\t\t\t\t\tapi.log('[warn] FileAPI.upload() — called without files');\n\t\t\t\t\t}\n\n\t\t\t\t\tif( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){\n\t\t\t\t\t\t// Mark active job\n\t\t\t\t\t\t_complete = false;\n\n\t\t\t\t\t\t// Set current upload file\n\t\t\t\t\t\tproxyXHR.currentFile = _file;\n\n\t\t\t\t\t\t// Prepare file options\n\t\t\t\t\t\tif (_file && options.prepare(_file, _fileOptions) === false) {\n\t\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_fileOptions.file = _file;\n\n\t\t\t\t\t\t_this._getFormData(_fileOptions, data, function (form){\n\t\t\t\t\t\t\tif( !_loaded ){\n\t\t\t\t\t\t\t\t// emit \"upload\" event\n\t\t\t\t\t\t\t\toptions.upload(proxyXHR, options);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar xhr = new api.XHR(_extend({}, _fileOptions, {\n\n\t\t\t\t\t\t\t\tupload: _file ? function (){\n\t\t\t\t\t\t\t\t\t// emit \"fileupload\" event\n\t\t\t\t\t\t\t\t\toptions.fileupload(_file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tprogress: _file ? function (evt){\n\t\t\t\t\t\t\t\t\tif( !_fileLoaded ){\n\t\t\t\t\t\t\t\t\t\t// For ignore the double calls.\n\t\t\t\t\t\t\t\t\t\t_fileLoaded = (evt.loaded === evt.total);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"fileprogress\" event\n\t\t\t\t\t\t\t\t\t\toptions.fileprogress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  data.total = evt.total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: data.loaded = evt.loaded\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\n\t\t\t\t\t\t\t\t\t\t// emit \"progress\" event\n\t\t\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t\t\t  type:   'progress'\n\t\t\t\t\t\t\t\t\t\t\t, total:  _total\n\t\t\t\t\t\t\t\t\t\t\t, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0\n\t\t\t\t\t\t\t\t\t\t}, _file, xhr, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} : noop,\n\n\t\t\t\t\t\t\t\tcomplete: function (err){\n\t\t\t\t\t\t\t\t\t_each(_xhrPropsExport, function (name){\n\t\t\t\t\t\t\t\t\t\tproxyXHR[name] = xhr[name];\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tif( _file ){\n\t\t\t\t\t\t\t\t\t\tdata.total = (data.total || data.size);\n\t\t\t\t\t\t\t\t\t\tdata.loaded\t= data.total;\n\n\t\t\t\t\t\t\t\t\t\tif( !err ) {\n\t\t\t\t\t\t\t\t\t\t\t// emulate 100% \"progress\"\n\t\t\t\t\t\t\t\t\t\t\tthis.progress(data);\n\n\t\t\t\t\t\t\t\t\t\t\t// fixed throttle event\n\t\t\t\t\t\t\t\t\t\t\t_fileLoaded = true;\n\n\t\t\t\t\t\t\t\t\t\t\t// bytes loaded\n\t\t\t\t\t\t\t\t\t\t\t_loaded += data.size; // data.size != data.total, it's desirable fix this\n\t\t\t\t\t\t\t\t\t\t\tproxyXHR.loaded = _loaded;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// emit \"filecomplete\" event\n\t\t\t\t\t\t\t\t\t\toptions.filecomplete(err, xhr, _file, _fileOptions);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// upload next file\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {_nextFile.call(_this);}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})); // xhr\n\n\n\t\t\t\t\t\t\t// ...\n\t\t\t\t\t\t\tproxyXHR.abort = function (current){\n\t\t\t\t\t\t\t\tif (!current) { dataArray.length = 0; }\n\t\t\t\t\t\t\t\tthis.current = current;\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Start upload\n\t\t\t\t\t\t\txhr.send(form);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tvar successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;\n\t\t\t\t\t\toptions.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);\n\t\t\t\t\t\t// Mark done state\n\t\t\t\t\t\t_complete = true;\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Next tick\n\t\t\t\tsetTimeout(_nextFile, 0);\n\n\n\t\t\t\t// Append more files to the existing request\n\t\t\t\t// first - add them to the queue head/tail\n\t\t\t\tproxyXHR.append = function (files, first) {\n\t\t\t\t\tfiles = api._getFilesDataArray([].concat(files));\n\n\t\t\t\t\t_each(files, function (data) {\n\t\t\t\t\t\t_total += data.size;\n\t\t\t\t\t\tproxyXHR.files.push(data.file);\n\t\t\t\t\t\tif (first) {\n\t\t\t\t\t\t\tdataArray.unshift(data);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdataArray.push(data);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tproxyXHR.statusText = \"\";\n\n\t\t\t\t\tif( _complete ){\n\t\t\t\t\t\t_nextFile.call(_this);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\n\t\t\t\t// Removes file from queue by file reference and returns it\n\t\t\t\tproxyXHR.remove = function (file) {\n\t\t\t\t    var i = dataArray.length, _file;\n\t\t\t\t    while( i-- ){\n\t\t\t\t\t\tif( dataArray[i].file == file ){\n\t\t\t\t\t\t\t_file = dataArray.splice(i, 1);\n\t\t\t\t\t\t\t_total -= _file.size;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn\t_file;\n\t\t\t\t};\n\n\t\t\t\treturn proxyXHR;\n\t\t\t},\n\n\n\t\t\t_getFilesDataArray: function (data){\n\t\t\t\tvar files = [], oFiles = {};\n\n\t\t\t\tif( isInputFile(data) ){\n\t\t\t\t\tvar tmp = api.getFiles(data);\n\t\t\t\t\toFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];\n\t\t\t\t}\n\t\t\t\telse if( _isArray(data) && isInputFile(data[0]) ){\n\t\t\t\t\t_each(data, function (input){\n\t\t\t\t\t\toFiles[input.name || 'file'] = api.getFiles(input);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\toFiles = data;\n\t\t\t\t}\n\n\t\t\t\t_each(oFiles, function add(file, name){\n\t\t\t\t\tif( _isArray(file) ){\n\t\t\t\t\t\t_each(file, function (file){\n\t\t\t\t\t\t\tadd(file, name);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( file && (file.name || file.image) ){\n\t\t\t\t\t\tfiles.push({\n\t\t\t\t\t\t\t  name: name\n\t\t\t\t\t\t\t, file: file\n\t\t\t\t\t\t\t, size: file.size\n\t\t\t\t\t\t\t, total: file.size\n\t\t\t\t\t\t\t, loaded: 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif( !files.length ){\n\t\t\t\t\t// Create fake `file` object\n\t\t\t\t\tfiles.push({ file: { name: api.expando } });\n\t\t\t\t}\n\n\t\t\t\treturn\tfiles;\n\t\t\t},\n\n\n\t\t\t_getFormData: function (options, data, fn){\n\t\t\t\tvar\n\t\t\t\t\t  file = data.file\n\t\t\t\t\t, name = data.name\n\t\t\t\t\t, filename = file.name\n\t\t\t\t\t, filetype = file.type\n\t\t\t\t\t, trans = api.support.transform && options.imageTransform\n\t\t\t\t\t, Form = new api.Form\n\t\t\t\t\t, queue = api.queue(function (){ fn(Form); })\n\t\t\t\t\t, isOrignTrans = trans && _isOriginTransform(trans)\n\t\t\t\t\t, postNameConcat = api.postNameConcat\n\t\t\t\t;\n\n\t\t\t\t// Append data\n\t\t\t\t_each(options.data, function add(val, name){\n\t\t\t\t\tif( typeof val == 'object' ){\n\t\t\t\t\t\t_each(val, function (v, i){\n\t\t\t\t\t\t\tadd(v, postNameConcat(name, i));\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tForm.append(name, val);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t(function _addFile(file/**Object*/){\n\t\t\t\t\tif( file.image ){ // This is a FileAPI.Image\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tfile.toData(function (err, image){\n\t\t\t\t\t\t\t// @todo: error\n\t\t\t\t\t\t\tfilename = filename || (new Date).getTime()+'.png';\n\n\t\t\t\t\t\t\t_addFile(image);\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){\n\t\t\t\t\t\tqueue.inc();\n\n\t\t\t\t\t\tif( isOrignTrans ){\n\t\t\t\t\t\t\t// Convert to array for transform function\n\t\t\t\t\t\t\ttrans = [trans];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tapi.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){\n\t\t\t\t\t\t\tif( isOrignTrans && !err ){\n\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t// Canvas.toBlob or Flash not supported, use multipart\n\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tForm.append(name, images[0], filename,  trans[0].type || filetype);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar addOrigin = 0;\n\n\t\t\t\t\t\t\t\tif( !err ){\n\t\t\t\t\t\t\t\t\t_each(images, function (image, idx){\n\t\t\t\t\t\t\t\t\t\tif( !dataURLtoBlob && !api.flashEngine ){\n\t\t\t\t\t\t\t\t\t\t\tForm.multipart = true;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif( !trans[idx].postName ){\n\t\t\t\t\t\t\t\t\t\t\taddOrigin = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tForm.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif( err || options.imageOriginal ){\n\t\t\t\t\t\t\t\t\tForm.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse if( filename !== api.expando ){\n\t\t\t\t\t\tForm.append(name, file, filename);\n\t\t\t\t\t}\n\t\t\t\t})(file);\n\n\t\t\t\tqueue.check();\n\t\t\t},\n\n\n\t\t\treset: function (inp, notRemove){\n\t\t\t\tvar parent, clone;\n\n\t\t\t\tif( jQuery ){\n\t\t\t\t\tclone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tjQuery(inp).remove();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tparent  = inp.parentNode;\n\t\t\t\t\tclone   = parent.insertBefore(inp.cloneNode(true), inp);\n\t\t\t\t\tclone.value = '';\n\n\t\t\t\t\tif( !notRemove ){\n\t\t\t\t\t\tparent.removeChild(inp);\n\t\t\t\t\t}\n\n\t\t\t\t\t_each(_elEvents[api.uid(inp)], function (fns, type){\n\t\t\t\t\t\t_each(fns, function (fn){\n\t\t\t\t\t\t\t_off(inp, type, fn);\n\t\t\t\t\t\t\t_on(clone, type, fn);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn  clone;\n\t\t\t},\n\n\n\t\t\t/**\n\t\t\t * Load remote file\n\t\t\t *\n\t\t\t * @param   {String}    url\n\t\t\t * @param   {Function}  fn\n\t\t\t * @return  {XMLHttpRequest}\n\t\t\t */\n\t\t\tload: function (url, fn){\n\t\t\t\tvar xhr = api.getXHR();\n\t\t\t\tif( xhr ){\n\t\t\t\t\txhr.open('GET', url, true);\n\n\t\t\t\t\tif( xhr.overrideMimeType ){\n\t\t\t\t        xhr.overrideMimeType('text/plain; charset=x-user-defined');\n\t\t\t\t\t}\n\n\t\t\t\t\t_on(xhr, 'progress', function (/**Event*/evt){\n\t\t\t\t\t\t/** @namespace evt.lengthComputable */\n\t\t\t\t\t\tif( evt.lengthComputable ){\n\t\t\t\t\t\t\tfn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\txhr.onreadystatechange = function(){\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\t\t\t\t\t\t\tif( xhr.status == 200 ){\n\t\t\t\t\t\t\t\turl = url.split('/');\n\t\t\t\t\t\t\t\t/** @namespace xhr.responseBody */\n\t\t\t\t\t\t\t\tvar file = {\n\t\t\t\t\t\t\t\t      name: url[url.length-1]\n\t\t\t\t\t\t\t\t\t, size: xhr.getResponseHeader('Content-Length')\n\t\t\t\t\t\t\t\t\t, type: xhr.getResponseHeader('Content-Type')\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfile.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);\n\t\t\t\t\t\t\t\tfn({ type: 'load', result: file }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tfn({ type: 'error' }, xhr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t    }\n\t\t\t\t\t};\n\t\t\t\t    xhr.send(null);\n\t\t\t\t} else {\n\t\t\t\t\tfn({ type: 'error' });\n\t\t\t\t}\n\n\t\t\t\treturn  xhr;\n\t\t\t},\n\n\t\t\tencode64: function (str){\n\t\t\t\tvar b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;\n\n\t\t\t\tif( typeof str !== 'string' ){\n\t\t\t\t\tstr\t= String(str);\n\t\t\t\t}\n\n\t\t\t\twhile( i < str.length ){\n\t\t\t\t\t//all three \"& 0xff\" added below are there to fix a known bug\n\t\t\t\t\t//with bytes returned by xhr.responseText\n\t\t\t\t\tvar\n\t\t\t\t\t\t  byte1 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte2 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, byte3 = str.charCodeAt(i++) & 0xff\n\t\t\t\t\t\t, enc1 = byte1 >> 2\n\t\t\t\t\t\t, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)\n\t\t\t\t\t\t, enc3, enc4\n\t\t\t\t\t;\n\n\t\t\t\t\tif( isNaN(byte2) ){\n\t\t\t\t\t\tenc3 = enc4 = 64;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tenc3 = ((byte2 & 15) << 2) | (byte3 >> 6);\n\t\t\t\t\t\tenc4 = isNaN(byte3) ? 64 : byte3 & 63;\n\t\t\t\t\t}\n\n\t\t\t\t\toutStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);\n\t\t\t\t}\n\n\t\t\t\treturn  outStr;\n\t\t\t}\n\n\t\t} // api\n\t;\n\n\n\tfunction _emit(target, fn, name, res, ext){\n\t\tvar evt = {\n\t\t\t  type:\t\tname.type || name\n\t\t\t, target:\ttarget\n\t\t\t, result:\tres\n\t\t};\n\t\t_extend(evt, ext);\n\t\tfn(evt);\n\t}\n\n\n\tfunction _hasSupportReadAs(as){\n\t\treturn\tFileReader && !!FileReader.prototype['readAs'+as];\n\t}\n\n\n\tfunction _readAs(file, fn, as, encoding){\n\t\tif( api.isBlob(file) && _hasSupportReadAs(as) ){\n\t\t\tvar Reader = new FileReader;\n\n\t\t\t// Add event listener\n\t\t\t_on(Reader, _readerEvents, function _fn(evt){\n\t\t\t\tvar type = evt.type;\n\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });\n\t\t\t\t}\n\t\t\t\telse if( type == 'loadend' ){\n\t\t\t\t\t_off(Reader, _readerEvents, _fn);\n\t\t\t\t\tReader = null;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_emit(file, fn, evt, evt.target.result);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\ttry {\n\t\t\t\t// ReadAs ...\n\t\t\t\tif( encoding ){\n\t\t\t\t\tReader['readAs'+as](file, encoding);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tReader['readAs'+as](file);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (err){\n\t\t\t\t_emit(file, fn, 'error', undef, { error: err.toString() });\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });\n\t\t}\n\t}\n\n\n\tfunction _isRegularFile(file, callback){\n\t\t// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects\n\t\tif( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){\n\t\t\tif( FileReader ){\n\t\t\t\ttry {\n\t\t\t\t\tvar Reader = new FileReader();\n\n\t\t\t\t\t_one(Reader, _readerEvents, function (evt){\n\t\t\t\t\t\tvar isFile = evt.type != 'error';\n\t\t\t\t\t\tcallback(isFile);\n\t\t\t\t\t\tif( isFile ){\n\t\t\t\t\t\t\tReader.abort();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tReader.readAsDataURL(file);\n\t\t\t\t} catch( err ){\n\t\t\t\t\tcallback(false);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcallback(null);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcallback(true);\n\t\t}\n\t}\n\n\n\tfunction _getAsEntry(item){\n\t\tvar entry;\n\t\tif( item.getAsEntry ){ entry = item.getAsEntry(); }\n\t\telse if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }\n\t\treturn\tentry;\n\t}\n\n\n\tfunction _readEntryAsFiles(entry, callback){\n\t\tif( !entry ){\n\t\t\t// error\n\t\t\tcallback('invalid entry');\n\t\t}\n\t\telse if( entry.isFile ){\n\t\t\t// Read as file\n\t\t\tentry.file(function(file){\n\t\t\t\t// success\n\t\t\t\tfile.fullPath = entry.fullPath;\n\t\t\t\tcallback(false, [file]);\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('FileError.code: '+err.code);\n\t\t\t});\n\t\t}\n\t\telse if( entry.isDirectory ){\n\t\t\tvar reader = entry.createReader(), result = [];\n\n\t\t\treader.readEntries(function(entries){\n\t\t\t\t// success\n\t\t\t\tapi.afor(entries, function (next, entry){\n\t\t\t\t\t_readEntryAsFiles(entry, function (err, files){\n\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\tapi.log(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tresult = result.concat(files);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( next ){\n\t\t\t\t\t\t\tnext();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tcallback(false, result);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}, function (err){\n\t\t\t\t// error\n\t\t\t\tcallback('directory_reader: ' + err);\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\t_readEntryAsFiles(_getAsEntry(entry), callback);\n\t\t}\n\t}\n\n\n\tfunction _simpleClone(obj){\n\t\tvar copy = {};\n\t\t_each(obj, function (val, key){\n\t\t\tif( val && (typeof val === 'object') && (val.nodeType === void 0) ){\n\t\t\t\tval = _extend({}, val);\n\t\t\t}\n\t\t\tcopy[key] = val;\n\t\t});\n\t\treturn\tcopy;\n\t}\n\n\n\tfunction isInputFile(el){\n\t\treturn\t_rinput.test(el && el.tagName);\n\t}\n\n\n\tfunction _getDataTransfer(evt){\n\t\treturn\t(evt.originalEvent || evt || '').dataTransfer || {};\n\t}\n\n\n\tfunction _isOriginTransform(trans){\n\t\tvar key;\n\t\tfor( key in trans ){\n\t\t\tif( trans.hasOwnProperty(key) ){\n\t\t\t\tif( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){\n\t\t\t\t\treturn\ttrue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn\tfalse;\n\t}\n\n\n\t// Add default image info reader\n\tapi.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){\n\t\tif( !file.__dimensions ){\n\t\t\tvar defer = file.__dimensions = api.defer();\n\n\t\t\tapi.readAsImage(file, function (evt){\n\t\t\t\tvar img = evt.target;\n\t\t\t\tdefer.resolve(evt.type == 'load' ? false : 'error', {\n\t\t\t\t\t  width:  img.width\n\t\t\t\t\t, height: img.height\n\t\t\t\t});\n                img.src = api.EMPTY_PNG;\n\t\t\t\timg = null;\n\t\t\t});\n\t\t}\n\n\t\tfile.__dimensions.then(callback);\n\t});\n\n\n\t/**\n\t * Drag'n'Drop special event\n\t *\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd = function (el, onHover, onDrop){\n\t\tvar _id, _type;\n\n\t\tif( !onDrop ){\n\t\t\tonDrop = onHover;\n\t\t\tonHover = api.F;\n\t\t}\n\n\t\tif( FileReader ){\n\t\t\t// Hover\n\t\t\t_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){\n\t\t\t\tvar\n\t\t\t\t\t  types = _getDataTransfer(evt).types\n\t\t\t\t\t, i = types && types.length\n\t\t\t\t\t, debounceTrigger = false\n\t\t\t\t;\n\n\t\t\t\twhile( i-- ){\n\t\t\t\t\tif( ~types[i].indexOf('File') ){\n\t\t\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t\t\tif( _type !== evt.type ){\n\t\t\t\t\t\t\t_type = evt.type; // Store current type of event\n\n\t\t\t\t\t\t\tif( _type != 'dragleave' ){\n\t\t\t\t\t\t\t\tonHover.call(evt[currentTarget], true, evt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdebounceTrigger = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak; // exit from \"while\"\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( debounceTrigger ){\n\t\t\t\t\tclearTimeout(_id);\n\t\t\t\t\t_id = setTimeout(function (){\n\t\t\t\t\t\tonHover.call(evt[currentTarget], _type != 'dragleave', evt);\n\t\t\t\t\t}, 50);\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\t// Drop\n\t\t\t_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){\n\t\t\t\tevt[preventDefault]();\n\n\t\t\t\t_type = 0;\n\t\t\t\tonHover.call(evt[currentTarget], false, evt);\n\n\t\t\t\tapi.getDropFiles(evt, function (files){\n\t\t\t\t\tonDrop.call(evt[currentTarget], files, evt);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\tapi.log(\"Drag'n'Drop -- not supported\");\n\t\t}\n\t};\n\n\n\t/**\n\t * Remove drag'n'drop\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Function}\t\tonHover\n\t * @param\t{Function}\t\tonDrop\n\t */\n\tapi.event.dnd.off = function (el, onHover, onDrop){\n\t\t_off(el, 'dragenter dragleave dragover', onHover.ff);\n\t\t_off(el, 'drop', onDrop.ff);\n\t};\n\n\n\t// Support jQuery\n\tif( jQuery && !jQuery.fn.dnd ){\n\t\tjQuery.fn.dnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\n\t\tjQuery.fn.offdnd = function (onHover, onDrop){\n\t\t\treturn this.each(function (){\n\t\t\t\tapi.event.dnd.off(this, onHover, onDrop);\n\t\t\t});\n\t\t};\n\t}\n\n\t// @export\n\twindow.FileAPI  = _extend(api, window.FileAPI);\n\n\n\t// Debug info\n\tapi.log('FileAPI: ' + api.version);\n\tapi.log('protocol: ' + window.location.protocol);\n\tapi.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);\n\n\n\t// @detect 'x-ua-compatible'\n\t_each(document.getElementsByTagName('meta'), function (meta){\n\t\tif( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){\n\t\t\tapi.log('meta.http-equiv: ' + meta.getAttribute('content'));\n\t\t}\n\t});\n\n\n\t// configuration\n\ttry {\n\t\tapi._supportConsoleLog = !!console.log;\n\t\tapi._supportConsoleLogApply = !!console.log.apply;\n\t} catch (err) {}\n\n\tif( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }\n\tif( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }\n\tif( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }\n})(window, void 0);\n\n/*global window, FileAPI, document */\n\n(function (api, document, undef) {\n\t'use strict';\n\n\tvar\n\t\tmin = Math.min,\n\t\tround = Math.round,\n\t\tgetCanvas = function () { return document.createElement('canvas'); },\n\t\tsupport = false,\n\t\texifOrientation = {\n\t\t\t  8:\t270\n\t\t\t, 3:\t180\n\t\t\t, 6:\t90\n\t\t\t, 7:\t270\n\t\t\t, 4:\t180\n\t\t\t, 5:\t90\n\t\t}\n\t;\n\n\ttry {\n\t\tsupport = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;\n\t}\n\tcatch (e){}\n\n\n\tfunction Image(file){\n\t\tif( file instanceof Image ){\n\t\t\tvar img = new Image(file.file);\n\t\t\tapi.extend(img.matrix, file.matrix);\n\t\t\treturn\timg;\n\t\t}\n\t\telse if( !(this instanceof Image) ){\n\t\t\treturn\tnew Image(file);\n\t\t}\n\n\t\tthis.file   = file;\n\t\tthis.size   = file.size || 100;\n\n\t\tthis.matrix\t= {\n\t\t\tsx: 0,\n\t\t\tsy: 0,\n\t\t\tsw: 0,\n\t\t\tsh: 0,\n\t\t\tdx: 0,\n\t\t\tdy: 0,\n\t\t\tdw: 0,\n\t\t\tdh: 0,\n\t\t\tresize: 0, // min, max OR preview\n\t\t\tdeg: 0,\n\t\t\tquality: 1, // jpeg quality\n\t\t\tfilter: 0\n\t\t};\n\t}\n\n\n\tImage.prototype = {\n\t\timage: true,\n\t\tconstructor: Image,\n\n\t\tset: function (attrs){\n\t\t\tapi.extend(this.matrix, attrs);\n\t\t\treturn\tthis;\n\t\t},\n\n\t\tcrop: function (x, y, w, h){\n\t\t\tif( w === undef ){\n\t\t\t\tw\t= x;\n\t\t\t\th\t= y;\n\t\t\t\tx = y = 0;\n\t\t\t}\n\t\t\treturn\tthis.set({ sx: x, sy: y, sw: w, sh: h || w });\n\t\t},\n\n\t\tresize: function (w, h, strategy){\n\t\t\tif( /min|max/.test(h) ){\n\t\t\t\tstrategy = h;\n\t\t\t\th = w;\n\t\t\t}\n\n\t\t\treturn\tthis.set({ dw: w, dh: h || w, resize: strategy });\n\t\t},\n\n\t\tpreview: function (w, h){\n\t\t\treturn\tthis.resize(w, h || w, 'preview');\n\t\t},\n\n\t\trotate: function (deg){\n\t\t\treturn\tthis.set({ deg: deg });\n\t\t},\n\n\t\tfilter: function (filter){\n\t\t\treturn\tthis.set({ filter: filter });\n\t\t},\n\n\t\toverlay: function (images){\n\t\t\treturn\tthis.set({ overlay: images });\n\t\t},\n\n\t\tclone: function (){\n\t\t\treturn\tnew Image(this);\n\t\t},\n\n\t\t_load: function (image, fn){\n\t\t\tvar self = this;\n\n\t\t\tif( /img|video/i.test(image.nodeName) ){\n\t\t\t\tfn.call(self, null, image);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.readAsImage(image, function (evt){\n\t\t\t\t\tfn.call(self, evt.type != 'load', evt.result);\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\t_apply: function (image, fn){\n\t\t\tvar\n\t\t\t\t  canvas = getCanvas()\n\t\t\t\t, m = this.getMatrix(image)\n\t\t\t\t, ctx = canvas.getContext('2d')\n\t\t\t\t, width = image.videoWidth || image.width\n\t\t\t\t, height = image.videoHeight || image.height\n\t\t\t\t, deg = m.deg\n\t\t\t\t, dw = m.dw\n\t\t\t\t, dh = m.dh\n\t\t\t\t, w = width\n\t\t\t\t, h = height\n\t\t\t\t, filter = m.filter\n\t\t\t\t, copy // canvas copy\n\t\t\t\t, buffer = image\n\t\t\t\t, overlay = m.overlay\n\t\t\t\t, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })\n\t\t\t\t, renderImageToCanvas = api.renderImageToCanvas\n\t\t\t;\n\n\t\t\t// Normalize angle\n\t\t\tdeg = deg - Math.floor(deg/360)*360;\n\n\t\t\t// For `renderImageToCanvas`\n\t\t\timage._type = this.file.type;\n\n\t\t\twhile(m.multipass && min(w/dw, h/dh) > 2 ){\n\t\t\t\tw = (w/2 + 0.5)|0;\n\t\t\t\th = (h/2 + 0.5)|0;\n\n\t\t\t\tcopy = getCanvas();\n\t\t\t\tcopy.width  = w;\n\t\t\t\tcopy.height = h;\n\n\t\t\t\tif( buffer !== image ){\n\t\t\t\t\trenderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbuffer = copy;\n\t\t\t\t\trenderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);\n\t\t\t\t\tm.sx = m.sy = m.sw = m.sh = 0;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tcanvas.width  = (deg % 180) ? dh : dw;\n\t\t\tcanvas.height = (deg % 180) ? dw : dh;\n\n\t\t\tcanvas.type = m.type;\n\t\t\tcanvas.quality = m.quality;\n\n\t\t\tctx.rotate(deg * Math.PI / 180);\n\t\t\trenderImageToCanvas(ctx.canvas, buffer\n\t\t\t\t, m.sx, m.sy\n\t\t\t\t, m.sw || buffer.width\n\t\t\t\t, m.sh || buffer.height\n\t\t\t\t, (deg == 180 || deg == 270 ? -dw : 0)\n\t\t\t\t, (deg == 90 || deg == 180 ? -dh : 0)\n\t\t\t\t, dw, dh\n\t\t\t);\n\t\t\tdw = canvas.width;\n\t\t\tdh = canvas.height;\n\n\t\t\t// Apply overlay\n\t\t\toverlay && api.each([].concat(overlay), function (over){\n\t\t\t\tqueue.inc();\n\t\t\t\t// preload\n\t\t\t\tvar img = new window.Image, fn = function (){\n\t\t\t\t\tvar\n\t\t\t\t\t\t  x = over.x|0\n\t\t\t\t\t\t, y = over.y|0\n\t\t\t\t\t\t, w = over.w || img.width\n\t\t\t\t\t\t, h = over.h || img.height\n\t\t\t\t\t\t, rel = over.rel\n\t\t\t\t\t;\n\n\t\t\t\t\t// center  |  right  |  left\n\t\t\t\t\tx = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);\n\n\t\t\t\t\t// center  |  bottom  |  top\n\t\t\t\t\ty = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);\n\n\t\t\t\t\tapi.event.off(img, 'error load abort', fn);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tctx.globalAlpha = over.opacity || 1;\n\t\t\t\t\t\tctx.drawImage(img, x, y, w, h);\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er){}\n\n\t\t\t\t\tqueue.next();\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(img, 'error load abort', fn);\n\t\t\t\timg.src = over.src;\n\n\t\t\t\tif( img.complete ){\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif( filter ){\n\t\t\t\tqueue.inc();\n\t\t\t\tImage.applyFilter(canvas, filter, queue.next);\n\t\t\t}\n\n\t\t\tqueue.check();\n\t\t},\n\n\t\tgetMatrix: function (image){\n\t\t\tvar\n\t\t\t\t  m  = api.extend({}, this.matrix)\n\t\t\t\t, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width\n\t\t\t\t, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height\n\t\t\t\t, dw = m.dw = m.dw || sw\n\t\t\t\t, dh = m.dh = m.dh || sh\n\t\t\t\t, sf = sw/sh, df = dw/dh\n\t\t\t\t, strategy = m.resize\n\t\t\t;\n\n\t\t\tif( strategy == 'preview' ){\n\t\t\t\tif( dw != sw || dh != sh ){\n\t\t\t\t\t// Make preview\n\t\t\t\t\tvar w, h;\n\n\t\t\t\t\tif( df >= sf ){\n\t\t\t\t\t\tw\t= sw;\n\t\t\t\t\t\th\t= w / df;\n\t\t\t\t\t} else {\n\t\t\t\t\t\th\t= sh;\n\t\t\t\t\t\tw\t= h * df;\n\t\t\t\t\t}\n\n\t\t\t\t\tif( w != sw || h != sh ){\n\t\t\t\t\t\tm.sx\t= ~~((sw - w)/2);\n\t\t\t\t\t\tm.sy\t= ~~((sh - h)/2);\n\t\t\t\t\t\tsw\t\t= w;\n\t\t\t\t\t\tsh\t\t= h;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if( strategy ){\n\t\t\t\tif( !(sw > dw || sh > dh) ){\n\t\t\t\t\tdw = sw;\n\t\t\t\t\tdh = sh;\n\t\t\t\t}\n\t\t\t\telse if( strategy == 'min' ){\n\t\t\t\t\tdw = round(sf < df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf < df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdw = round(sf >= df ? min(sw, dw) : dh*sf);\n\t\t\t\t\tdh = round(sf >= df ? dw/sf : min(sh, dh));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm.sw = sw;\n\t\t\tm.sh = sh;\n\t\t\tm.dw = dw;\n\t\t\tm.dh = dh;\n\t\t\tm.multipass = api.multiPassResize;\n\t\t\treturn\tm;\n\t\t},\n\n\t\t_trans: function (fn){\n\t\t\tthis._load(this.file, function (err, image){\n\t\t\t\tif( err ){\n\t\t\t\t\tfn(err);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis._apply(image, fn);\n\t\t\t\t\t} catch (err){\n\t\t\t\t\t\tapi.log('[err] FileAPI.Image.fn._apply:', err);\n\t\t\t\t\t\tfn(err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\tget: function (fn){\n\t\t\tif( api.support.transform ){\n\t\t\t\tvar _this = this, matrix = _this.matrix;\n\n\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\tapi.getInfo(_this.file, function (err, info){\n\t\t\t\t\t\t// rotate by exif orientation\n\t\t\t\t\t\tmatrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t_this._trans(fn);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_this._trans(fn);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn('not_support_transform');\n\t\t\t}\n\n\t\t\treturn this;\n\t\t},\n\n\n\t\ttoData: function (fn){\n\t\t\treturn this.get(fn);\n\t\t}\n\n\t};\n\n\n\tImage.exifOrientation = exifOrientation;\n\n\n\tImage.transform = function (file, transform, autoOrientation, fn){\n\t\tfunction _transform(err, img){\n\t\t\t// img -- info object\n\t\t\tvar\n\t\t\t\t  images = {}\n\t\t\t\t, queue = api.queue(function (err){\n\t\t\t\t\tfn(err, images);\n\t\t\t\t})\n\t\t\t;\n\n\t\t\tif( !err ){\n\t\t\t\tapi.each(transform, function (params, name){\n\t\t\t\t\tif( !queue.isFail() ){\n\t\t\t\t\t\tvar ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';\n\n\t\t\t\t\t\tif( isFn ){\n\t\t\t\t\t\t\tparams(img, ImgTrans);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( params.width ){\n\t\t\t\t\t\t\tImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tif( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){\n\t\t\t\t\t\t\t\tImgTrans.resize(params.maxWidth, params.maxHeight, 'max');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.crop ){\n\t\t\t\t\t\t\tvar crop = params.crop;\n\t\t\t\t\t\t\tImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( params.rotate === undef && autoOrientation ){\n\t\t\t\t\t\t\tparams.rotate = 'auto';\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });\n\n\t\t\t\t\t\tif( !isFn ){\n\t\t\t\t\t\t\tImgTrans.set({\n\t\t\t\t\t\t\t\t  deg: params.rotate\n\t\t\t\t\t\t\t\t, overlay: params.overlay\n\t\t\t\t\t\t\t\t, filter: params.filter\n\t\t\t\t\t\t\t\t, quality: params.quality || 1\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\tImgTrans.toData(function (err, image){\n\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\tqueue.fail();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\timages[name] = image;\n\t\t\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tqueue.fail();\n\t\t\t}\n\t\t}\n\n\n\t\t// @todo: Оло-ло, нужно рефакторить это место\n\t\tif( file.width ){\n\t\t\t_transform(false, file);\n\t\t} else {\n\t\t\tapi.getInfo(file, _transform);\n\t\t}\n\t};\n\n\n\t// @const\n\tapi.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){\n\t\tapi.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){\n\t\t\tImage[x+'_'+y] = i*3 + j;\n\t\t\tImage[y+'_'+x] = i*3 + j;\n\t\t});\n\t});\n\n\n\t/**\n\t * Trabsform element to canvas\n\t *\n\t * @param    {Image|HTMLVideoElement}   el\n\t * @returns  {Canvas}\n\t */\n\tImage.toCanvas = function(el){\n\t\tvar canvas\t\t= document.createElement('canvas');\n\t\tcanvas.width\t= el.videoWidth || el.width;\n\t\tcanvas.height\t= el.videoHeight || el.height;\n\t\tcanvas.getContext('2d').drawImage(el, 0, 0);\n\t\treturn\tcanvas;\n\t};\n\n\n\t/**\n\t * Create image from DataURL\n\t * @param  {String}  dataURL\n\t * @param  {Object}  size\n\t * @param  {Function}  callback\n\t */\n\tImage.fromDataURL = function (dataURL, size, callback){\n\t\tvar img = api.newImage(dataURL);\n\t\tapi.extend(img, size);\n\t\tcallback(img);\n\t};\n\n\n\t/**\n\t * Apply filter (caman.js)\n\t *\n\t * @param  {Canvas|Image}   canvas\n\t * @param  {String|Function}  filter\n\t * @param  {Function}  doneFn\n\t */\n\tImage.applyFilter = function (canvas, filter, doneFn){\n\t\tif( typeof filter == 'function' ){\n\t\t\tfilter(canvas, doneFn);\n\t\t}\n\t\telse if( window.Caman ){\n\t\t\t// http://camanjs.com/guides/\n\t\t\twindow.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){\n\t\t\t\tif( typeof filter == 'string' ){\n\t\t\t\t\tthis[filter]();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tapi.each(filter, function (val, method){\n\t\t\t\t\t\tthis[method](val);\n\t\t\t\t\t}, this);\n\t\t\t\t}\n\t\t\t\tthis.render(doneFn);\n\t\t\t});\n\t\t}\n\t};\n\n\n\t/**\n\t * For load-image-ios.js\n\t */\n\tapi.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){\n\t\ttry {\n\t\t\treturn canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);\n\t\t} catch (ex) {\n\t\t\tapi.log('renderImageToCanvas failed');\n\t\t\tthrow ex;\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.support.canvas = api.support.transform = support;\n\tapi.Image = Image;\n})(FileAPI, document);\n\n/*\n * JavaScript Load Image iOS scaling fixes 1.0.3\n * https://github.com/blueimp/JavaScript-Load-Image\n *\n * Copyright 2013, Sebastian Tschan\n * https://blueimp.net\n *\n * iOS image scaling fixes based on\n * https://github.com/stomita/ios-imagefile-megapixel\n *\n * Licensed under the MIT license:\n * http://www.opensource.org/licenses/MIT\n */\n\n/*jslint nomen: true, bitwise: true */\n/*global FileAPI, window, document */\n\n(function (factory) {\n\t'use strict';\n\tfactory(FileAPI);\n}(function (loadImage) {\n    'use strict';\n\n    // Only apply fixes on the iOS platform:\n    if (!window.navigator || !window.navigator.platform ||\n             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {\n        return;\n    }\n\n    var originalRenderMethod = loadImage.renderImageToCanvas;\n\n    // Detects subsampling in JPEG images:\n    loadImage.detectSubsampling = function (img) {\n        var canvas,\n            context;\n        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images\n            canvas = document.createElement('canvas');\n            canvas.width = canvas.height = 1;\n            context = canvas.getContext('2d');\n            context.drawImage(img, -img.width + 1, 0);\n            // subsampled image becomes half smaller in rendering size.\n            // check alpha channel value to confirm image is covering edge pixel or not.\n            // if alpha value is 0 image is not covering, hence subsampled.\n            return context.getImageData(0, 0, 1, 1).data[3] === 0;\n        }\n        return false;\n    };\n\n    // Detects vertical squash in JPEG images:\n    loadImage.detectVerticalSquash = function (img, subsampled) {\n        var naturalHeight = img.naturalHeight || img.height,\n            canvas = document.createElement('canvas'),\n            context = canvas.getContext('2d'),\n            data,\n            sy,\n            ey,\n            py,\n            alpha;\n        if (subsampled) {\n            naturalHeight /= 2;\n        }\n        canvas.width = 1;\n        canvas.height = naturalHeight;\n        context.drawImage(img, 0, 0);\n        data = context.getImageData(0, 0, 1, naturalHeight).data;\n        // search image edge pixel position in case it is squashed vertically:\n        sy = 0;\n        ey = naturalHeight;\n        py = naturalHeight;\n        while (py > sy) {\n            alpha = data[(py - 1) * 4 + 3];\n            if (alpha === 0) {\n                ey = py;\n            } else {\n                sy = py;\n            }\n            py = (ey + sy) >> 1;\n        }\n        return (py / naturalHeight) || 1;\n    };\n\n    // Renders image to canvas while working around iOS image scaling bugs:\n    // https://github.com/blueimp/JavaScript-Load-Image/issues/13\n    loadImage.renderImageToCanvas = function (\n        canvas,\n        img,\n        sourceX,\n        sourceY,\n        sourceWidth,\n        sourceHeight,\n        destX,\n        destY,\n        destWidth,\n        destHeight\n    ) {\n        if (img._type === 'image/jpeg') {\n            var context = canvas.getContext('2d'),\n                tmpCanvas = document.createElement('canvas'),\n                tileSize = 1024,\n                tmpContext = tmpCanvas.getContext('2d'),\n                subsampled,\n                vertSquashRatio,\n                tileX,\n                tileY;\n            tmpCanvas.width = tileSize;\n            tmpCanvas.height = tileSize;\n            context.save();\n            subsampled = loadImage.detectSubsampling(img);\n            if (subsampled) {\n                sourceX /= 2;\n                sourceY /= 2;\n                sourceWidth /= 2;\n                sourceHeight /= 2;\n            }\n            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);\n            if (subsampled || vertSquashRatio !== 1) {\n                sourceY *= vertSquashRatio;\n                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);\n                destHeight = Math.ceil(\n                    tileSize * destHeight / sourceHeight / vertSquashRatio\n                );\n                destY = 0;\n                tileY = 0;\n                while (tileY < sourceHeight) {\n                    destX = 0;\n                    tileX = 0;\n                    while (tileX < sourceWidth) {\n                        tmpContext.clearRect(0, 0, tileSize, tileSize);\n                        tmpContext.drawImage(\n                            img,\n                            sourceX,\n                            sourceY,\n                            sourceWidth,\n                            sourceHeight,\n                            -tileX,\n                            -tileY,\n                            sourceWidth,\n                            sourceHeight\n                        );\n                        context.drawImage(\n                            tmpCanvas,\n                            0,\n                            0,\n                            tileSize,\n                            tileSize,\n                            destX,\n                            destY,\n                            destWidth,\n                            destHeight\n                        );\n                        tileX += tileSize;\n                        destX += destWidth;\n                    }\n                    tileY += tileSize;\n                    destY += destHeight;\n                }\n                context.restore();\n                return canvas;\n            }\n        }\n        return originalRenderMethod(\n            canvas,\n            img,\n            sourceX,\n            sourceY,\n            sourceWidth,\n            sourceHeight,\n            destX,\n            destY,\n            destWidth,\n            destHeight\n        );\n    };\n\n}));\n\n/*global window, FileAPI */\n\n(function (api, window){\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, FormData = window.FormData\n\t\t, Form = function (){ this.items = []; }\n\t\t, encodeURIComponent = window.encodeURIComponent\n\t;\n\n\n\tForm.prototype = {\n\n\t\tappend: function (name, blob, file, type){\n\t\t\tthis.items.push({\n\t\t\t\t  name: name\n\t\t\t\t, blob: blob && blob.blob || (blob == void 0 ? '' : blob)\n\t\t\t\t, file: blob && (file || blob.name)\n\t\t\t\t, type:\tblob && (type || blob.type)\n\t\t\t});\n\t\t},\n\n\t\teach: function (fn){\n\t\t\tvar i = 0, n = this.items.length;\n\t\t\tfor( ; i < n; i++ ){\n\t\t\t\tfn.call(this, this.items[i]);\n\t\t\t}\n\t\t},\n\n\t\ttoData: function (fn, options){\n\t\t    // allow chunked transfer if we have only one file to send\n\t\t    // flag is used below and in XHR._send\n\t\t    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;\n\n\t\t\tif( !api.support.html5 ){\n\t\t\t\tapi.log('FileAPI.Form.toHtmlData');\n\t\t\t\tthis.toHtmlData(fn);\n\t\t\t}\n\t\t\telse if( !api.formData || this.multipart || !FormData ){\n\t\t\t\tapi.log('FileAPI.Form.toMultipartData');\n\t\t\t\tthis.toMultipartData(fn);\n\t\t\t}\n\t\t\telse if( options._chunked ){\n\t\t\t\tapi.log('FileAPI.Form.toPlainData');\n\t\t\t\tthis.toPlainData(fn);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tapi.log('FileAPI.Form.toFormData');\n\t\t\t\tthis.toFormData(fn);\n\t\t\t}\n\t\t},\n\n\t\t_to: function (data, complete, next, arg){\n\t\t\tvar queue = api.queue(function (){\n\t\t\t\tcomplete(data);\n\t\t\t});\n\n\t\t\tthis.each(function (file){\n\t\t\t\tnext(file, data, queue, arg);\n\t\t\t});\n\n\t\t\tqueue.check();\n\t\t},\n\n\n\t\ttoHtmlData: function (fn){\n\t\t\tthis._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){\n\t\t\t\tvar blob = file.blob, hidden;\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tapi.reset(blob, true);\n\t\t\t\t\t// set new name\n\t\t\t\t\tblob.name = file.name;\n\t\t\t\t\tblob.disabled = false;\n\t\t\t\t\tdata.appendChild(blob);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thidden = document.createElement('input');\n\t\t\t\t\thidden.name  = file.name;\n\t\t\t\t\thidden.type  = 'hidden';\n\t\t\t\t\thidden.value = blob;\n\t\t\t\t\tdata.appendChild(hidden);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\ttoPlainData: function (fn){\n\t\t\tthis._to({}, fn, function (file, data, queue){\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.type = file.file;\n\t\t\t\t}\n\n\t\t\t\tif( file.blob.toBlob ){\n\t\t\t\t    // canvas\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.name = file.name;\n\t\t\t\t\t\tdata.file = blob;\n\t\t\t\t\t\tdata.size = blob.length;\n\t\t\t\t\t\tdata.type = file.type;\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t    // file\n\t\t\t\t\tdata.name = file.blob.name;\n\t\t\t\t\tdata.file = file.blob;\n\t\t\t\t\tdata.size = file.blob.size;\n\t\t\t\t\tdata.type = file.type;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t    // additional data\n\t\t\t\t    if( !data.params ){\n\t\t\t\t        data.params = [];\n\t\t\t\t    }\n\t\t\t\t    data.params.push(encodeURIComponent(file.name) +\"=\"+ encodeURIComponent(file.blob));\n\t\t\t\t}\n\n\t\t\t\tdata.start = -1;\n\t\t\t\tdata.end = data.file && data.file.FileAPIReadPosition || -1;\n\t\t\t\tdata.retry = 0;\n\t\t\t});\n\t\t},\n\n\t\ttoFormData: function (fn){\n\t\t\tthis._to(new FormData, fn, function (file, data, queue){\n\t\t\t\tif( file.blob && file.blob.toBlob ){\n\t\t\t\t\tqueue.inc();\n\t\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\t\tdata.append(file.name, blob, file.file);\n\t\t\t\t\t\tqueue.next();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if( file.file ){\n\t\t\t\t\tdata.append(file.name, file.blob, file.file);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdata.append(file.name, file.blob);\n\t\t\t\t}\n\n\t\t\t\tif( file.file ){\n\t\t\t\t\tdata.append('_'+file.name, file.file);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\n\t\ttoMultipartData: function (fn){\n\t\t\tthis._to([], fn, function (file, data, queue, boundary){\n\t\t\t\tqueue.inc();\n\t\t\t\t_convertFile(file, function (file, blob){\n\t\t\t\t\tdata.push(\n\t\t\t\t\t\t  '--_' + boundary + ('\\r\\nContent-Disposition: form-data; name=\"'+ file.name +'\"'+ (file.file ? '; filename=\"'+ encodeURIComponent(file.file) +'\"' : '')\n\t\t\t\t\t\t+ (file.file ? '\\r\\nContent-Type: '+ (file.type || 'application/octet-stream') : '')\n\t\t\t\t\t\t+ '\\r\\n'\n\t\t\t\t\t\t+ '\\r\\n'+ (file.file ? blob : encodeURIComponent(blob))\n\t\t\t\t\t\t+ '\\r\\n')\n\t\t\t\t\t);\n\t\t\t\t\tqueue.next();\n\t\t\t\t}, true);\n\t\t\t}, api.expando);\n\t\t}\n\t};\n\n\n\tfunction _convertFile(file, fn, useBinaryString){\n\t\tvar blob = file.blob, filename = file.file;\n\n\t\tif( filename ){\n\t\t\tif( !blob.toDataURL ){\n\t\t\t\t// The Blob is not an image.\n\t\t\t\tapi.readAsBinaryString(blob, function (evt){\n\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\tfn(file, evt.result);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar\n\t\t\t\t  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }\n\t\t\t\t, type = mime[file.type] ? file.type : 'image/png'\n\t\t\t\t, ext  = mime[type] || '.png'\n\t\t\t\t, quality = blob.quality || 1\n\t\t\t;\n\n\t\t\tif( !filename.match(new RegExp(ext+'$', 'i')) ){\n\t\t\t\t// Does not change the current extension, but add a new one.\n\t\t\t\tfilename += ext.replace('?', '');\n\t\t\t}\n\n\t\t\tfile.file = filename;\n\t\t\tfile.type = type;\n\n\t\t\tif( !useBinaryString && blob.toBlob ){\n\t\t\t\tblob.toBlob(function (blob){\n\t\t\t\t\tfn(file, blob);\n\t\t\t\t}, type, quality);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfn(file, api.toBinaryString(blob.toDataURL(type, quality)));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfn(file, blob);\n\t\t}\n\t}\n\n\n\t// @export\n\tapi.Form = Form;\n})(FileAPI, window);\n\n/*global window, FileAPI, Uint8Array */\n\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\t  noop = function (){}\n\t\t, document = window.document\n\n\t\t, XHR = function (options){\n\t\t\tthis.uid = api.uid();\n\t\t\tthis.xhr = {\n\t\t\t\t  abort: noop\n\t\t\t\t, getResponseHeader: noop\n\t\t\t\t, getAllResponseHeaders: noop\n\t\t\t};\n\t\t\tthis.options = options;\n\t\t},\n\n\t\t_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }\n\t;\n\n\n\tXHR.prototype = {\n\t\tstatus: 0,\n\t\tstatusText: '',\n\t\tconstructor: XHR,\n\n\t\tgetResponseHeader: function (name){\n\t\t\treturn this.xhr.getResponseHeader(name);\n\t\t},\n\n\t\tgetAllResponseHeaders: function (){\n\t\t\treturn this.xhr.getAllResponseHeaders() || {};\n\t\t},\n\n\t\tend: function (status, statusText){\n\t\t\tvar _this = this, options = _this.options;\n\n\t\t\t_this.end\t\t=\n\t\t\t_this.abort\t\t= noop;\n\t\t\t_this.status\t= status;\n\n\t\t\tif( statusText ){\n\t\t\t\t_this.statusText = statusText;\n\t\t\t}\n\n\t\t\tapi.log('xhr.end:', status, statusText);\n\t\t\toptions.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);\n\n\t\t\tif( _this.xhr && _this.xhr.node ){\n\t\t\t\tsetTimeout(function (){\n\t\t\t\t\tvar node = _this.xhr.node;\n\t\t\t\t\ttry { node.parentNode.removeChild(node); } catch (e){}\n\t\t\t\t\ttry { delete window[_this.uid]; } catch (e){}\n\t\t\t\t\twindow[_this.uid] = _this.xhr.node = null;\n\t\t\t\t}, 9);\n\t\t\t}\n\t\t},\n\n\t\tabort: function (){\n\t\t\tthis.end(0, 'abort');\n\n\t\t\tif( this.xhr ){\n\t\t\t\tthis.xhr.aborted = true;\n\t\t\t\tthis.xhr.abort();\n\t\t\t}\n\t\t},\n\n\t\tsend: function (FormData){\n\t\t\tvar _this = this, options = this.options;\n\n\t\t\tFormData.toData(function (data){\n\t\t\t\t// Start uploading\n\t\t\t\toptions.upload(options, _this);\n\t\t\t\t_this._send.call(_this, options, data);\n\t\t\t}, options);\n\t\t},\n\n\t\t_send: function (options, data){\n\t\t\tvar _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + \"Load\", url = options.url;\n\n\t\t\tapi.log('XHR._send:', data);\n\n\t\t\tif( !options.cache ){\n\t\t\t\t// No cache\n\t\t\t\turl += (~url.indexOf('?') ? '&' : '?') + api.uid();\n\t\t\t}\n\n\t\t\tif( data.nodeName ){\n\t\t\t\tvar jsonp = options.jsonp;\n\n\t\t\t\t// prepare callback in GET\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)/i, '$1='+uid);\n\n\t\t\t\t// legacy\n\t\t\t\toptions.upload(options, _this);\n\n\t\t\t\tvar\n\t\t\t\t\tonPostMessage = function (evt){\n\t\t\t\t\t\tif( ~url.indexOf(evt.origin) ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar result = api.parseJSON(evt.data);\n\t\t\t\t\t\t\t\tif( result.id == uid ){\n\t\t\t\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\tcomplete(0, err.message);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// jsonp-callack\n\t\t\t\t\tcomplete = window[uid] = function (status, statusText, response){\n\t\t\t\t\t\t_this.readyState\t= 4;\n\t\t\t\t\t\t_this.responseText\t= response;\n\t\t\t\t\t\t_this.end(status, statusText);\n\n\t\t\t\t\t\tapi.event.off(window, 'message', onPostMessage);\n\t\t\t\t\t\twindow[uid] = xhr = transport = window[onloadFuncName] = null;\n\t\t\t\t\t}\n\t\t\t\t;\n\n\t\t\t\t_this.xhr.abort = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif( transport.stop ){ transport.stop(); }\n\t\t\t\t\t\telse if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }\n\t\t\t\t\t\telse { transport.contentWindow.document.execCommand('Stop'); }\n\t\t\t\t\t}\n\t\t\t\t\tcatch (er) {}\n\t\t\t\t\tcomplete(0, \"abort\");\n\t\t\t\t};\n\n\t\t\t\tapi.event.on(window, 'message', onPostMessage);\n\n\t\t\t\twindow[onloadFuncName] = function (){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  win = transport.contentWindow\n\t\t\t\t\t\t\t, doc = win.document\n\t\t\t\t\t\t\t, result = win.result || api.parseJSON(doc.body.innerHTML)\n\t\t\t\t\t\t;\n\t\t\t\t\t\tcomplete(result.status, result.statusText, result.response);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('[transport.onload]', e);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\txhr = document.createElement('div');\n\t\t\t\txhr.innerHTML = '<form target=\"'+ uid +'\" action=\"'+ url +'\" method=\"POST\" enctype=\"multipart/form-data\" style=\"position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;\">'\n\t\t\t\t\t\t\t+ '<iframe name=\"'+ uid +'\" src=\"javascript:false;\" onload=\"' + onloadFuncName + '()\"></iframe>'\n\t\t\t\t\t\t\t+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value=\"'+ uid +'\" name=\"'+jsonp+'\" type=\"hidden\"/>' : '')\n\t\t\t\t\t\t\t+ '</form>'\n\t\t\t\t;\n\n\t\t\t\t// get form-data & transport\n\t\t\t\tvar\n\t\t\t\t\t  form = xhr.getElementsByTagName('form')[0]\n\t\t\t\t\t, transport = xhr.getElementsByTagName('iframe')[0]\n\t\t\t\t;\n\n\t\t\t\tform.appendChild(data);\n\n\t\t\t\tapi.log(form.parentNode.innerHTML);\n\n\t\t\t\t// append to DOM\n\t\t\t\tdocument.body.appendChild(xhr);\n\n\t\t\t\t// keep a reference to node-transport\n\t\t\t\t_this.xhr.node = xhr;\n\n\t\t\t\t// send\n\t\t\t\t_this.readyState = 2; // loaded\n\t\t\t\tform.submit();\n\t\t\t\tform = null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Clean url\n\t\t\t\turl = url.replace(/([a-z]+)=(\\?)&?/i, '');\n\n\t\t\t\t// html5\n\t\t\t\tif (this.xhr && this.xhr.aborted) {\n\t\t\t\t\tapi.log(\"Error: already aborted\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\txhr = _this.xhr = api.getXHR();\n\n\t\t\t\tif (data.params) {\n\t\t\t\t\turl += (url.indexOf('?') < 0 ? \"?\" : \"&\") + data.params.join(\"&\");\n\t\t\t\t}\n\n\t\t\t\txhr.open('POST', url, true);\n\n\t\t\t\tif( api.withCredentials ){\n\t\t\t\t\txhr.withCredentials = \"true\";\n\t\t\t\t}\n\n\t\t\t\tif( !options.headers || !options.headers['X-Requested-With'] ){\n\t\t\t\t\txhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\t\t\t}\n\n\t\t\t\tapi.each(options.headers, function (val, key){\n\t\t\t\t\txhr.setRequestHeader(key, val);\n\t\t\t\t});\n\n\n\t\t\t\tif ( options._chunked ) {\n\t\t\t\t\t// chunked upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\tif (!data.retry) {\n\t\t\t\t\t\t\t\t// show progress only for correct chunk uploads\n\t\t\t\t\t\t\t\toptions.progress({\n\t\t\t\t\t\t\t\t\t  type:\t\t\tevt.type\n\t\t\t\t\t\t\t\t\t, total:\t\tdata.size\n\t\t\t\t\t\t\t\t\t, loaded:\t\tdata.start + evt.loaded\n\t\t\t\t\t\t\t\t\t, totalSize:\tdata.size\n\t\t\t\t\t\t\t\t}, _this, options);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\tvar lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);\n\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}catch(_){}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status - 201 > 0) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\t// some kind of error\n\t\t\t\t\t\t\t\t// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action\n\t\t\t\t\t\t\t\t// up - server error\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {\n\t\t\t\t\t\t\t\t\t// let's try again the same chunk\n\t\t\t\t\t\t\t\t\t// only applicable for recoverable error codes 500 && 416\n\t\t\t\t\t\t\t\t\tvar delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(data.file, options);\n\n\t\t\t\t\t\t\t\t\t// smart restart if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdata.end = data.start - 1;\n\t\t\t\t\t\t\t\t\t\tif (416 == xhr.status) {\n\t\t\t\t\t\t\t\t\t\t\tdata.end = data.end - options.chunkSize;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// no mo retries\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// success\n\t\t\t\t\t\t\t\tdata.retry = 0;\n\n\t\t\t\t\t\t\t\tif (data.end == data.size - 1) {\n\t\t\t\t\t\t\t\t\t// finished\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// next chunk\n\n\t\t\t\t\t\t\t\t\t// shift position if server reports about the last known byte\n\t\t\t\t\t\t\t\t\tapi.log(\"X-Last-Known-Byte: \" + lkb);\n\t\t\t\t\t\t\t\t\tif (lkb) {\n\t\t\t\t\t\t\t\t\t\tdata.end = lkb;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdata.file.FileAPIReadPosition = data.end;\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tdata.start = data.end + 1;\n\t\t\t\t\tdata.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);\n\n\t\t\t\t\t// Retrieve a slice of file\n\t\t\t\t\tvar\n\t\t\t\t\t\t  file = data.file\n\t\t\t\t\t\t, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)\n\t\t\t\t\t;\n\n\t\t\t\t\tif( data.size && !slice.size ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\t_this.end(-1);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Range\", \"bytes \" + data.start + \"-\" + data.end + \"/\" + data.size);\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Disposition\", 'attachment; filename=' + encodeURIComponent(data.name));\n\t\t\t\t\t\txhr.setRequestHeader(\"Content-Type\", data.type || \"application/octet-stream\");\n\n\t\t\t\t\t\txhr.send(slice);\n\t\t\t\t\t}\n\n\t\t\t\t\tfile = slice = null;\n\t\t\t\t} else {\n\t\t\t\t\t// single piece upload\n\t\t\t\t\tif( xhr.upload ){\n\t\t\t\t\t\t// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29\n\t\t\t\t\t\txhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){\n\t\t\t\t\t\t\toptions.progress(evt, _this, options);\n\t\t\t\t\t\t}, 100), false);\n\t\t\t\t\t}\n\n\t\t\t\t\txhr.onreadystatechange = function (){\n\t\t\t\t\t\t_this.status     = xhr.status;\n\t\t\t\t\t\t_this.statusText = xhr.statusText;\n\t\t\t\t\t\t_this.readyState = xhr.readyState;\n\n\t\t\t\t\t\tif( xhr.readyState == 4 ){\n\t\t\t\t\t\t\tfor( var k in _xhrResponsePostfix ){\n\t\t\t\t\t\t\t\t_this['response'+k]  = xhr['response'+k];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\txhr.onreadystatechange = null;\n\n\t\t\t\t\t\t\tif (!xhr.status || xhr.status > 201) {\n\t\t\t\t\t\t\t\tapi.log(\"Error: \" + xhr.status);\n\t\t\t\t\t\t\t\tif (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {\n\t\t\t\t\t\t\t\t\toptions.retry = (options.retry || 0) + 1;\n\t\t\t\t\t\t\t\t\tvar delay = api.networkDownRetryTimeout;\n\n\t\t\t\t\t\t\t\t\t// inform about recoverable problems\n\t\t\t\t\t\t\t\t\toptions.pause(options.file, options);\n\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\t_this._send(options, data);\n\t\t\t\t\t\t\t\t\t}, delay);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t//success\n\t\t\t\t\t\t\t\t_this.end(xhr.status);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\txhr = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif( api.isArray(data) ){\n\t\t\t\t\t\t// multipart\n\t\t\t\t\t\txhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);\n\t\t\t\t\t\tvar rawData = data.join('') +'--_'+ api.expando +'--';\n\n\t\t\t\t\t\t/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */\n\t\t\t\t\t\tif( xhr.sendAsBinary ){\n\t\t\t\t\t\t\txhr.sendAsBinary(rawData);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tvar bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });\n\t\t\t\t\t\t\txhr.send(new Uint8Array(bytes).buffer);\n\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// FormData\n\t\t\t\t\t\txhr.send(data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\n\t// @export\n\tapi.XHR = XHR;\n})(window, FileAPI);\n\n/**\n * @class\tFileAPI.Camera\n * @author\tRubaXa\t<trash@rubaxa.org>\n * @support\tChrome 21+, FF 18+, Opera 12+\n */\n\n/*global window, FileAPI, jQuery */\n/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */\n(function (window, api){\n\t\"use strict\";\n\n\tvar\n\t\tURL = window.URL || window.webkitURL,\n\n\t\tdocument = window.document,\n\t\tnavigator = window.navigator,\n\n\t\tgetMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,\n\n\t\thtml5 = !!getMedia\n\t;\n\n\n\t// Support \"media\"\n\tapi.support.media = html5;\n\n\n\tvar Camera = function (video){\n\t\tthis.video = video;\n\t};\n\n\n\tCamera.prototype = {\n\t\tisActive: function (){\n\t\t\treturn\t!!this._active;\n\t\t},\n\n\n\t\t/**\n\t\t * Start camera streaming\n\t\t * @param\t{Function}\tcallback\n\t\t */\n\t\tstart: function (callback){\n\t\t\tvar\n\t\t\t\t  _this = this\n\t\t\t\t, video = _this.video\n\t\t\t\t, _successId\n\t\t\t\t, _failId\n\t\t\t\t, _complete = function (err){\n\t\t\t\t\t_this._active = !err;\n\t\t\t\t\tclearTimeout(_failId);\n\t\t\t\t\tclearTimeout(_successId);\n//\t\t\t\t\tapi.event.off(video, 'loadedmetadata', _complete);\n\t\t\t\t\tcallback && callback(err, _this);\n\t\t\t\t}\n\t\t\t;\n\n\t\t\tgetMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){\n\t\t\t\t// Success\n\t\t\t\t_this.stream = stream;\n\n//\t\t\t\tapi.event.on(video, 'loadedmetadata', function (){\n//\t\t\t\t\t_complete(null);\n//\t\t\t\t});\n\n\t\t\t\t// Set camera stream\n\t\t\t\tvideo.src = URL.createObjectURL(stream);\n\n\t\t\t\t// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.\n\t\t\t\t// See crbug.com/110938.\n\t\t\t\t_successId = setInterval(function (){\n\t\t\t\t\tif( _detectVideoSignal(video) ){\n\t\t\t\t\t\t_complete(null);\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\n\t\t\t\t_failId = setTimeout(function (){\n\t\t\t\t\t_complete('timeout');\n\t\t\t\t}, 5000);\n\n\t\t\t\t// Go-go-go!\n\t\t\t\tvideo.play();\n\t\t\t}, _complete/*error*/);\n\t\t},\n\n\n\t\t/**\n\t\t * Stop camera streaming\n\t\t */\n\t\tstop: function (){\n\t\t\ttry {\n\t\t\t\tthis._active = false;\n\t\t\t\tthis.video.pause();\n\t\t\t\tthis.stream.stop();\n\t\t\t} catch( err ){ }\n\t\t},\n\n\n\t\t/**\n\t\t * Create screenshot\n\t\t * @return {FileAPI.Camera.Shot}\n\t\t */\n\t\tshot: function (){\n\t\t\treturn\tnew Shot(this.video);\n\t\t}\n\t};\n\n\n\t/**\n\t * Get camera element from container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @return\t{Camera}\n\t */\n\tCamera.get = function (el){\n\t\treturn\tnew Camera(el.firstChild);\n\t};\n\n\n\t/**\n\t * Publish camera element into container\n\t *\n\t * @static\n\t * @param\t{HTMLElement}\tel\n\t * @param\t{Object}\t\toptions\n\t * @param\t{Function}\t\t[callback]\n\t */\n\tCamera.publish = function (el, options, callback){\n\t\tif( typeof options == 'function' ){\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\n\t\t// Dimensions of \"camera\"\n\t\toptions = api.extend({}, {\n\t\t\t  width:\t'100%'\n\t\t\t, height:\t'100%'\n\t\t\t, start:\ttrue\n\t\t}, options);\n\n\n\t\tif( el.jquery ){\n\t\t\t// Extract first element, from jQuery collection\n\t\t\tel = el[0];\n\t\t}\n\n\n\t\tvar doneFn = function (err){\n\t\t\tif( err ){\n\t\t\t\tcallback(err);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Get camera\n\t\t\t\tvar cam = Camera.get(el);\n\t\t\t\tif( options.start ){\n\t\t\t\t\tcam.start(callback);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallback(null, cam);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\n\t\tel.style.width\t= _px(options.width);\n\t\tel.style.height\t= _px(options.height);\n\n\n\t\tif( api.html5 && html5 ){\n\t\t\t// Create video element\n\t\t\tvar video = document.createElement('video');\n\n\t\t\t// Set dimensions\n\t\t\tvideo.style.width\t= _px(options.width);\n\t\t\tvideo.style.height\t= _px(options.height);\n\n\t\t\t// Clean container\n\t\t\tif( window.jQuery ){\n\t\t\t\tjQuery(el).empty();\n\t\t\t} else {\n\t\t\t\tel.innerHTML = '';\n\t\t\t}\n\n\t\t\t// Add \"camera\" to container\n\t\t\tel.appendChild(video);\n\n\t\t\t// end\n\t\t\tdoneFn();\n\t\t}\n\t\telse {\n\t\t\tCamera.fallback(el, options, doneFn);\n\t\t}\n\t};\n\n\n\tCamera.fallback = function (el, options, callback){\n\t\tcallback('not_support_camera');\n\t};\n\n\n\t/**\n\t * @class\tFileAPI.Camera.Shot\n\t */\n\tvar Shot = function (video){\n\t\tvar canvas\t= video.nodeName ? api.Image.toCanvas(video) : video;\n\t\tvar shot\t= api.Image(canvas);\n\t\tshot.type\t= 'image/png';\n\t\tshot.width\t= canvas.width;\n\t\tshot.height\t= canvas.height;\n\t\tshot.size\t= canvas.width * canvas.height * 4;\n\t\treturn\tshot;\n\t};\n\n\n\t/**\n\t * Add \"px\" postfix, if value is a number\n\t *\n\t * @private\n\t * @param\t{*}  val\n\t * @return\t{String}\n\t */\n\tfunction _px(val){\n\t\treturn\tval >= 0 ? val + 'px' : val;\n\t}\n\n\n\t/**\n\t * @private\n\t * @param\t{HTMLVideoElement} video\n\t * @return\t{Boolean}\n\t */\n\tfunction _detectVideoSignal(video){\n\t\tvar canvas = document.createElement('canvas'), ctx, res = false;\n\t\ttry {\n\t\t\tctx = canvas.getContext('2d');\n\t\t\tctx.drawImage(video, 0, 0, 1, 1);\n\t\t\tres = ctx.getImageData(0, 0, 1, 1).data[4] != 255;\n\t\t}\n\t\tcatch( e ){}\n\t\treturn\tres;\n\t}\n\n\n\t// @export\n\tCamera.Shot\t= Shot;\n\tapi.Camera\t= Camera;\n})(window, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, ActiveXObject, FileAPI */\n(function (window, jQuery, api) {\n\t\"use strict\";\n\n\tvar\n\t\t  document = window.document\n\t\t, location = window.location\n\t\t, navigator = window.navigator\n\t\t, _each = api.each\n\t;\n\n\n\tapi.support.flash = (function (){\n\t\tvar mime = navigator.mimeTypes, has = false;\n\n\t\tif( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){\n\t\t\thas\t= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);\n\t\t}\n\t\telse {\n\t\t\ttry {\n\t\t\t\thas\t= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));\n\t\t\t}\n\t\t\tcatch(er){\n\t\t\t\tapi.log('Flash -- does not supported.');\n\t\t\t}\n\t\t}\n\n\t\tif( has && /^file:/i.test(location) ){\n\t\t\tapi.log('[warn] Flash does not work on `file:` protocol.');\n\t\t}\n\n\t\treturn\thas;\n\t})();\n\n\n\t   api.support.flash\n\t&& (0\n\t\t|| !api.html5 || !api.support.html5\n\t\t|| (api.cors && !api.support.cors)\n\t\t|| (api.media && !api.support.media)\n\t)\n\t&& (function (){\n\t\tvar\n\t\t\t  _attr  = api.uid()\n\t\t\t, _retry = 0\n\t\t\t, _files = {}\n\t\t\t, _rhttp = /^https?:/i\n\n\t\t\t, flash = {\n\t\t\t\t_fn: {},\n\n\n\t\t\t\t/**\n\t\t\t\t * Publish flash-object\n\t\t\t\t *\n\t\t\t\t * @param {HTMLElement} el\n\t\t\t\t * @param {String} id\n\t\t\t\t * @param {Object} [opts]\n\t\t\t\t */\n\t\t\t\tpublish: function (el, id, opts){\n\t\t\t\t\topts = opts || {};\n\t\t\t\t\tel.innerHTML = _makeFlashHTML({\n\t\t\t\t\t\tid: id\n\t\t\t\t\t\t, src: _getUrl(api.flashUrl, 'r=' + api.version)\n//\t\t\t\t\t\t, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')\n\t\t\t\t\t\t, wmode: opts.camera ? '' : 'transparent'\n\t\t\t\t\t\t, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')\n\t\t\t\t\t\t+ '&flashId='+ id\n\t\t\t\t\t\t+ '&storeKey='+ navigator.userAgent.match(/\\d/ig).join('') +'_'+ api.version\n\t\t\t\t\t\t+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))\n\t\t\t\t\t\t+ '&timeout='+api.flashAbortTimeout\n\t\t\t\t\t\t+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')\n\t\t\t\t\t\t+ '&debug='+(api.debug?\"1\":\"\")\n\t\t\t\t\t}, opts);\n\t\t\t\t},\n\n\n\t\t\t\t/**\n\t\t\t\t * Initialization & preload flash object\n\t\t\t\t */\n\t\t\t\tinit: function (){\n\t\t\t\t\tvar child = document.body && document.body.firstChild;\n\n\t\t\t\t\tif( child ){\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tif( child.nodeType == 1 ){\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.state: awaiting');\n\n\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\n\t\t\t\t\t\t\t\tdummy.id = '_' + _attr;\n\n\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t  top: 1\n\t\t\t\t\t\t\t\t\t, right: 1\n\t\t\t\t\t\t\t\t\t, width: 5\n\t\t\t\t\t\t\t\t\t, height: 5\n\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tchild.parentNode.insertBefore(dummy, child);\n\t\t\t\t\t\t\t\tflash.publish(dummy, _attr);\n\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile( child = child.nextSibling );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( _retry < 10 ){\n\t\t\t\t\t\tsetTimeout(flash.init, ++_retry*50);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tready: function (){\n\t\t\t\t\tapi.log('FlashAPI.state: ready');\n\n\t\t\t\t\tflash.ready = api.F;\n\t\t\t\t\tflash.isReady = true;\n\t\t\t\t\tflash.patch();\n\t\t\t\t\tflash.patchCamera && flash.patchCamera();\n\t\t\t\t\tapi.event.on(document, 'mouseover', flash.mouseover);\n\t\t\t\t\tapi.event.on(document, 'click', function (evt){\n\t\t\t\t\t\tif( flash.mouseover(evt) ){\n\t\t\t\t\t\t\tevt.preventDefault\n\t\t\t\t\t\t\t\t? evt.preventDefault()\n\t\t\t\t\t\t\t\t: (evt.returnValue = true)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\n\n\t\t\t\tgetEl: function (){\n\t\t\t\t\treturn\tdocument.getElementById('_'+_attr);\n\t\t\t\t},\n\n\n\t\t\t\tgetWrapper: function (node){\n\t\t\t\t\tdo {\n\t\t\t\t\t\tif( /js-fileapi-wrapper/.test(node.className) ){\n\t\t\t\t\t\t\treturn\tnode;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twhile( (node = node.parentNode) && (node !== document.body) );\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tdisableMouseover: false,\n\n\t\t\t\tmouseover: function (evt){\n\t\t\t\t\tif (!flash.disableMouseover) {\n\t\t\t\t\t\tvar target = api.event.fix(evt).target;\n\t\n\t\t\t\t\t\tif( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  state = target.getAttribute(_attr)\n\t\t\t\t\t\t\t\t, wrapper = flash.getWrapper(target)\n\t\t\t\t\t\t\t;\n\t\n\t\t\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\t\t\t// check state:\n\t\t\t\t\t\t\t\t//   i — published\n\t\t\t\t\t\t\t\t//   i — initialization\n\t\t\t\t\t\t\t\t//   r — ready\n\t\t\t\t\t\t\t\tif( state == 'i' || state == 'r' ){\n\t\t\t\t\t\t\t\t\t// publish fail\n\t\t\t\t\t\t\t\t\treturn\tfalse;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse if( state != 'p' ){\n\t\t\t\t\t\t\t\t\t// set \"init\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'i');\n\t\n\t\t\t\t\t\t\t\t\tvar dummy = document.createElement('div');\n\t\n\t\t\t\t\t\t\t\t\tif( !wrapper ){\n\t\t\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t_css(dummy, {\n\t\t\t\t\t\t\t\t\t\t  top:    0\n\t\t\t\t\t\t\t\t\t\t, left:   0\n\t\t\t\t\t\t\t\t\t\t, width:  target.offsetWidth\n\t\t\t\t\t\t\t\t\t\t, height: target.offsetHeight\n\t\t\t\t\t\t\t\t\t\t, zIndex: 1e6+'' // set max zIndex\n\t\t\t\t\t\t\t\t\t\t, position: 'absolute'\n\t\t\t\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t\t\t\twrapper.appendChild(dummy);\n\t\t\t\t\t\t\t\t\tflash.publish(dummy, api.uid());\n\t\n\t\t\t\t\t\t\t\t\t// set \"publish\" state\n\t\t\t\t\t\t\t\t\ttarget.setAttribute(_attr, 'p');\n\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( wrapper ){\n\t\t\t\t\t\t\t\t// Use one flash element\n\t\t\t\t\t\t\t\tvar box = _getDimensions(wrapper);\n\t\t\t\t\t\t\t\t_css(flash.getEl(), box);\n\t\n\t\t\t\t\t\t\t\t// Set current input\n\t\t\t\t\t\t\t\tflash.curInp = target;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( !/object|embed/i.test(target.nodeName) ){\n\t\t\t\t\t\t\t_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tonEvent: function (evt){\n\t\t\t\t\tvar type = evt.type;\n\t\t\t\t\t\n\t\t\t\t\tif( type == 'ready' ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// set \"ready\" state\n\t\t\t\t\t\t\tflash.getInput(evt.flashId).setAttribute(_attr, 'r');\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tflash.ready();\n\t\t\t\t\t\tsetTimeout(function (){ flash.mouseenter(evt); }, 50);\n\t\t\t\t\t\treturn\ttrue;\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'ping' ){\n\t\t\t\t\t\tapi.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type === 'log' ){\n\t\t\t\t\t\tapi.log('(flash -> js).log:', evt.target);\n\t\t\t\t\t}\n\t\t\t\t\telse if( type in flash ){\n\t\t\t\t\t\tsetTimeout(function (){\n\t\t\t\t\t\t\tapi.log('FlashAPI.event.'+evt.type+':', evt);\n\t\t\t\t\t\t\tflash[type](evt);\n\t\t\t\t\t\t}, 1);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tmouseDown: function(evt) {\n\t\t\t\t\tflash.disableMouseover = true;\n\t\t\t\t},\n\t\t\t\tcancel: function(evt) {\n\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t},\n\t\t\t\tmouseenter: function (evt){\n\t\t\t\t\tvar node = flash.getInput(evt.flashId);\n\n\t\t\t\t\tif( node ){\n\t\t\t\t\t\t// Set multiple mode\n\t\t\t\t\t\tflash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);\n\n\n\t\t\t\t\t\t// Set files filter\n\t\t\t\t\t\tvar accept = [], exts = {};\n\n\t\t\t\t\t\t_each((node.getAttribute('accept') || '').split(/,\\s*/), function (mime){\n\t\t\t\t\t\t\tapi.accept[mime] && _each(api.accept[mime].split(' '), function (ext){\n\t\t\t\t\t\t\t\texts[ext] = 1;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t_each(exts, function (i, ext){\n\t\t\t\t\t\t\taccept.push( ext );\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tflash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tget: function (id){\n\t\t\t\t\treturn\tdocument[id] || window[id] || document.embeds[id];\n\t\t\t\t},\n\n\n\t\t\t\tgetInput: function (id){\n\t\t\t\t\tif( api.multiFlash ){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar node = flash.getWrapper(flash.get(id));\n\t\t\t\t\t\t\tif( node ){\n\t\t\t\t\t\t\t\treturn node.getElementsByTagName('input')[0];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e){\n\t\t\t\t\t\t\tapi.log('[err] Can not find \"input\" by flashId:', id, e);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn\tflash.curInp;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\n\t\t\t\tselect: function (evt){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t  inp = flash.getInput(evt.flashId)\n\t\t\t\t\t\t\t, uid = api.uid(inp)\n\t\t\t\t\t\t\t, files = evt.target.files\n\t\t\t\t\t\t\t, event\n\t\t\t\t\t\t;\n\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\tapi.checkFileObj(file);\n\t\t\t\t\t\t});\n\t\n\t\t\t\t\t\t_files[uid] = files;\n\t\n\t\t\t\t\t\tif( document.createEvent ){\n\t\t\t\t\t\t\tevent = document.createEvent('Event');\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tevent.initEvent('change', true, true);\n\t\t\t\t\t\t\tinp.dispatchEvent(event);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if( jQuery ){\n\t\t\t\t\t\t\tjQuery(inp).trigger({ type: 'change', files: files });\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tevent = document.createEventObject();\n\t\t\t\t\t\t\tevent.files = files;\n\t\t\t\t\t\t\tinp.fireEvent('onchange', event);\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tflash.disableMouseover = false;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tinterval: null,\n\t\t\t\tcmd: function (id, name, data, last) {\n\t\t\t\t\tif (flash.uploadInProgress && flash.readInProgress) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tflash.cmd(id, name, data, last);\n\t\t\t\t\t\t}, 100);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.cmdFn(id, name, data, last);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tcmdFn: function(id, name, data, last) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tapi.log('(js -> flash).'+name+':', data);\n\t\t\t\t\t\treturn flash.get(id.flashId || id).cmd(name, data);\n\t\t\t\t\t} catch (e){\n\t\t\t\t\t\tapi.log('(js -> flash).onError:', e);\n\t\t\t\t\t\tif( !last ){\n\t\t\t\t\t\t\t// try again\n\t\t\t\t\t\t\tsetTimeout(function (){ flash.cmd(id, name, data, true); }, 50);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tpatch: function (){\n\t\t\t\t\tapi.flashEngine = true;\n\n\t\t\t\t\t// FileAPI\n\t\t\t\t\t_inherit(api, {\n\t\t\t\t\t\treadAsDataURL: function (file, callback){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64');\n\t\t\t\t\t\t\t\tflash.readInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(file, 'readAsBase64', {\n\t\t\t\t\t\t\t\t\tid: file.id,\n\t\t\t\t\t\t\t\t\tcallback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tflash.readInProgress = false;\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.readAsBase64:', err);\n\n\t\t\t\t\t\t\t\t\t\tcallback({\n\t\t\t\t\t\t\t\t\t\t\t  type: err ? 'error' : 'load'\n\t\t\t\t\t\t\t\t\t\t\t, error: err\n\t\t\t\t\t\t\t\t\t\t\t, result: 'data:'+ file.type +';base64,'+ base64\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\treadAsText: function (file, encoding, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.log('[warn] FlashAPI.readAsText not supported `encoding` param');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcallback = encoding;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.readAsDataURL(file, function (evt){\n\t\t\t\t\t\t\t\tif( evt.type == 'load' ){\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tevt.result = window.atob(evt.result.split(';base64,')[1]);\n\t\t\t\t\t\t\t\t\t} catch( err ){\n\t\t\t\t\t\t\t\t\t\tevt.type = 'error';\n\t\t\t\t\t\t\t\t\t\tevt.error = err.toString();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcallback(evt);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\n\t\t\t\t\t\tgetFiles: function (input, filter, callback){\n\t\t\t\t\t\t\tif( callback ){\n\t\t\t\t\t\t\t\tapi.filterFiles(api.getFiles(input), filter, callback);\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvar files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];\n\n\n\t\t\t\t\t\t\tif( !files ){\n\t\t\t\t\t\t\t\t// Файлов нету, вызываем родительский метод\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tif( filter ){\n\t\t\t\t\t\t\t\tfilter\t= api.getFilesFilter(filter);\n\t\t\t\t\t\t\t\tfiles\t= api.filter(files, function (file){ return filter.test(file.name); });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn\tfiles;\n\t\t\t\t\t\t},\n\n\n\t\t\t\t\t\tgetInfo: function (file, fn){\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if( file.isShot ){\n\t\t\t\t\t\t\t\tfn(null, file.info = {\n\t\t\t\t\t\t\t\t\twidth: file.width,\n\t\t\t\t\t\t\t\t\theight: file.height\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( !file.__info ){\n\t\t\t\t\t\t\t\t\tvar defer = file.__info = api.defer();\n\n//\t\t\t\t\t\t\t\t\tflash.cmd(file, 'getFileInfo', {\n//\t\t\t\t\t\t\t\t\t\t  id: file.id\n//\t\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, info){\n//\t\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n//\t\t\t\t\t\t\t\t\t\t\tdefer.resolve(err, file.info = info);\n//\t\t\t\t\t\t\t\t\t\t})\n//\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tdefer.resolve(null, file.info = null);\n\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfile.__info.then(fn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.Image\n\t\t\t\t\tapi.support.transform = true;\n\t\t\t\t\tapi.Image && _inherit(api.Image.prototype, {\n\t\t\t\t\t\tget: function (fn, scaleMode){\n\t\t\t\t\t\t\tthis.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit\n\t\t\t\t\t\t\treturn this.parent(fn);\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_load: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._load:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar _this = this;\n\t\t\t\t\t\t\t\tapi.getInfo(file, function (err){\n\t\t\t\t\t\t\t\t\tfn.call(_this, err, file);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t_apply: function (file, fn){\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply:', file);\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvar m = this.getMatrix(file.info), doneFn = fn;\n\n\t\t\t\t\t\t\t\tflash.cmd(file, 'imageTransform', {\n\t\t\t\t\t\t\t\t\t  id: file.id\n\t\t\t\t\t\t\t\t\t, matrix: m\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function _(err, base64){\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.Image._apply.callback:', err);\n\t\t\t\t\t\t\t\t\t\t_unwrap(_);\n\n\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\tdoneFn(err);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){\n\t\t\t\t\t\t\t\t\t\t\t_makeFlashImage({\n\t\t\t\t\t\t\t\t\t\t\t\t  width:\t(m.deg % 180) ? m.dh : m.dw\n\t\t\t\t\t\t\t\t\t\t\t\t, height:\t(m.deg % 180) ? m.dw : m.dh\n\t\t\t\t\t\t\t\t\t\t\t\t, scale:\tm.scaleMode\n\t\t\t\t\t\t\t\t\t\t\t}, base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\tif( m.filter ){\n\t\t\t\t\t\t\t\t\t\t\t\tdoneFn = function (err, img){\n\t\t\t\t\t\t\t\t\t\t\t\t\tif( err ){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tapi.Image.applyFilter(img, m.filter, function (){\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfn(err, this.canvas);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tapi.newImage('data:'+ file.type +';base64,'+ base64, doneFn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  file = this.file\n\t\t\t\t\t\t\t\t, info = file.info\n\t\t\t\t\t\t\t\t, matrix = this.getMatrix(info)\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t\tapi.log('FlashAPI.Image.toData');\n\n\t\t\t\t\t\t\tif( _isHtmlFile(file) ){\n\t\t\t\t\t\t\t\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tif( matrix.deg == 'auto' ){\n\t\t\t\t\t\t\t\t\tmatrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfn.call(this, !file.info, {\n\t\t\t\t\t\t\t\t\t  id:\t\tfile.id\n\t\t\t\t\t\t\t\t\t, flashId:\tfile.flashId\n\t\t\t\t\t\t\t\t\t, name:\t\tfile.name\n\t\t\t\t\t\t\t\t\t, type:\t\tfile.type\n\t\t\t\t\t\t\t\t\t, matrix:\tmatrix\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\tapi.Image && _inherit(api.Image, {\n\t\t\t\t\t\tfromDataURL: function (dataURL, size, callback){\n\t\t\t\t\t\t\tif( !api.support.dataURI || dataURL.length > 3e4 ){\n\t\t\t\t\t\t\t\t_makeFlashImage(\n\t\t\t\t\t\t\t\t\t  api.extend({ scale: 'exactFit' }, size)\n\t\t\t\t\t\t\t\t\t, dataURL.replace(/^data:[^,]+,/, '')\n\t\t\t\t\t\t\t\t\t, function (err, el){ callback(el); }\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tthis.parent(dataURL, size, callback);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// FileAPI.Form\n\t\t\t\t\t_inherit(api.Form.prototype, {\n\t\t\t\t\t\ttoData: function (fn){\n\t\t\t\t\t\t\tvar items = this.items, i = items.length;\n\n\t\t\t\t\t\t\tfor( ; i--; ){\n\t\t\t\t\t\t\t\tif( items[i].file && _isHtmlFile(items[i].blob) ){\n\t\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tapi.log('FlashAPI.Form.toData');\n\t\t\t\t\t\t\tfn(items);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\n\t\t\t\t\t// FileAPI.XHR\n\t\t\t\t\t_inherit(api.XHR.prototype, {\n\t\t\t\t\t\t_send: function (options, formData){\n\t\t\t\t\t\t\tif(\n\t\t\t\t\t\t\t\t   formData.nodeName\n\t\t\t\t\t\t\t\t|| formData.append && api.support.html5\n\t\t\t\t\t\t\t\t|| api.isArray(formData) && (typeof formData[0] === 'string')\n\t\t\t\t\t\t\t){\n\t\t\t\t\t\t\t\t// HTML5, Multipart or IFrame\n\t\t\t\t\t\t\t\treturn\tthis.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\tvar\n\t\t\t\t\t\t\t\t  data = {}\n\t\t\t\t\t\t\t\t, files = {}\n\t\t\t\t\t\t\t\t, _this = this\n\t\t\t\t\t\t\t\t, flashId\n\t\t\t\t\t\t\t\t, fileId\n\t\t\t\t\t\t\t;\n\n\t\t\t\t\t\t\t_each(formData, function (item){\n\t\t\t\t\t\t\t\tif( item.file ){\n\t\t\t\t\t\t\t\t\tfiles[item.name] = item = _getFileDescr(item.blob);\n\t\t\t\t\t\t\t\t\tfileId  = item.id;\n\t\t\t\t\t\t\t\t\tflashId = item.flashId;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tdata[item.name] = item.blob;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tif( !fileId ){\n\t\t\t\t\t\t\t\tflashId = _attr;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif( !flashId ){\n\t\t\t\t\t\t\t\tapi.log('[err] FlashAPI._send: flashId -- undefined');\n\t\t\t\t\t\t\t\treturn this.parent.apply(this, arguments);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tapi.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t_this.xhr = {\n\t\t\t\t\t\t\t\theaders: {},\n\t\t\t\t\t\t\t\tabort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },\n\t\t\t\t\t\t\t\tgetResponseHeader: function (name){ return this.headers[name]; },\n\t\t\t\t\t\t\t\tgetAllResponseHeaders: function (){ return this.headers; }\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tvar queue = api.queue(function (){\n\t\t\t\t\t\t\t\tflash.uploadInProgress = true;\n\t\t\t\t\t\t\t\tflash.cmd(flashId, 'upload', {\n\t\t\t\t\t\t\t\t\t  url: _getUrl(options.url.replace(/([a-z]+)=(\\?)&?/i, ''))\n\t\t\t\t\t\t\t\t\t, data: data\n\t\t\t\t\t\t\t\t\t, files: fileId ? files : null\n\t\t\t\t\t\t\t\t\t, headers: options.headers || {}\n\t\t\t\t\t\t\t\t\t, callback: _wrap(function upload(evt){\n\t\t\t\t\t\t\t\t\t\tvar type = evt.type, result = evt.result;\n\n\t\t\t\t\t\t\t\t\t\tapi.log('FlashAPI.upload.'+type);\n\n\t\t\t\t\t\t\t\t\t\tif( type == 'progress' ){\n\t\t\t\t\t\t\t\t\t\t\tevt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme\n\t\t\t\t\t\t\t\t\t\t\tevt.lengthComputable = true;\n\t\t\t\t\t\t\t\t\t\t\toptions.progress(evt);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'complete' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\n\t\t\t\t\t\t\t\t\t\t\tif( typeof result == 'string' ){\n\t\t\t\t\t\t\t\t\t\t\t\t_this.responseText\t= result.replace(/%22/g, \"\\\"\").replace(/%5c/g, \"\\\\\").replace(/%26/g, \"&\").replace(/%25/g, \"%\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 200);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if( type == 'abort' || type == 'error' ){\n\t\t\t\t\t\t\t\t\t\t\tflash.uploadInProgress = false;\n\t\t\t\t\t\t\t\t\t\t\t_this.end(evt.status || 0, evt.message);\n\t\t\t\t\t\t\t\t\t\t\t_unwrap(upload);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\n\n\t\t\t\t\t\t\t// #2174: FileReference.load() call while FileReference.upload() or vice versa\n\t\t\t\t\t\t\t_each(files, function (file){\n\t\t\t\t\t\t\t\tqueue.inc();\n\t\t\t\t\t\t\t\tapi.getInfo(file, queue.next);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tqueue.check();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t;\n\n\n\t\tfunction _makeFlashHTML(opts){\n\t\t\treturn ('<object id=\"#id#\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\">'\n\t\t\t\t+ '<param name=\"movie\" value=\"#src#\" />'\n\t\t\t\t+ '<param name=\"flashvars\" value=\"#flashvars#\" />'\n\t\t\t\t+ '<param name=\"swliveconnect\" value=\"true\" />'\n\t\t\t\t+ '<param name=\"allowscriptaccess\" value=\"always\" />'\n\t\t\t\t+ '<param name=\"allownetworking\" value=\"all\" />'\n\t\t\t\t+ '<param name=\"menu\" value=\"false\" />'\n\t\t\t\t+ '<param name=\"wmode\" value=\"#wmode#\" />'\n\t\t\t\t+ '<embed flashvars=\"#flashvars#\" swliveconnect=\"true\" allownetworking=\"all\" allowscriptaccess=\"always\" name=\"#id#\" src=\"#src#\" width=\"'+(opts.width || '100%')+'\" height=\"'+(opts.height || '100%')+'\" menu=\"false\" wmode=\"transparent\" type=\"application/x-shockwave-flash\"></embed>'\n\t\t\t\t+ '</object>').replace(/#(\\w+)#/ig, function (a, name){ return opts[name]; })\n\t\t\t;\n\t\t}\n\n\n\t\tfunction _css(el, css){\n\t\t\tif( el && el.style ){\n\t\t\t\tvar key, val;\n\t\t\t\tfor( key in css ){\n\t\t\t\t\tval = css[key];\n\t\t\t\t\tif( typeof val == 'number' ){\n\t\t\t\t\t\tval += 'px';\n\t\t\t\t\t}\n\t\t\t\t\ttry { el.style[key] = val; } catch (e) {}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t}\n\t\t}\n\n\n\t\tfunction _inherit(obj, methods){\n\t\t\t_each(methods, function (fn, name){\n\t\t\t\tvar prev = obj[name];\n\t\t\t\tobj[name] = function (){\n\t\t\t\t\tthis.parent = prev;\n\t\t\t\t\treturn fn.apply(this, arguments);\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\n\t\tfunction _isHtmlFile(file){\n\t\t\treturn\tfile && !file.flashId;\n\t\t}\n\n\t\tfunction _wrap(fn){\n\t\t\tvar id = fn.wid = api.uid();\n\t\t\tflash._fn[id] = fn;\n\t\t\treturn\t'FileAPI.Flash._fn.'+id;\n\t\t}\n\n\n\t\tfunction _unwrap(fn){\n\t\t\ttry {\n\t\t\t\tflash._fn[fn.wid] = null;\n\t\t\t\tdelete\tflash._fn[fn.wid];\n\t\t\t}\n\t\t\tcatch(e){}\n\t\t}\n\n\n\t\tfunction _getUrl(url, params){\n\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\tif( /^\\.\\//.test(url) || '/' != url.charAt(0) ){\n\t\t\t\t\tvar path = location.pathname;\n\t\t\t\t\tpath = path.substr(0, path.lastIndexOf('/'));\n\t\t\t\t\turl = (path +'/'+ url).replace('/./', '/');\n\t\t\t\t}\n\n\t\t\t\tif( '//' != url.substr(0, 2) ){\n\t\t\t\t\turl = '//' + location.host + url;\n\t\t\t\t}\n\n\t\t\t\tif( !_rhttp.test(url) ){\n\t\t\t\t\turl = location.protocol + url;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( params ){\n\t\t\t\turl += (/\\?/.test(url) ? '&' : '?') + params;\n\t\t\t}\n\n\t\t\treturn\turl;\n\t\t}\n\n\n\t\tfunction _makeFlashImage(opts, base64, fn){\n\t\t\tvar\n\t\t\t\t  key\n\t\t\t\t, flashId = api.uid()\n\t\t\t\t, el = document.createElement('div')\n\t\t\t\t, attempts = 10\n\t\t\t;\n\n\t\t\tfor( key in opts ){\n\t\t\t\tel.setAttribute(key, opts[key]);\n\t\t\t\tel[key] = opts[key];\n\t\t\t}\n\n\t\t\t_css(el, opts);\n\n\t\t\topts.width\t= '100%';\n\t\t\topts.height\t= '100%';\n\n\t\t\tel.innerHTML = _makeFlashHTML(api.extend({\n\t\t\t\t  id: flashId\n\t\t\t\t, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())\n\t\t\t\t, wmode: 'opaque'\n\t\t\t\t, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){\n\t\t\t\t\t_unwrap(_);\n\t\t\t\t\tif( --attempts > 0 ){\n\t\t\t\t\t\t_setImage();\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t})\n\t\t\t}, opts));\n\n\t\t\tfunction _setImage(){\n\t\t\t\ttry {\n\t\t\t\t\t// Get flash-object by id\n\t\t\t\t\tvar img = flash.get(flashId);\n\t\t\t\t\timg.setImage(base64);\n\t\t\t\t} catch (e){\n\t\t\t\t\tapi.log('[err] FlashAPI.Preview.setImage -- can not set \"base64\":', e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfn(false, el);\n\t\t\tel = null;\n\t\t}\n\n\n\t\tfunction _getFileDescr(file){\n\t\t\treturn\t{\n\t\t\t\t  id: file.id\n\t\t\t\t, name: file.name\n\t\t\t\t, matrix: file.matrix\n\t\t\t\t, flashId: file.flashId\n\t\t\t};\n\t\t}\n\n\n\t\tfunction _getDimensions(el){\n\t\t\tvar\n\t\t\t\t  box = el.getBoundingClientRect()\n\t\t\t\t, body = document.body\n\t\t\t\t, docEl = (el && el.ownerDocument).documentElement\n\t\t\t;\n\t\t\t\n\t\t\tfunction getOffset(obj) {\n\t\t\t    var left, top;\n\t\t\t    left = top = 0;\n\t\t\t    if (obj.offsetParent) {\n\t\t\t        do {\n\t\t\t            left += obj.offsetLeft;\n\t\t\t            top  += obj.offsetTop;\n\t\t\t        } while (obj = obj.offsetParent);\n\t\t\t    }\n\t\t\t    return {\n\t\t\t        left : left,\n\t\t\t        top : top\n\t\t\t    };\n\t\t\t};\n\t\t\t\n\t\t\treturn {\n\t\t\t\t  top:\t\tgetOffset(el).top\n\t\t\t\t, left:\t\tgetOffset(el).left\n\t\t\t\t, width:\tel.offsetWidth\n\t\t\t\t, height:\tel.offsetHeight\n\t\t\t};\n\t\t}\n\n\t\t// @export\n\t\tapi.Flash = flash;\n\n\n\t\t// Check dataURI support\n\t\tapi.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){\n\t\t\tapi.support.dataURI = !(img.width != 1 || img.height != 1);\n\t\t\tflash.init();\n\t\t});\n\t})();\n})(window, window.jQuery, FileAPI);\n\n/**\n * FileAPI fallback to Flash\n *\n * @flash-developer  \"Vladimir Demidov\" <v.demidov@corp.mail.ru>\n */\n\n/*global window, FileAPI */\n(function (window, jQuery, api) {\n    \"use strict\";\n\n    var _each = api.each,\n        _cameraQueue = [];\n\n\n    if (api.support.flash && (api.media && !api.support.media)) {\n        (function () {\n\n            function _wrap(fn) {\n                var id = fn.wid = api.uid();\n                api.Flash._fn[id] = fn;\n                return 'FileAPI.Flash._fn.' + id;\n            }\n\n\n            function _unwrap(fn) {\n                try {\n                    api.Flash._fn[fn.wid] = null;\n                    delete api.Flash._fn[fn.wid];\n                } catch (e) {\n                }\n            }\n\n            var flash = api.Flash;\n            api.extend(api.Flash, {\n\n                patchCamera: function () {\n                    api.Camera.fallback = function (el, options, callback) {\n                        var camId = api.uid();\n                        api.log('FlashAPI.Camera.publish: ' + camId);\n                        flash.publish(el, camId, api.extend(options, {\n                            camera: true,\n                            onEvent: _wrap(function _(evt) {\n                                if (evt.type === 'camera') {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);\n                                        callback(evt.error);\n                                    } else {\n                                        api.log('FlashAPI.Camera.publish.success: ' + camId);\n                                        callback(null);\n                                    }\n                                }\n                            })\n                        }));\n                    };\n                    // Run\n                    _each(_cameraQueue, function (args) {\n                        api.Camera.fallback.apply(api.Camera, args);\n                    });\n                    _cameraQueue = [];\n\n\n                    // FileAPI.Camera:proto\n                    api.extend(api.Camera.prototype, {\n                        _id: function () {\n                            return this.video.id;\n                        },\n\n                        start: function (callback) {\n                            var _this = this;\n                            flash.cmd(this._id(), 'camera.on', {\n                                callback: _wrap(function _(evt) {\n                                    _unwrap(_);\n\n                                    if (evt.error) {\n                                        api.log('FlashAPI.camera.on.error: ' + evt.error);\n                                        callback(evt.error, _this);\n                                    } else {\n                                        api.log('FlashAPI.camera.on.success: ' + _this._id());\n                                        _this._active = true;\n                                        callback(null, _this);\n                                    }\n                                })\n                            });\n                        },\n\n                        stop: function () {\n                            this._active = false;\n                            flash.cmd(this._id(), 'camera.off');\n                        },\n\n                        shot: function () {\n                            api.log('FlashAPI.Camera.shot:', this._id());\n\n                            var shot = api.Flash.cmd(this._id(), 'shot', {});\n                            shot.type = 'image/png';\n                            shot.flashId = this._id();\n                            shot.isShot = true;\n\n                            return new api.Camera.Shot(shot);\n                        }\n                    });\n                }\n            });\n\n            api.Camera.fallback = function () {\n                _cameraQueue.push(arguments);\n            };\n\n        }());\n    }\n}(window, window.jQuery, FileAPI));\nif( typeof define === \"function\" && define.amd ){ define(\"FileAPI\", [], function (){ return FileAPI; }); }\n"
  },
  {
    "path": "src/data-url.js",
    "content": "(function () {\n\n  ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {\n    var upload = UploadBase;\n    upload.base64DataUrl = function (file) {\n      if (angular.isArray(file)) {\n        var d = $q.defer(), count = 0;\n        angular.forEach(file, function (f) {\n          upload.dataUrl(f, true)['finally'](function () {\n            count++;\n            if (count === file.length) {\n              var urls = [];\n              angular.forEach(file, function (ff) {\n                urls.push(ff.$ngfDataUrl);\n              });\n              d.resolve(urls, file);\n            }\n          });\n        });\n        return d.promise;\n      } else {\n        return upload.dataUrl(file, true);\n      }\n    };\n    upload.dataUrl = function (file, disallowObjectUrl) {\n      if (!file) return upload.emptyPromise(file, file);\n      if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {\n        return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);\n      }\n      var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;\n      if (p) return p;\n\n      var deferred = $q.defer();\n      $timeout(function () {\n        if (window.FileReader && file &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&\n          (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {\n          //prefer URL.createObjectURL for handling refrences to files of all sizes\n          //since it doesn´t build a large string in memory\n          var URL = window.URL || window.webkitURL;\n          if (URL && URL.createObjectURL && !disallowObjectUrl) {\n            var url;\n            try {\n              url = URL.createObjectURL(file);\n            } catch (e) {\n              $timeout(function () {\n                file.$ngfBlobUrl = '';\n                deferred.reject();\n              });\n              return;\n            }\n            $timeout(function () {\n              file.$ngfBlobUrl = url;\n              if (url) {\n                deferred.resolve(url, file);\n                upload.blobUrls = upload.blobUrls || [];\n                upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;\n                upload.blobUrls.push({url: url, size: file.size});\n                upload.blobUrlsTotalSize += file.size || 0;\n                var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;\n                var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;\n                while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {\n                  var obj = upload.blobUrls.splice(0, 1)[0];\n                  URL.revokeObjectURL(obj.url);\n                  upload.blobUrlsTotalSize -= obj.size;\n                }\n              }\n            });\n          } else {\n            var fileReader = new FileReader();\n            fileReader.onload = function (e) {\n              $timeout(function () {\n                file.$ngfDataUrl = e.target.result;\n                deferred.resolve(e.target.result, file);\n                $timeout(function () {\n                  delete file.$ngfDataUrl;\n                }, 1000);\n              });\n            };\n            fileReader.onerror = function () {\n              $timeout(function () {\n                file.$ngfDataUrl = '';\n                deferred.reject();\n              });\n            };\n            fileReader.readAsDataURL(file);\n          }\n        } else {\n          $timeout(function () {\n            file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';\n            deferred.reject();\n          });\n        }\n      });\n\n      if (disallowObjectUrl) {\n        p = file.$$ngfDataUrlPromise = deferred.promise;\n      } else {\n        p = file.$$ngfBlobUrlPromise = deferred.promise;\n      }\n      p['finally'](function () {\n        delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];\n      });\n      return p;\n    };\n    return upload;\n  }]);\n\n  function getTagType(el) {\n    if (el.tagName.toLowerCase() === 'img') return 'image';\n    if (el.tagName.toLowerCase() === 'audio') return 'audio';\n    if (el.tagName.toLowerCase() === 'video') return 'video';\n    return /./;\n  }\n\n  function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {\n    function constructDataUrl(file) {\n      var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);\n      Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {\n        $timeout(function () {\n          var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;\n          if (isBackground) {\n            elem.css('background-image', 'url(\\'' + (src || '') + '\\')');\n          } else {\n            elem.attr('src', src);\n          }\n          if (src) {\n            elem.removeClass('ng-hide');\n          } else {\n            elem.addClass('ng-hide');\n          }\n        });\n      });\n    }\n\n    $timeout(function () {\n      var unwatch = scope.$watch(attr[directiveName], function (file) {\n        var size = resizeParams;\n        if (directiveName === 'ngfThumbnail') {\n          if (!size) {\n            size = {\n              width: elem[0].naturalWidth || elem[0].clientWidth,\n              height: elem[0].naturalHeight || elem[0].clientHeight\n            };\n          }\n          if (size.width === 0 && window.getComputedStyle) {\n            var style = getComputedStyle(elem[0]);\n            if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {\n              size = {\n                width: parseInt(style.width.slice(0, -2)),\n                height: parseInt(style.height.slice(0, -2))\n              };\n            }\n          }\n        }\n\n        if (angular.isString(file)) {\n          elem.removeClass('ng-hide');\n          if (isBackground) {\n            return elem.css('background-image', 'url(\\'' + file + '\\')');\n          } else {\n            return elem.attr('src', file);\n          }\n        }\n        if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&\n          (!isBackground || file.type.indexOf('image') === 0)) {\n          if (size && Upload.isResizeSupported()) {\n            size.resizeIf = function (width, height) {\n              return Upload.attrGetter('ngfResizeIf', attr, scope,\n                {$width: width, $height: height, $file: file});\n            };\n            Upload.resize(file, size).then(\n              function (f) {\n                constructDataUrl(f);\n              }, function (e) {\n                throw e;\n              }\n            );\n          } else {\n            constructDataUrl(file);\n          }\n        } else {\n          elem.addClass('ng-hide');\n        }\n      });\n\n      scope.$on('$destroy', function () {\n        unwatch();\n      });\n    });\n  }\n\n\n  /** @namespace attr.ngfSrc */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',\n          Upload.attrGetter('ngfResize', attr, scope), false);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfBackground */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',\n          Upload.attrGetter('ngfResize', attr, scope), true);\n      }\n    };\n  }]);\n\n  /** @namespace attr.ngfThumbnail */\n  /** @namespace attr.ngfAsBackground */\n  /** @namespace attr.ngfSize */\n  /** @namespace attr.ngfNoObjectUrl */\n  ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {\n    return {\n      restrict: 'AE',\n      link: function (scope, elem, attr) {\n        var size = Upload.attrGetter('ngfSize', attr, scope);\n        linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,\n          Upload.attrGetter('ngfAsBackground', attr, scope));\n      }\n    };\n  }]);\n\n  ngFileUpload.config(['$compileProvider', function ($compileProvider) {\n    if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n    if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);\n  }]);\n\n  ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {\n    return function (file, disallowObjectUrl, trustedUrl) {\n      if (angular.isString(file)) {\n        return $sce.trustAsResourceUrl(file);\n      }\n      var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);\n      if (file && !src) {\n        if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {\n          file.$ngfDataUrlFilterInProgress = true;\n          UploadDataUrl.dataUrl(file, disallowObjectUrl);\n        }\n        return '';\n      }\n      if (file) delete file.$ngfDataUrlFilterInProgress;\n      return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';\n    };\n  }]);\n\n})();\n"
  },
  {
    "path": "src/drop.js",
    "content": "(function () {\n  ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',\n    function ($parse, $timeout, $window, Upload, $http, $q) {\n      return {\n        restrict: 'AEC',\n        require: '?ngModel',\n        link: function (scope, elem, attr, ngModel) {\n          linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);\n        }\n      };\n    }]);\n\n  ngFileUpload.directive('ngfNoFileDrop', function () {\n    return function (scope, elem) {\n      if (dropAvailable()) elem.css('display', 'none');\n    };\n  });\n\n  ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {\n    return function (scope, elem, attr) {\n      if (dropAvailable()) {\n        var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));\n        $timeout(function () {\n          model(scope);\n          if (model.assign) {\n            model.assign(scope, true);\n          }\n        });\n      }\n    };\n  }]);\n\n  function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {\n    var available = dropAvailable();\n\n    var attrGetter = function (name, scope, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    if (attrGetter('dropAvailable')) {\n      $timeout(function () {\n        if (scope[attrGetter('dropAvailable')]) {\n          scope[attrGetter('dropAvailable')].value = available;\n        } else {\n          scope[attrGetter('dropAvailable')] = available;\n        }\n      });\n    }\n    if (!available) {\n      if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {\n        elem.css('display', 'none');\n      }\n      return;\n    }\n\n    function isDisabled() {\n      return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);\n    }\n\n    if (attrGetter('ngfSelect') == null) {\n      upload.registerModelChangeValidator(ngModel, attr, scope);\n    }\n\n    var leaveTimeout = null;\n    var stopPropagation = $parse(attrGetter('ngfStopPropagation'));\n    var dragOverDelay = 1;\n    var actualDragOverClass;\n\n    elem[0].addEventListener('dragover', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      // handling dragover events from the Chrome download bar\n      if (navigator.userAgent.indexOf('Chrome') > -1) {\n        var b = evt.dataTransfer.effectAllowed;\n        evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';\n      }\n      $timeout.cancel(leaveTimeout);\n      if (!actualDragOverClass) {\n        actualDragOverClass = 'C';\n        calculateDragOverClass(scope, attr, evt, function (clazz) {\n          actualDragOverClass = clazz;\n          elem.addClass(actualDragOverClass);\n          attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});\n        });\n      }\n    }, false);\n    elem[0].addEventListener('dragenter', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n    }, false);\n    elem[0].addEventListener('dragleave', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      leaveTimeout = $timeout(function () {\n        if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n        actualDragOverClass = null;\n        attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});\n      }, dragOverDelay || 100);\n    }, false);\n    elem[0].addEventListener('drop', function (evt) {\n      if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;\n      evt.preventDefault();\n      if (stopPropagation(scope)) evt.stopPropagation();\n      if (actualDragOverClass) elem.removeClass(actualDragOverClass);\n      actualDragOverClass = null;\n      extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');\n    }, false);\n    elem[0].addEventListener('paste', function (evt) {\n      if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n        attrGetter('ngfEnableFirefoxPaste', scope)) {\n        evt.preventDefault();\n      }\n      if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;\n      extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');\n    }, false);\n\n    if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&\n      attrGetter('ngfEnableFirefoxPaste', scope)) {\n      elem.attr('contenteditable', true);\n      elem.on('keypress', function (e) {\n        if (!e.metaKey && !e.ctrlKey) {\n          e.preventDefault();\n        }\n      });\n    }\n\n    function extractFilesAndUpdateModel(source, evt, updateOnType) {\n      if (!source) return;\n      // html needs to be calculated on the same process otherwise the data will be wiped\n      // after promise resolve or setTimeout.\n      var html;\n      try {\n        html = source && source.getData && source.getData('text/html');\n      } catch (e) {/* Fix IE11 that throw error calling getData */\n      }\n      extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,\n        attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {\n        if (files.length) {\n          updateModel(files, evt);\n        } else {\n          extractFilesFromHtml(updateOnType, html).then(function (files) {\n            updateModel(files, evt);\n          });\n        }\n      });\n    }\n\n    function updateModel(files, evt) {\n      upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);\n    }\n\n    function extractFilesFromHtml(updateOn, html) {\n      if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);\n      var urls = [];\n      html.replace(/<(img src|img [^>]* src) *=\\\"([^\\\"]*)\\\"/gi, function (m, n, src) {\n        urls.push(src);\n      });\n      var promises = [], files = [];\n      if (urls.length) {\n        angular.forEach(urls, function (url) {\n          promises.push(upload.urlToBlob(url).then(function (blob) {\n            files.push(blob);\n          }));\n        });\n        var defer = $q.defer();\n        $q.all(promises).then(function () {\n          defer.resolve(files);\n        }, function (e) {\n          defer.reject(e);\n        });\n        return defer.promise;\n      }\n      return upload.emptyPromise();\n    }\n\n    function calculateDragOverClass(scope, attr, evt, callback) {\n      var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';\n      if (angular.isString(obj)) {\n        dClass = obj;\n      } else if (obj) {\n        if (obj.delay) dragOverDelay = obj.delay;\n        if (obj.accept || obj.reject) {\n          var items = evt.dataTransfer.items;\n          if (items == null || !items.length) {\n            dClass = obj.accept;\n          } else {\n            var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});\n            var len = items.length;\n            while (len--) {\n              if (!upload.validatePattern(items[len], pattern)) {\n                dClass = obj.reject;\n                break;\n              } else {\n                dClass = obj.accept;\n              }\n            }\n          }\n        }\n      }\n      callback(dClass);\n    }\n\n    function extractFiles(items, fileList, allowDir, multiple) {\n      var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');\n      if (maxFiles == null) {\n        maxFiles = Number.MAX_VALUE;\n      }\n      var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');\n      if (maxTotalSize == null) {\n        maxTotalSize = Number.MAX_VALUE;\n      }\n      var includeDir = attrGetter('ngfIncludeDir', scope);\n      var files = [], totalSize = 0;\n\n      function traverseFileTree(entry, path) {\n        var defer = $q.defer();\n        if (entry != null) {\n          if (entry.isDirectory) {\n            var promises = [upload.emptyPromise()];\n            if (includeDir) {\n              var file = {type: 'directory'};\n              file.name = file.path = (path || '') + entry.name;\n              files.push(file);\n            }\n            var dirReader = entry.createReader();\n            var entries = [];\n            var readEntries = function () {\n              dirReader.readEntries(function (results) {\n                try {\n                  if (!results.length) {\n                    angular.forEach(entries.slice(0), function (e) {\n                      if (files.length <= maxFiles && totalSize <= maxTotalSize) {\n                        promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));\n                      }\n                    });\n                    $q.all(promises).then(function () {\n                      defer.resolve();\n                    }, function (e) {\n                      defer.reject(e);\n                    });\n                  } else {\n                    entries = entries.concat(Array.prototype.slice.call(results || [], 0));\n                    readEntries();\n                  }\n                } catch (e) {\n                  defer.reject(e);\n                }\n              }, function (e) {\n                defer.reject(e);\n              });\n            };\n            readEntries();\n          } else {\n            entry.file(function (file) {\n              try {\n                file.path = (path ? path : '') + file.name;\n                if (includeDir) {\n                  file = upload.rename(file, file.path);\n                }\n                files.push(file);\n                totalSize += file.size;\n                defer.resolve();\n              } catch (e) {\n                defer.reject(e);\n              }\n            }, function (e) {\n              defer.reject(e);\n            });\n          }\n        }\n        return defer.promise;\n      }\n\n      var promises = [upload.emptyPromise()];\n\n      if (items && items.length > 0 && $window.location.protocol !== 'file:') {\n        for (var i = 0; i < items.length; i++) {\n          if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {\n            var entry = items[i].webkitGetAsEntry();\n            if (entry.isDirectory && !allowDir) {\n              continue;\n            }\n            if (entry != null) {\n              promises.push(traverseFileTree(entry));\n            }\n          } else {\n            var f = items[i].getAsFile();\n            if (f != null) {\n              files.push(f);\n              totalSize += f.size;\n            }\n          }\n          if (files.length > maxFiles || totalSize > maxTotalSize ||\n            (!multiple && files.length > 0)) break;\n        }\n      } else {\n        if (fileList != null) {\n          for (var j = 0; j < fileList.length; j++) {\n            var file = fileList.item(j);\n            if (file.type || file.size > 0) {\n              files.push(file);\n              totalSize += file.size;\n            }\n            if (files.length > maxFiles || totalSize > maxTotalSize ||\n              (!multiple && files.length > 0)) break;\n          }\n        }\n      }\n\n      var defer = $q.defer();\n      $q.all(promises).then(function () {\n        if (!multiple && !includeDir && files.length) {\n          var i = 0;\n          while (files[i] && files[i].type === 'directory') i++;\n          defer.resolve([files[i]]);\n        } else {\n          defer.resolve(files);\n        }\n      }, function (e) {\n        defer.reject(e);\n      });\n\n      return defer.promise;\n    }\n  }\n\n  function dropAvailable() {\n    var div = document.createElement('div');\n    return ('draggable' in div) && ('ondrop' in div) && !/Edge\\/12./i.test(navigator.userAgent);\n  }\n\n})();\n"
  },
  {
    "path": "src/exif.js",
    "content": "// customized version of https://github.com/exif-js/exif-js\nngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {\n  var upload = UploadResize;\n\n  upload.isExifSupported = function () {\n    return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();\n  };\n\n  function applyTransform(ctx, orientation, width, height) {\n    switch (orientation) {\n      case 2:\n        return ctx.transform(-1, 0, 0, 1, width, 0);\n      case 3:\n        return ctx.transform(-1, 0, 0, -1, width, height);\n      case 4:\n        return ctx.transform(1, 0, 0, -1, 0, height);\n      case 5:\n        return ctx.transform(0, 1, 1, 0, 0, 0);\n      case 6:\n        return ctx.transform(0, 1, -1, 0, height, 0);\n      case 7:\n        return ctx.transform(0, -1, -1, 0, height, width);\n      case 8:\n        return ctx.transform(0, -1, 1, 0, 0, width);\n    }\n  }\n\n  upload.readOrientation = function (file) {\n    var defer = $q.defer();\n    var reader = new FileReader();\n    var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;\n    reader.readAsArrayBuffer(slicedFile);\n    reader.onerror = function (e) {\n      return defer.reject(e);\n    };\n    reader.onload = function (e) {\n      var result = {orientation: 1};\n      var view = new DataView(this.result);\n      if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);\n\n      var length = view.byteLength,\n        offset = 2;\n      while (offset < length) {\n        var marker = view.getUint16(offset, false);\n        offset += 2;\n        if (marker === 0xFFE1) {\n          if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);\n\n          var little = view.getUint16(offset += 6, false) === 0x4949;\n          offset += view.getUint32(offset + 4, little);\n          var tags = view.getUint16(offset, little);\n          offset += 2;\n          for (var i = 0; i < tags; i++)\n            if (view.getUint16(offset + (i * 12), little) === 0x0112) {\n              var orientation = view.getUint16(offset + (i * 12) + 8, little);\n              if (orientation >= 2 && orientation <= 8) {\n                view.setUint16(offset + (i * 12) + 8, 1, little);\n                result.fixedArrayBuffer = e.target.result;\n              }\n              result.orientation = orientation;\n              return defer.resolve(result);\n            }\n        } else if ((marker & 0xFF00) !== 0xFF00) break;\n        else offset += view.getUint16(offset, false);\n      }\n      return defer.resolve(result);\n    };\n    return defer.promise;\n  };\n\n  function arrayBufferToBase64(buffer) {\n    var binary = '';\n    var bytes = new Uint8Array(buffer);\n    var len = bytes.byteLength;\n    for (var i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return window.btoa(binary);\n  }\n\n  upload.applyExifRotation = function (file) {\n    if (file.type.indexOf('image/jpeg') !== 0) {\n      return upload.emptyPromise(file);\n    }\n\n    var deferred = $q.defer();\n    upload.readOrientation(file).then(function (result) {\n      if (result.orientation < 2 || result.orientation > 8) {\n        return deferred.resolve(file);\n      }\n      upload.dataUrl(file, true).then(function (url) {\n        var canvas = document.createElement('canvas');\n        var img = document.createElement('img');\n\n        img.onload = function () {\n          try {\n            canvas.width = result.orientation > 4 ? img.height : img.width;\n            canvas.height = result.orientation > 4 ? img.width : img.height;\n            var ctx = canvas.getContext('2d');\n            applyTransform(ctx, result.orientation, img.width, img.height);\n            ctx.drawImage(img, 0, 0);\n            var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);\n            dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);\n            var blob = upload.dataUrltoBlob(dataUrl, file.name);\n            deferred.resolve(blob);\n          } catch (e) {\n            return deferred.reject(e);\n          }\n        };\n        img.onerror = function () {\n          deferred.reject();\n        };\n        img.src = url;\n      }, function (e) {\n        deferred.reject(e);\n      });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  upload.restoreExif = function (orig, resized) {\n    var ExifRestorer = {};\n\n    ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +\n      'QRSTUVWXYZabcdef' +\n      'ghijklmnopqrstuv' +\n      'wxyz0123456789+/' +\n      '=';\n\n    ExifRestorer.encode64 = function (input) {\n      var output = '',\n        chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0;\n\n      do {\n        chr1 = input[i++];\n        chr2 = input[i++];\n        chr3 = input[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n          enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n          enc4 = 64;\n        }\n\n        output = output +\n          this.KEY_STR.charAt(enc1) +\n          this.KEY_STR.charAt(enc2) +\n          this.KEY_STR.charAt(enc3) +\n          this.KEY_STR.charAt(enc4);\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n      } while (i < input.length);\n\n      return output;\n    };\n\n    ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {\n      if (origFileBase64.match('data:image/jpeg;base64,')) {\n        origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');\n      }\n\n      var rawImage = this.decode64(origFileBase64);\n      var segments = this.slice2Segments(rawImage);\n\n      var image = this.exifManipulation(resizedFileBase64, segments);\n\n      return 'data:image/jpeg;base64,' + this.encode64(image);\n    };\n\n\n    ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {\n      var exifArray = this.getExifArray(segments),\n        newImageArray = this.insertExif(resizedFileBase64, exifArray);\n      return new Uint8Array(newImageArray);\n    };\n\n\n    ExifRestorer.getExifArray = function (segments) {\n      var seg;\n      for (var x = 0; x < segments.length; x++) {\n        seg = segments[x];\n        if (seg[0] === 255 & seg[1] === 225) //(ff e1)\n        {\n          return seg;\n        }\n      }\n      return [];\n    };\n\n\n    ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {\n      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),\n        buf = this.decode64(imageData),\n        separatePoint = buf.indexOf(255, 3),\n        mae = buf.slice(0, separatePoint),\n        ato = buf.slice(separatePoint),\n        array = mae;\n\n      array = array.concat(exifArray);\n      array = array.concat(ato);\n      return array;\n    };\n\n\n    ExifRestorer.slice2Segments = function (rawImageArray) {\n      var head = 0,\n        segments = [];\n\n      while (1) {\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {\n          break;\n        }\n        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {\n          head += 2;\n        }\n        else {\n          var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n            endPoint = head + length + 2,\n            seg = rawImageArray.slice(head, endPoint);\n          segments.push(seg);\n          head = endPoint;\n        }\n        if (head > rawImageArray.length) {\n          break;\n        }\n      }\n\n      return segments;\n    };\n\n\n    ExifRestorer.decode64 = function (input) {\n      var chr1, chr2, chr3 = '',\n        enc1, enc2, enc3, enc4 = '',\n        i = 0,\n        buf = [];\n\n      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n      var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n      if (base64test.exec(input)) {\n        console.log('There were invalid base64 characters in the input text.\\n' +\n          'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and \"=\"\\n' +\n          'Expect errors in decoding.');\n      }\n      input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, '');\n\n      do {\n        enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n        enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        buf.push(chr1);\n\n        if (enc3 !== 64) {\n          buf.push(chr2);\n        }\n        if (enc4 !== 64) {\n          buf.push(chr3);\n        }\n\n        chr1 = chr2 = chr3 = '';\n        enc1 = enc2 = enc3 = enc4 = '';\n\n      } while (i < input.length);\n\n      return buf;\n    };\n\n    return ExifRestorer.restore(orig, resized);  //<= EXIF\n  };\n\n  return upload;\n}]);\n\n"
  },
  {
    "path": "src/model.js",
    "content": "ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {\n  var upload = UploadExif;\n  upload.getAttrWithDefaults = function (attr, name) {\n    if (attr[name] != null) return attr[name];\n    var def = upload.defaults[name];\n    return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));\n  };\n\n  upload.attrGetter = function (name, attr, scope, params) {\n    var attrVal = this.getAttrWithDefaults(attr, name);\n    if (scope) {\n      try {\n        if (params) {\n          return $parse(attrVal)(scope, params);\n        } else {\n          return $parse(attrVal)(scope);\n        }\n      } catch (e) {\n        // hangle string value without single qoute\n        if (name.search(/min|max|pattern/i)) {\n          return attrVal;\n        } else {\n          throw e;\n        }\n      }\n    } else {\n      return attrVal;\n    }\n  };\n\n  upload.shouldUpdateOn = function (type, attr, scope) {\n    var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);\n    if (modelOptions && modelOptions.updateOn) {\n      return modelOptions.updateOn.split(' ').indexOf(type) > -1;\n    }\n    return true;\n  };\n\n  upload.emptyPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.resolve.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.rejectPromise = function () {\n    var d = $q.defer();\n    var args = arguments;\n    $timeout(function () {\n      d.reject.apply(d, args);\n    });\n    return d.promise;\n  };\n\n  upload.happyPromise = function (promise, data) {\n    var d = $q.defer();\n    promise.then(function (result) {\n      d.resolve(result);\n    }, function (error) {\n      $timeout(function () {\n        throw error;\n      });\n      d.resolve(data);\n    });\n    return d.promise;\n  };\n\n  function applyExifRotations(files, attr, scope) {\n    var promises = [upload.emptyPromise()];\n    angular.forEach(files, function (f, i) {\n      if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {\n        promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {\n          files.splice(i, 1, fixedFile);\n        }));\n      }\n    });\n    return $q.all(promises);\n  }\n\n  function resizeFile(files, attr, scope, ngModel) {\n    var resizeVal = upload.attrGetter('ngfResize', attr, scope);\n    if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();\n    if (resizeVal instanceof Function) {\n      var defer = $q.defer();\n      return resizeVal(files).then(function (p) {\n        resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {\n          defer.resolve(r);\n        }, function (e) {\n          defer.reject(e);\n        });\n      }, function (e) {\n        defer.reject(e);\n      });\n    } else {\n      return resizeWithParams(resizeVal, files, attr, scope, ngModel);\n    }\n  }\n\n  function resizeWithParams(params, files, attr, scope, ngModel) {\n    var promises = [upload.emptyPromise()];\n\n    function handleFile(f, i) {\n      if (f.type.indexOf('image') === 0) {\n        if (params.pattern && !upload.validatePattern(f, params.pattern)) return;\n        params.resizeIf = function (width, height) {\n          return upload.attrGetter('ngfResizeIf', attr, scope,\n            {$width: width, $height: height, $file: f});\n        };\n        var promise = upload.resize(f, params);\n        promises.push(promise);\n        promise.then(function (resizedFile) {\n          files.splice(i, 1, resizedFile);\n        }, function (e) {\n          f.$error = 'resize';\n          (f.$errorMessages = (f.$errorMessages || {})).resize = true;\n          f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);\n          ngModel.$ngfValidations.push({name: 'resize', valid: false});\n          upload.applyModelValidation(ngModel, files);\n        });\n      }\n    }\n\n    for (var i = 0; i < files.length; i++) {\n      handleFile(files[i], i);\n    }\n    return $q.all(promises);\n  }\n\n  upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {\n    function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {\n      attr.$$ngfPrevValidFiles = files;\n      attr.$$ngfPrevInvalidFiles = invalidFiles;\n      var file = files && files.length ? files[0] : null;\n      var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;\n\n      if (ngModel) {\n        upload.applyModelValidation(ngModel, files);\n        ngModel.$setViewValue(isSingleModel ? file : files);\n      }\n\n      if (fileChange) {\n        $parse(fileChange)(scope, {\n          $files: files,\n          $file: file,\n          $newFiles: newFiles,\n          $duplicateFiles: dupFiles,\n          $invalidFiles: invalidFiles,\n          $invalidFile: invalidFile,\n          $event: evt\n        });\n      }\n\n      var invalidModel = upload.attrGetter('ngfModelInvalid', attr);\n      if (invalidModel) {\n        $timeout(function () {\n          $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);\n        });\n      }\n      $timeout(function () {\n        // scope apply changes\n      });\n    }\n\n    var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,\n      invalids = [], valids = [];\n\n    function removeDuplicates() {\n      function equals(f1, f2) {\n        return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&\n          f1.type === f2.type;\n      }\n\n      function isInPrevFiles(f) {\n        var j;\n        for (j = 0; j < prevValidFiles.length; j++) {\n          if (equals(f, prevValidFiles[j])) {\n            return true;\n          }\n        }\n        for (j = 0; j < prevInvalidFiles.length; j++) {\n          if (equals(f, prevInvalidFiles[j])) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n      if (files) {\n        allNewFiles = [];\n        dupFiles = [];\n        for (var i = 0; i < files.length; i++) {\n          if (isInPrevFiles(files[i])) {\n            dupFiles.push(files[i]);\n          } else {\n            allNewFiles.push(files[i]);\n          }\n        }\n      }\n    }\n\n    function toArray(v) {\n      return angular.isArray(v) ? v : [v];\n    }\n\n    function resizeAndUpdate() {\n      function updateModel() {\n        $timeout(function () {\n          update(keep ? prevValidFiles.concat(valids) : valids,\n            keep ? prevInvalidFiles.concat(invalids) : invalids,\n            files, dupFiles, isSingleModel);\n        }, options && options.debounce ? options.debounce.change || options.debounce : 0);\n      }\n\n      var resizingFiles = validateAfterResize ? allNewFiles : valids;\n      resizeFile(resizingFiles, attr, scope, ngModel).then(function () {\n        if (validateAfterResize) {\n          upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n            .then(function (validationResult) {\n              valids = validationResult.validFiles;\n              invalids = validationResult.invalidFiles;\n              updateModel();\n            });\n        } else {\n          updateModel();\n        }\n      }, function () {\n        for (var i = 0; i < resizingFiles.length; i++) {\n          var f = resizingFiles[i];\n          if (f.$error === 'resize') {\n            var index = valids.indexOf(f);\n            if (index > -1) {\n              valids.splice(index, 1);\n              invalids.push(f);\n            }\n            updateModel();\n          }\n        }\n      });\n    }\n\n    prevValidFiles = attr.$$ngfPrevValidFiles || [];\n    prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];\n    if (ngModel && ngModel.$modelValue) {\n      prevValidFiles = toArray(ngModel.$modelValue);\n    }\n\n    var keep = upload.attrGetter('ngfKeep', attr, scope);\n    allNewFiles = (files || []).slice(0);\n    if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {\n      removeDuplicates(attr, scope);\n    }\n\n    var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);\n\n    if (keep && !allNewFiles.length) return;\n\n    upload.attrGetter('ngfBeforeModelChange', attr, scope, {\n      $files: files,\n      $file: files && files.length ? files[0] : null,\n      $newFiles: allNewFiles,\n      $duplicateFiles: dupFiles,\n      $event: evt\n    });\n\n    var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);\n\n    var options = upload.attrGetter('ngfModelOptions', attr, scope);\n    upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)\n      .then(function (validationResult) {\n      if (noDelay) {\n        update(allNewFiles, [], files, dupFiles, isSingleModel);\n      } else {\n        if ((!options || !options.allowInvalid) && !validateAfterResize) {\n          valids = validationResult.validFiles;\n          invalids = validationResult.invalidFiles;\n        } else {\n          valids = allNewFiles;\n        }\n        if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {\n          applyExifRotations(valids, attr, scope).then(function () {\n            resizeAndUpdate();\n          });\n        } else {\n          resizeAndUpdate();\n        }\n      }\n    });\n  };\n\n  return upload;\n}]);\n"
  },
  {
    "path": "src/resize.js",
    "content": "ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {\n  var upload = UploadValidate;\n\n  /**\n   * Conserve aspect ratio of the original region. Useful when shrinking/enlarging\n   * images to fit into a certain area.\n   * Source:  http://stackoverflow.com/a/14731922\n   *\n   * @param {Number} srcWidth Source area width\n   * @param {Number} srcHeight Source area height\n   * @param {Number} maxWidth Nestable area maximum available width\n   * @param {Number} maxHeight Nestable area maximum available height\n   * @return {Object} { width, height }\n   */\n  var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {\n    var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :\n      Math.min(maxWidth / srcWidth, maxHeight / srcHeight);\n    return {\n      width: srcWidth * ratio, height: srcHeight * ratio,\n      marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight\n    };\n  };\n\n  // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89\n  var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {\n    var deferred = $q.defer();\n    var canvasElement = document.createElement('canvas');\n    var imageElement = document.createElement('img');\n    imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');\n    document.body.appendChild(imageElement);\n\n    imageElement.onload = function () {\n      var imgWidth = imageElement.width, imgHeight = imageElement.height;\n      imageElement.parentNode.removeChild(imageElement);\n      if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {\n        deferred.reject('resizeIf');\n        return;\n      }\n      try {\n        if (ratio) {\n          var ratioFloat = upload.ratioToFloat(ratio);\n          var imgRatio = imgWidth / imgHeight;\n          if (imgRatio < ratioFloat) {\n            width = imgWidth;\n            height = width / ratioFloat;\n          } else {\n            height = imgHeight;\n            width = height * ratioFloat;\n          }\n        }\n        if (!width) {\n          width = imgWidth;\n        }\n        if (!height) {\n          height = imgHeight;\n        }\n        var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);\n        canvasElement.width = Math.min(dimensions.width, width);\n        canvasElement.height = Math.min(dimensions.height, height);\n        var context = canvasElement.getContext('2d');\n        context.drawImage(imageElement,\n          Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),\n          dimensions.width, dimensions.height);\n        deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));\n      } catch (e) {\n        deferred.reject(e);\n      }\n    };\n    imageElement.onerror = function () {\n      imageElement.parentNode.removeChild(imageElement);\n      deferred.reject();\n    };\n    imageElement.src = imagen;\n    return deferred.promise;\n  };\n\n  upload.dataUrltoBlob = function (dataurl, name, origSize) {\n    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],\n      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);\n    while (n--) {\n      u8arr[n] = bstr.charCodeAt(n);\n    }\n    var blob = new window.Blob([u8arr], {type: mime});\n    blob.name = name;\n    blob.$ngfOrigSize = origSize;\n    return blob;\n  };\n\n  upload.isResizeSupported = function () {\n    var elem = document.createElement('canvas');\n    return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;\n  };\n\n  if (upload.isResizeSupported()) {\n    // add name getter to the blob constructor prototype\n    Object.defineProperty(window.Blob.prototype, 'name', {\n      get: function () {\n        return this.$ngfName;\n      },\n      set: function (v) {\n        this.$ngfName = v;\n      },\n      configurable: true\n    });\n  }\n\n  upload.resize = function (file, options) {\n    if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);\n\n    var deferred = $q.defer();\n    upload.dataUrl(file, true).then(function (url) {\n      resize(url, options.width, options.height, options.quality, options.type || file.type,\n        options.ratio, options.centerCrop, options.resizeIf)\n        .then(function (dataUrl) {\n          if (file.type === 'image/jpeg' && options.restoreExif !== false) {\n            try {\n              dataUrl = upload.restoreExif(url, dataUrl);\n            } catch (e) {\n              setTimeout(function () {throw e;}, 1);\n            }\n          }\n          try {\n            var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);\n            deferred.resolve(blob);\n          } catch (e) {\n            deferred.reject(e);\n          }\n        }, function (r) {\n          if (r === 'resizeIf') {\n            deferred.resolve(file);\n          }\n          deferred.reject(r);\n        });\n    }, function (e) {\n      deferred.reject(e);\n    });\n    return deferred.promise;\n  };\n\n  return upload;\n}]);\n"
  },
  {
    "path": "src/select.js",
    "content": "ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {\n  var generatedElems = [];\n\n  function isDelayedClickSupported(ua) {\n    // fix for android native browser < 4.4 and safari windows\n    var m = ua.match(/Android[^\\d]*(\\d+)\\.(\\d+)/);\n    if (m && m.length > 2) {\n      var v = Upload.defaults.androidFixMinorVersion || 4;\n      return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);\n    }\n\n    // safari on windows\n    return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);\n  }\n\n  function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {\n    /** @namespace attr.ngfSelect */\n    /** @namespace attr.ngfChange */\n    /** @namespace attr.ngModel */\n    /** @namespace attr.ngfModelOptions */\n    /** @namespace attr.ngfMultiple */\n    /** @namespace attr.ngfCapture */\n    /** @namespace attr.ngfValidate */\n    /** @namespace attr.ngfKeep */\n    var attrGetter = function (name, scope) {\n      return upload.attrGetter(name, attr, scope);\n    };\n\n    function isInputTypeFile() {\n      return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';\n    }\n\n    function fileChangeAttr() {\n      return attrGetter('ngfChange') || attrGetter('ngfSelect');\n    }\n\n    function changeFn(evt) {\n      if (upload.shouldUpdateOn('change', attr, scope)) {\n        var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];\n        /* Handle duplicate call in  IE11 */\n        if (!fileList) return;\n        for (var i = 0; i < fileList.length; i++) {\n          files.push(fileList[i]);\n        }\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(),\n          files.length ? files : null, evt);\n      }\n    }\n\n    upload.registerModelChangeValidator(ngModel, attr, scope);\n\n    var unwatches = [];\n    if (attrGetter('ngfMultiple')) {\n      unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {\n        fileElem.attr('multiple', attrGetter('ngfMultiple', scope));\n      }));\n    }\n    if (attrGetter('ngfCapture')) {\n      unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {\n        fileElem.attr('capture', attrGetter('ngfCapture', scope));\n      }));\n    }\n    if (attrGetter('ngfAccept')) {\n      unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {\n        fileElem.attr('accept', attrGetter('ngfAccept', scope));\n      }));\n    }\n    unwatches.push(attr.$observe('accept', function () {\n      fileElem.attr('accept', attrGetter('accept'));\n    }));\n    function bindAttrToFileInput(fileElem, label) {\n      function updateId(val) {\n        fileElem.attr('id', 'ngf-' + val);\n        label.attr('id', 'ngf-label-' + val);\n      }\n\n      for (var i = 0; i < elem[0].attributes.length; i++) {\n        var attribute = elem[0].attributes[i];\n        if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {\n          if (attribute.name === 'id') {\n            updateId(attribute.value);\n            unwatches.push(attr.$observe('id', updateId));\n          } else {\n            fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||\n            attribute.name === 'multiple')) ? attribute.name : attribute.value);\n          }\n        }\n      }\n    }\n\n    function createFileInput() {\n      if (isInputTypeFile()) {\n        return elem;\n      }\n\n      var fileElem = angular.element('<input type=\"file\">');\n\n      var label = angular.element('<label>upload</label>');\n      label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')\n        .css('width', '0px').css('height', '0px').css('border', 'none')\n        .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');\n      bindAttrToFileInput(fileElem, label);\n\n      generatedElems.push({el: elem, ref: label});\n\n      document.body.appendChild(label.append(fileElem)[0]);\n\n      return fileElem;\n    }\n\n    function clickHandler(evt) {\n      if (elem.attr('disabled')) return false;\n      if (attrGetter('ngfSelectDisabled', scope)) return;\n\n      var r = detectSwipe(evt);\n      // prevent the click if it is a swipe\n      if (r != null) return r;\n\n      resetModel(evt);\n\n      // fix for md when the element is removed from the DOM and added back #460\n      try {\n        if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {\n          generatedElems.push({el: elem, ref: fileElem.parent()});\n          document.body.appendChild(fileElem.parent()[0]);\n          fileElem.bind('change', changeFn);\n        }\n      } catch (e) {/*ignore*/\n      }\n\n      if (isDelayedClickSupported(navigator.userAgent)) {\n        setTimeout(function () {\n          fileElem[0].click();\n        }, 0);\n      } else {\n        fileElem[0].click();\n      }\n\n      return false;\n    }\n\n\n    var initialTouchStartY = 0;\n    var initialTouchStartX = 0;\n\n    function detectSwipe(evt) {\n      var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);\n      if (touches) {\n        if (evt.type === 'touchstart') {\n          initialTouchStartX = touches[0].clientX;\n          initialTouchStartY = touches[0].clientY;\n          return true; // don't block event default\n        } else {\n          // prevent scroll from triggering event\n          if (evt.type === 'touchend') {\n            var currentX = touches[0].clientX;\n            var currentY = touches[0].clientY;\n            if ((Math.abs(currentX - initialTouchStartX) > 20) ||\n              (Math.abs(currentY - initialTouchStartY) > 20)) {\n              evt.stopPropagation();\n              evt.preventDefault();\n              return false;\n            }\n          }\n          return true;\n        }\n      }\n    }\n\n    var fileElem = elem;\n\n    function resetModel(evt) {\n      if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {\n        fileElem.val(null);\n        upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);\n      }\n    }\n\n    if (!isInputTypeFile()) {\n      fileElem = createFileInput();\n    }\n    fileElem.bind('change', changeFn);\n\n    if (!isInputTypeFile()) {\n      elem.bind('click touchstart touchend', clickHandler);\n    } else {\n      elem.bind('click', resetModel);\n    }\n\n    function ie10SameFileSelectFix(evt) {\n      if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {\n        if (!fileElem[0].parentNode) {\n          fileElem = null;\n          return;\n        }\n        evt.preventDefault();\n        evt.stopPropagation();\n        fileElem.unbind('click');\n        var clone = fileElem.clone();\n        fileElem.replaceWith(clone);\n        fileElem = clone;\n        fileElem.attr('__ngf_ie10_Fix_', 'true');\n        fileElem.bind('change', changeFn);\n        fileElem.bind('click', ie10SameFileSelectFix);\n        fileElem[0].click();\n        return false;\n      } else {\n        fileElem.removeAttr('__ngf_ie10_Fix_');\n      }\n    }\n\n    if (navigator.appVersion.indexOf('MSIE 10') !== -1) {\n      fileElem.bind('click', ie10SameFileSelectFix);\n    }\n\n    if (ngModel) ngModel.$formatters.push(function (val) {\n      if (val == null || val.length === 0) {\n        if (fileElem.val()) {\n          fileElem.val(null);\n        }\n      }\n      return val;\n    });\n\n    scope.$on('$destroy', function () {\n      if (!isInputTypeFile()) fileElem.parent().remove();\n      angular.forEach(unwatches, function (unwatch) {\n        unwatch();\n      });\n    });\n\n    $timeout(function () {\n      for (var i = 0; i < generatedElems.length; i++) {\n        var g = generatedElems[i];\n        if (!document.body.contains(g.el[0])) {\n          generatedElems.splice(i, 1);\n          g.ref.remove();\n        }\n      }\n    });\n\n    if (window.FileAPI && window.FileAPI.ngfFixIE) {\n      window.FileAPI.ngfFixIE(elem, fileElem, changeFn);\n    }\n  }\n\n  return {\n    restrict: 'AEC',\n    require: '?ngModel',\n    link: function (scope, elem, attr, ngModel) {\n      linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/shim-elem.js",
    "content": "(function () {\n  /** @namespace FileAPI.forceLoad */\n  /** @namespace window.FileAPI.jsUrl */\n  /** @namespace window.FileAPI.jsPath */\n\n  function isInputTypeFile(elem) {\n    return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';\n  }\n\n  function hasFlash() {\n    try {\n      var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n      if (fo) return true;\n    } catch (e) {\n      if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;\n    }\n    return false;\n  }\n\n  function getOffset(obj) {\n    var left = 0, top = 0;\n\n    if (window.jQuery) {\n      return jQuery(obj).offset();\n    }\n\n    if (obj.offsetParent) {\n      do {\n        left += (obj.offsetLeft - obj.scrollLeft);\n        top += (obj.offsetTop - obj.scrollTop);\n        obj = obj.offsetParent;\n      } while (obj);\n    }\n    return {\n      left: left,\n      top: top\n    };\n  }\n\n  if (FileAPI.shouldLoad) {\n    FileAPI.hasFlash = hasFlash();\n\n    //load FileAPI\n    if (FileAPI.forceLoad) {\n      FileAPI.html5 = false;\n    }\n\n    if (!FileAPI.upload) {\n      var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;\n      if (window.FileAPI.jsUrl) {\n        jsUrl = window.FileAPI.jsUrl;\n      } else if (window.FileAPI.jsPath) {\n        basePath = window.FileAPI.jsPath;\n      } else {\n        for (i = 0; i < allScripts.length; i++) {\n          src = allScripts[i].src;\n          index = src.search(/\\/ng\\-file\\-upload[\\-a-zA-z0-9\\.]*\\.js/);\n          if (index > -1) {\n            basePath = src.substring(0, index + 1);\n            break;\n          }\n        }\n      }\n\n      if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;\n      script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');\n      document.getElementsByTagName('head')[0].appendChild(script);\n    }\n\n    FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {\n      if (!hasFlash()) {\n        throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n      }\n      var fixInputStyle = function () {\n        var label = fileElem.parent();\n        if (elem.attr('disabled')) {\n          if (label) label.removeClass('js-fileapi-wrapper');\n        } else {\n          if (!fileElem.attr('__ngf_flash_')) {\n            fileElem.unbind('change');\n            fileElem.unbind('click');\n            fileElem.bind('change', function (evt) {\n              fileApiChangeFn.apply(this, [evt]);\n              changeFn.apply(this, [evt]);\n            });\n            fileElem.attr('__ngf_flash_', 'true');\n          }\n          label.addClass('js-fileapi-wrapper');\n          if (!isInputTypeFile(elem)) {\n            label.css('position', 'absolute')\n              .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')\n              .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))\n              .css('overflow', 'hidden').css('z-index', '900000')\n              .css('visibility', 'visible');\n            fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')\n              .css('position', 'absolute').css('top', '0px').css('left', '0px');\n          }\n        }\n      };\n\n      elem.bind('mouseenter', fixInputStyle);\n\n      var fileApiChangeFn = function (evt) {\n        var files = FileAPI.getFiles(evt);\n        //just a double check for #233\n        for (var i = 0; i < files.length; i++) {\n          if (files[i].size === undefined) files[i].size = 0;\n          if (files[i].name === undefined) files[i].name = 'file';\n          if (files[i].type === undefined) files[i].type = 'undefined';\n        }\n        if (!evt.target) {\n          evt.target = {};\n        }\n        evt.target.files = files;\n        // if evt.target.files is not writable use helper field\n        if (evt.target.files !== files) {\n          evt.__files_ = files;\n        }\n        (evt.__files_ || evt.target.files).item = function (i) {\n          return (evt.__files_ || evt.target.files)[i] || null;\n        };\n      };\n    };\n\n    FileAPI.disableFileInput = function (elem, disable) {\n      if (disable) {\n        elem.removeClass('js-fileapi-wrapper');\n      } else {\n        elem.addClass('js-fileapi-wrapper');\n      }\n    };\n  }\n})();\n"
  },
  {
    "path": "src/shim-filereader.js",
    "content": "if (!window.FileReader) {\n  window.FileReader = function () {\n    var _this = this, loadStarted = false;\n    this.listeners = {};\n    this.addEventListener = function (type, fn) {\n      _this.listeners[type] = _this.listeners[type] || [];\n      _this.listeners[type].push(fn);\n    };\n    this.removeEventListener = function (type, fn) {\n      if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);\n    };\n    this.dispatchEvent = function (evt) {\n      var list = _this.listeners[evt.type];\n      if (list) {\n        for (var i = 0; i < list.length; i++) {\n          list[i].call(_this, evt);\n        }\n      }\n    };\n    this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;\n\n    var constructEvent = function (type, evt) {\n      var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};\n      if (evt.result != null) e.target.result = evt.result;\n      return e;\n    };\n    var listener = function (evt) {\n      if (!loadStarted) {\n        loadStarted = true;\n        if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));\n      }\n      var e;\n      if (evt.type === 'load') {\n        if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));\n        e = constructEvent('load', evt);\n        if (_this.onload) _this.onload(e);\n        _this.dispatchEvent(e);\n      } else if (evt.type === 'progress') {\n        e = constructEvent('progress', evt);\n        if (_this.onprogress) _this.onprogress(e);\n        _this.dispatchEvent(e);\n      } else {\n        e = constructEvent('error', evt);\n        if (_this.onerror) _this.onerror(e);\n        _this.dispatchEvent(e);\n      }\n    };\n    this.readAsDataURL = function (file) {\n      FileAPI.readAsDataURL(file, listener);\n    };\n    this.readAsText = function (file) {\n      FileAPI.readAsText(file, listener);\n    };\n  };\n}\n"
  },
  {
    "path": "src/shim-upload.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * FileAPI Flash shim for old browsers not supporting FormData\n * @author  Danial  <danial.farid@gmail.com>\n * @version <%= pkg.version %>\n */\n\n(function () {\n  /** @namespace FileAPI.noContentTimeout */\n\n  function patchXHR(fnName, newFn) {\n    window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);\n  }\n\n  function redefineProp(xhr, prop, fn) {\n    try {\n      Object.defineProperty(xhr, prop, {get: fn});\n    } catch (e) {/*ignore*/\n    }\n  }\n\n  if (!window.FileAPI) {\n    window.FileAPI = {};\n  }\n\n  if (!window.XMLHttpRequest) {\n    throw 'AJAX is not supported. XMLHttpRequest is not defined.';\n  }\n\n  FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;\n  if (FileAPI.shouldLoad) {\n    var initializeUploadListener = function (xhr) {\n      if (!xhr.__listeners) {\n        if (!xhr.upload) xhr.upload = {};\n        xhr.__listeners = [];\n        var origAddEventListener = xhr.upload.addEventListener;\n        xhr.upload.addEventListener = function (t, fn) {\n          xhr.__listeners[t] = fn;\n          if (origAddEventListener) origAddEventListener.apply(this, arguments);\n        };\n      }\n    };\n\n    patchXHR('open', function (orig) {\n      return function (m, url, b) {\n        initializeUploadListener(this);\n        this.__url = url;\n        try {\n          orig.apply(this, [m, url, b]);\n        } catch (e) {\n          if (e.message.indexOf('Access is denied') > -1) {\n            this.__origError = e;\n            orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);\n          }\n        }\n      };\n    });\n\n    patchXHR('getResponseHeader', function (orig) {\n      return function (h) {\n        return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));\n      };\n    });\n\n    patchXHR('getAllResponseHeaders', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('abort', function (orig) {\n      return function () {\n        return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));\n      };\n    });\n\n    patchXHR('setRequestHeader', function (orig) {\n      return function (header, value) {\n        if (header === '__setXHR_') {\n          initializeUploadListener(this);\n          var val = value(this);\n          // fix for angular < 1.2.0\n          if (val instanceof Function) {\n            val(this);\n          }\n        } else {\n          this.__requestHeaders = this.__requestHeaders || {};\n          this.__requestHeaders[header] = value;\n          orig.apply(this, arguments);\n        }\n      };\n    });\n\n    patchXHR('send', function (orig) {\n      return function () {\n        var xhr = this;\n        if (arguments[0] && arguments[0].__isFileAPIShim) {\n          var formData = arguments[0];\n          var config = {\n            url: xhr.__url,\n            jsonp: false, //removes the callback form param\n            cache: true, //removes the ?fileapiXXX in the url\n            complete: function (err, fileApiXHR) {\n              if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {\n                // this error seems to be fine the file is being uploaded properly.\n                err = null;\n              }\n              xhr.__completed = true;\n              if (!err && xhr.__listeners.load)\n                xhr.__listeners.load({\n                  type: 'load',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (!err && xhr.__listeners.loadend)\n                xhr.__listeners.loadend({\n                  type: 'loadend',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (err === 'abort' && xhr.__listeners.abort)\n                xhr.__listeners.abort({\n                  type: 'abort',\n                  loaded: xhr.__loaded,\n                  total: xhr.__total,\n                  target: xhr,\n                  lengthComputable: true\n                });\n              if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {\n                return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;\n              });\n              if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {\n                return fileApiXHR.statusText;\n              });\n              redefineProp(xhr, 'readyState', function () {\n                return 4;\n              });\n              if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {\n                return fileApiXHR.response;\n              });\n              var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);\n              redefineProp(xhr, 'responseText', function () {\n                return resp;\n              });\n              redefineProp(xhr, 'response', function () {\n                return resp;\n              });\n              if (err) redefineProp(xhr, 'err', function () {\n                return err;\n              });\n              xhr.__fileApiXHR = fileApiXHR;\n              if (xhr.onreadystatechange) xhr.onreadystatechange();\n              if (xhr.onload) xhr.onload();\n            },\n            progress: function (e) {\n              e.target = xhr;\n              if (xhr.__listeners.progress) xhr.__listeners.progress(e);\n              xhr.__total = e.total;\n              xhr.__loaded = e.loaded;\n              if (e.total === e.loaded) {\n                // fix flash issue that doesn't call complete if there is no response text from the server\n                var _this = this;\n                setTimeout(function () {\n                  if (!xhr.__completed) {\n                    xhr.getAllResponseHeaders = function () {\n                    };\n                    _this.complete(null, {status: 204, statusText: 'No Content'});\n                  }\n                }, FileAPI.noContentTimeout || 10000);\n              }\n            },\n            headers: xhr.__requestHeaders\n          };\n          config.data = {};\n          config.files = {};\n          for (var i = 0; i < formData.data.length; i++) {\n            var item = formData.data[i];\n            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {\n              config.files[item.key] = item.val;\n            } else {\n              config.data[item.key] = item.val;\n            }\n          }\n\n          setTimeout(function () {\n            if (!FileAPI.hasFlash) {\n              throw 'Adode Flash Player need to be installed. To check ahead use \"FileAPI.hasFlash\"';\n            }\n            xhr.__fileApiXHR = FileAPI.upload(config);\n          }, 1);\n        } else {\n          if (this.__origError) {\n            throw this.__origError;\n          }\n          orig.apply(xhr, arguments);\n        }\n      };\n    });\n    window.XMLHttpRequest.__isFileAPIShim = true;\n    window.FormData = FormData = function () {\n      return {\n        append: function (key, val, name) {\n          if (val.__isFileAPIBlobShim) {\n            val = val.data[0];\n          }\n          this.data.push({\n            key: key,\n            val: val,\n            name: name\n          });\n        },\n        data: [],\n        __isFileAPIShim: true\n      };\n    };\n\n    window.Blob = Blob = function (b) {\n      return {\n        data: b,\n        __isFileAPIBlobShim: true\n      };\n    };\n  }\n\n})();\n"
  },
  {
    "path": "src/upload.js",
    "content": "/**!\n * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,\n * progress, resize, thumbnail, preview, validation and CORS\n * @author  Danial  <danial.farid@gmail.com>\n * @version <%= pkg.version %>\n */\n\nif (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {\n  window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {\n    return function (header, value) {\n      if (header === '__setXHR_') {\n        var val = value(this);\n        // fix for angular < 1.2.0\n        if (val instanceof Function) {\n          val(this);\n        }\n      } else {\n        orig.apply(this, arguments);\n      }\n    };\n  })(window.XMLHttpRequest.prototype.setRequestHeader);\n}\n\nvar ngFileUpload = angular.module('ngFileUpload', []);\n\nngFileUpload.version = '<%= pkg.version %>';\n\nngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {\n  var upload = this;\n  upload.promisesCount = 0;\n\n  this.isResumeSupported = function () {\n    return window.Blob && window.Blob.prototype.slice;\n  };\n\n  var resumeSupported = this.isResumeSupported();\n\n  function sendHttp(config) {\n    config.method = config.method || 'POST';\n    config.headers = config.headers || {};\n\n    var deferred = config._deferred = config._deferred || $q.defer();\n    var promise = deferred.promise;\n\n    function notifyProgress(e) {\n      if (deferred.notify) {\n        deferred.notify(e);\n      }\n      if (promise.progressFunc) {\n        $timeout(function () {\n          promise.progressFunc(e);\n        });\n      }\n    }\n\n    function getNotifyEvent(n) {\n      if (config._start != null && resumeSupported) {\n        return {\n          loaded: n.loaded + config._start,\n          total: (config._file && config._file.size) || n.total,\n          type: n.type, config: config,\n          lengthComputable: true, target: n.target\n        };\n      } else {\n        return n;\n      }\n    }\n\n    if (!config.disableProgress) {\n      config.headers.__setXHR_ = function () {\n        return function (xhr) {\n          if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;\n          config.__XHR = xhr;\n          if (config.xhrFn) config.xhrFn(xhr);\n          xhr.upload.addEventListener('progress', function (e) {\n            e.config = config;\n            notifyProgress(getNotifyEvent(e));\n          }, false);\n          //fix for firefox not firing upload progress end, also IE8-9\n          xhr.upload.addEventListener('load', function (e) {\n            if (e.lengthComputable) {\n              e.config = config;\n              notifyProgress(getNotifyEvent(e));\n            }\n          }, false);\n        };\n      };\n    }\n\n    function uploadWithAngular() {\n      $http(config).then(function (r) {\n          if (resumeSupported && config._chunkSize && !config._finished && config._file) {\n            var fileSize = config._file && config._file.size || 0;\n            notifyProgress({\n                loaded: Math.min(config._end, fileSize),\n                total: fileSize,\n                config: config,\n                type: 'progress'\n              }\n            );\n            upload.upload(config, true);\n          } else {\n            if (config._finished) delete config._finished;\n            deferred.resolve(r);\n          }\n        }, function (e) {\n          deferred.reject(e);\n        }, function (n) {\n          deferred.notify(n);\n        }\n      );\n    }\n\n    if (!resumeSupported) {\n      uploadWithAngular();\n    } else if (config._chunkSize && config._end && !config._finished) {\n      config._start = config._end;\n      config._end += config._chunkSize;\n      uploadWithAngular();\n    } else if (config.resumeSizeUrl) {\n      $http.get(config.resumeSizeUrl).then(function (resp) {\n        if (config.resumeSizeResponseReader) {\n          config._start = config.resumeSizeResponseReader(resp.data);\n        } else {\n          config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());\n        }\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else if (config.resumeSize) {\n      config.resumeSize().then(function (size) {\n        config._start = size;\n        if (config._chunkSize) {\n          config._end = config._start + config._chunkSize;\n        }\n        uploadWithAngular();\n      }, function (e) {\n        throw e;\n      });\n    } else {\n      if (config._chunkSize) {\n        config._start = 0;\n        config._end = config._start + config._chunkSize;\n      }\n      uploadWithAngular();\n    }\n\n\n    promise.success = function (fn) {\n      promise.then(function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.error = function (fn) {\n      promise.then(null, function (response) {\n        fn(response.data, response.status, response.headers, config);\n      });\n      return promise;\n    };\n\n    promise.progress = function (fn) {\n      promise.progressFunc = fn;\n      promise.then(null, null, function (n) {\n        fn(n);\n      });\n      return promise;\n    };\n    promise.abort = promise.pause = function () {\n      if (config.__XHR) {\n        $timeout(function () {\n          config.__XHR.abort();\n        });\n      }\n      return promise;\n    };\n    promise.xhr = function (fn) {\n      config.xhrFn = (function (origXhrFn) {\n        return function () {\n          if (origXhrFn) origXhrFn.apply(promise, arguments);\n          fn.apply(promise, arguments);\n        };\n      })(config.xhrFn);\n      return promise;\n    };\n\n    upload.promisesCount++;\n    if (promise['finally'] && promise['finally'] instanceof Function) {\n      promise['finally'](function () {\n        upload.promisesCount--;\n      });\n    }\n    return promise;\n  }\n\n  this.isUploadInProgress = function () {\n    return upload.promisesCount > 0;\n  };\n\n  this.rename = function (file, name) {\n    file.ngfName = name;\n    return file;\n  };\n\n  this.jsonBlob = function (val) {\n    if (val != null && !angular.isString(val)) {\n      val = JSON.stringify(val);\n    }\n    var blob = new window.Blob([val], {type: 'application/json'});\n    blob._ngfBlob = true;\n    return blob;\n  };\n\n  this.json = function (val) {\n    return angular.toJson(val);\n  };\n\n  function copy(obj) {\n    var clone = {};\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        clone[key] = obj[key];\n      }\n    }\n    return clone;\n  }\n\n  this.isFile = function (file) {\n    return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));\n  };\n\n  this.upload = function (config, internal) {\n    function toResumeFile(file, formData) {\n      if (file._ngfBlob) return file;\n      config._file = config._file || file;\n      if (config._start != null && resumeSupported) {\n        if (config._end && config._end >= file.size) {\n          config._finished = true;\n          config._end = file.size;\n        }\n        var slice = file.slice(config._start, config._end || file.size);\n        slice.name = file.name;\n        slice.ngfName = file.ngfName;\n        if (config._chunkSize) {\n          formData.append('_chunkSize', config._chunkSize);\n          formData.append('_currentChunkSize', config._end - config._start);\n          formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));\n          formData.append('_totalSize', config._file.size);\n        }\n        return slice;\n      }\n      return file;\n    }\n\n    function addFieldToFormData(formData, val, key) {\n      if (val !== undefined) {\n        if (angular.isDate(val)) {\n          val = val.toISOString();\n        }\n        if (angular.isString(val)) {\n          formData.append(key, val);\n        } else if (upload.isFile(val)) {\n          var file = toResumeFile(val, formData);\n          var split = key.split(',');\n          if (split[1]) {\n            file.ngfName = split[1].replace(/^\\s+|\\s+$/g, '');\n            key = split[0];\n          }\n          config._fileKey = config._fileKey || key;\n          formData.append(key, file, file.ngfName || file.name);\n        } else {\n          if (angular.isObject(val)) {\n            if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;\n\n            val.$$ngfCircularDetection = true;\n            try {\n              for (var k in val) {\n                if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {\n                  var objectKey = config.objectKey == null ? '[i]' : config.objectKey;\n                  if (val.length && parseInt(k) > -1) {\n                    objectKey = config.arrayKey == null ? objectKey : config.arrayKey;\n                  }\n                  addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));\n                }\n              }\n            } finally {\n              delete val.$$ngfCircularDetection;\n            }\n          } else {\n            formData.append(key, val);\n          }\n        }\n      }\n    }\n\n    function digestConfig() {\n      config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n      config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n      config.headers = config.headers || {};\n      config.headers['Content-Type'] = undefined;\n      config.transformRequest = config.transformRequest ?\n        (angular.isArray(config.transformRequest) ?\n          config.transformRequest : [config.transformRequest]) : [];\n      config.transformRequest.push(function (data) {\n        var formData = new window.FormData(), key;\n        data = data || config.fields || {};\n        if (config.file) {\n          data.file = config.file;\n        }\n        for (key in data) {\n          if (data.hasOwnProperty(key)) {\n            var val = data[key];\n            if (config.formDataAppender) {\n              config.formDataAppender(formData, key, val);\n            } else {\n              addFieldToFormData(formData, val, key);\n            }\n          }\n        }\n\n        return formData;\n      });\n    }\n\n    if (!internal) config = copy(config);\n    if (!config._isDigested) {\n      config._isDigested = true;\n      digestConfig();\n    }\n\n    return sendHttp(config);\n  };\n\n  this.http = function (config) {\n    config = copy(config);\n    config.transformRequest = config.transformRequest || function (data) {\n        if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {\n          return data;\n        }\n        return $http.defaults.transformRequest[0].apply(this, arguments);\n      };\n    config._chunkSize = upload.translateScalars(config.resumeChunkSize);\n    config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;\n\n    return sendHttp(config);\n  };\n\n  this.translateScalars = function (str) {\n    if (angular.isString(str)) {\n      if (str.search(/kb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1024);\n      } else if (str.search(/mb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1048576);\n      } else if (str.search(/gb/i) === str.length - 2) {\n        return parseFloat(str.substring(0, str.length - 2) * 1073741824);\n      } else if (str.search(/b/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/s/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1));\n      } else if (str.search(/m/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 60);\n      } else if (str.search(/h/i) === str.length - 1) {\n        return parseFloat(str.substring(0, str.length - 1) * 3600);\n      }\n    }\n    return str;\n  };\n\n  this.urlToBlob = function(url) {\n    var defer = $q.defer();\n    $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {\n      var arrayBufferView = new Uint8Array(resp.data);\n      var type = resp.headers('content-type') || 'image/WebP';\n      var blob = new window.Blob([arrayBufferView], {type: type});\n      var matches = url.match(/.*\\/(.+?)(\\?.*)?$/);\n      if (matches.length > 1) {\n        blob.name = matches[1];\n      }\n      defer.resolve(blob);\n    }, function (e) {\n      defer.reject(e);\n    });\n    return defer.promise;\n  };\n\n  this.setDefaults = function (defaults) {\n    this.defaults = defaults || {};\n  };\n\n  this.defaults = {};\n  this.version = ngFileUpload.version;\n}\n\n]);\n"
  },
  {
    "path": "src/validate.js",
    "content": "ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {\n  var upload = UploadDataUrl;\n\n  function globStringToRegex(str) {\n    var regexp = '', excludes = [];\n    if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {\n      regexp = str.substring(1, str.length - 1);\n    } else {\n      var split = str.split(',');\n      if (split.length > 1) {\n        for (var i = 0; i < split.length; i++) {\n          var r = globStringToRegex(split[i]);\n          if (r.regexp) {\n            regexp += '(' + r.regexp + ')';\n            if (i < split.length - 1) {\n              regexp += '|';\n            }\n          } else {\n            excludes = excludes.concat(r.excludes);\n          }\n        }\n      } else {\n        if (str.indexOf('!') === 0) {\n          excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');\n        } else {\n          if (str.indexOf('.') === 0) {\n            str = '*' + str;\n          }\n          regexp = '^' + str.replace(new RegExp('[.\\\\\\\\+*?\\\\[\\\\^\\\\]$(){}=!<>|:\\\\-]', 'g'), '\\\\$&') + '$';\n          regexp = regexp.replace(/\\\\\\*/g, '.*').replace(/\\\\\\?/g, '.');\n        }\n      }\n    }\n    return {regexp: regexp, excludes: excludes};\n  }\n\n  upload.validatePattern = function (file, val) {\n    if (!val) {\n      return true;\n    }\n    var pattern = globStringToRegex(val), valid = true;\n    if (pattern.regexp && pattern.regexp.length) {\n      var regexp = new RegExp(pattern.regexp, 'i');\n      valid = (file.type != null && regexp.test(file.type)) ||\n        (file.name != null && regexp.test(file.name));\n    }\n    var len = pattern.excludes.length;\n    while (len--) {\n      var exclude = new RegExp(pattern.excludes[len], 'i');\n      valid = valid && (file.type == null || exclude.test(file.type)) &&\n        (file.name == null || exclude.test(file.name));\n    }\n    return valid;\n  };\n\n  upload.ratioToFloat = function (val) {\n    var r = val.toString(), xIndex = r.search(/[x:]/i);\n    if (xIndex > -1) {\n      r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));\n    } else {\n      r = parseFloat(r);\n    }\n    return r;\n  };\n\n  upload.registerModelChangeValidator = function (ngModel, attr, scope) {\n    if (ngModel) {\n      ngModel.$formatters.push(function (files) {\n        if (ngModel.$dirty) {\n          var filesArray = files;\n          if (files && !angular.isArray(files)) {\n            filesArray = [files];\n          }\n          upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {\n            upload.applyModelValidation(ngModel, filesArray);\n          });\n        }\n        return files;\n      });\n    }\n  };\n\n  function markModelAsDirty(ngModel, files) {\n    if (files != null && !ngModel.$dirty) {\n      if (ngModel.$setDirty) {\n        ngModel.$setDirty();\n      } else {\n        ngModel.$dirty = true;\n      }\n    }\n  }\n\n  upload.applyModelValidation = function (ngModel, files) {\n    markModelAsDirty(ngModel, files);\n    angular.forEach(ngModel.$ngfValidations, function (validation) {\n      ngModel.$setValidity(validation.name, validation.valid);\n    });\n  };\n\n  upload.getValidationAttr = function (attr, scope, name, validationName, file) {\n    var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);\n    var val = upload.attrGetter(dName, attr, scope, {$file: file});\n    if (val == null) {\n      val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});\n      if (val) {\n        var split = (validationName || name).split('.');\n        val = val[split[0]];\n        if (split.length > 1) {\n          val = val && val[split[1]];\n        }\n      }\n    }\n    return val;\n  };\n\n  upload.validate = function (files, prevLength, ngModel, attr, scope) {\n    ngModel = ngModel || {};\n    ngModel.$ngfValidations = ngModel.$ngfValidations || [];\n\n    angular.forEach(ngModel.$ngfValidations, function (v) {\n      v.valid = true;\n    });\n\n    var attrGetter = function (name, params) {\n      return upload.attrGetter(name, attr, scope, params);\n    };\n\n    var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');\n    var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);\n\n    if (files == null || files.length === 0) {\n      return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});\n    }\n\n    files = files.length === undefined ? [files] : files.slice(0);\n    var invalidFiles = [];\n\n    function validateSync(name, validationName, fn) {\n      if (files) {\n        var i = files.length, valid = null;\n        while (i--) {\n          var file = files[i];\n          if (file) {\n            var val = upload.getValidationAttr(attr, scope, name, validationName, file);\n            if (val != null) {\n              if (!fn(file, val, i)) {\n                if (ignoredErrors.indexOf(name) === -1) {\n                  file.$error = name;\n                  (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n                  file.$errorParam = val;\n                  if (invalidFiles.indexOf(file) === -1) {\n                    invalidFiles.push(file);\n                  }\n                  if (!runAllValidation) {\n                    files.splice(i, 1);\n                  }\n                  valid = false;\n                } else {\n                  files.splice(i, 1);\n                }\n              }\n            }\n          }\n        }\n        if (valid !== null) {\n          ngModel.$ngfValidations.push({name: name, valid: valid});\n        }\n      }\n    }\n\n    validateSync('pattern', null, upload.validatePattern);\n    validateSync('minSize', 'size.min', function (file, val) {\n      return file.size + 0.1 >= upload.translateScalars(val);\n    });\n    validateSync('maxSize', 'size.max', function (file, val) {\n      return file.size - 0.1 <= upload.translateScalars(val);\n    });\n    var totalSize = 0;\n    validateSync('maxTotalSize', null, function (file, val) {\n      totalSize += file.size;\n      if (totalSize > upload.translateScalars(val)) {\n        files.splice(0, files.length);\n        return false;\n      }\n      return true;\n    });\n\n    validateSync('validateFn', null, function (file, r) {\n      return r === true || r === null || r === '';\n    });\n\n    if (!files.length) {\n      return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});\n    }\n\n    function validateAsync(name, validationName, type, asyncFn, fn) {\n      function resolveResult(defer, file, val) {\n        function resolveInternal(fn) {\n          if (fn()) {\n            if (ignoredErrors.indexOf(name) === -1) {\n              file.$error = name;\n              (file.$errorMessages = (file.$errorMessages || {}))[name] = true;\n              file.$errorParam = val;\n              if (invalidFiles.indexOf(file) === -1) {\n                invalidFiles.push(file);\n              }\n              if (!runAllValidation) {\n                var i = files.indexOf(file);\n                if (i > -1) files.splice(i, 1);\n              }\n              defer.resolve(false);\n            } else {\n              var j = files.indexOf(file);\n              if (j > -1) files.splice(j, 1);\n              defer.resolve(true);\n            }\n          } else {\n            defer.resolve(true);\n          }\n        }\n\n        if (val != null) {\n          asyncFn(file, val).then(function (d) {\n            resolveInternal(function () {\n              return !fn(d, val);\n            });\n          }, function () {\n            resolveInternal(function () {\n              return attrGetter('ngfValidateForce', {$file: file});\n            });\n          });\n        } else {\n          defer.resolve(true);\n        }\n      }\n\n      var promises = [upload.emptyPromise(true)];\n      if (files) {\n        files = files.length === undefined ? [files] : files;\n        angular.forEach(files, function (file) {\n          var defer = $q.defer();\n          promises.push(defer.promise);\n          if (type && (file.type == null || file.type.search(type) !== 0)) {\n            defer.resolve(true);\n            return;\n          }\n          if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {\n            upload.imageDimensions(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {\n            upload.mediaDuration(file).then(function (d) {\n              resolveResult(defer, file,\n                attrGetter('ngfDuration', {$file: file, $duration: d}));\n            }, function () {\n              defer.resolve(false);\n            });\n          } else {\n            resolveResult(defer, file,\n              upload.getValidationAttr(attr, scope, name, validationName, file));\n          }\n        });\n      }\n      var deffer = $q.defer();\n      $q.all(promises).then(function (values) {\n        var isValid = true;\n        for (var i = 0; i < values.length; i++) {\n          if (!values[i]) {\n            isValid = false;\n            break;\n          }\n        }\n        ngModel.$ngfValidations.push({name: name, valid: isValid});\n        deffer.resolve(isValid);\n      });\n      return deffer.promise;\n    }\n\n    var deffer = $q.defer();\n    var promises = [];\n\n    promises.push(validateAsync('maxHeight', 'height.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height <= val;\n      }));\n    promises.push(validateAsync('minHeight', 'height.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.height >= val;\n      }));\n    promises.push(validateAsync('maxWidth', 'width.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width <= val;\n      }));\n    promises.push(validateAsync('minWidth', 'width.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return d.width >= val;\n      }));\n    promises.push(validateAsync('dimensions', null, /image/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n    promises.push(validateAsync('ratio', null, /image/,\n      this.imageDimensions, function (d, val) {\n        var split = val.toString().split(','), valid = false;\n        for (var i = 0; i < split.length; i++) {\n          if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {\n            valid = true;\n          }\n        }\n        return valid;\n      }));\n    promises.push(validateAsync('maxRatio', 'ratio.max', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;\n      }));\n    promises.push(validateAsync('minRatio', 'ratio.min', /image/,\n      this.imageDimensions, function (d, val) {\n        return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;\n      }));\n    promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d <= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,\n      this.mediaDuration, function (d, val) {\n        return d >= upload.translateScalars(val);\n      }));\n    promises.push(validateAsync('duration', null, /audio|video/,\n      function (file, val) {\n        return upload.emptyPromise(val);\n      }, function (r) {\n        return r;\n      }));\n\n    promises.push(validateAsync('validateAsyncFn', null, null,\n      function (file, val) {\n        return val;\n      }, function (r) {\n        return r === true || r === null || r === '';\n      }));\n\n    $q.all(promises).then(function () {\n\n      if (runAllValidation) {\n        for (var i = 0; i < files.length; i++) {\n          var file = files[i];\n          if (file.$error) {\n            files.splice(i--, 1);\n          }\n        }\n      }\n\n      runAllValidation = false;\n      validateSync('maxFiles', null, function (file, val, i) {\n        return prevLength + i < val;\n      });\n\n      deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});\n    });\n    return deffer.promise;\n  };\n\n  upload.imageDimensions = function (file) {\n    if (file.$ngfWidth && file.$ngfHeight) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});\n      });\n      return d.promise;\n    }\n    if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('image') !== 0) {\n        deferred.reject('not image');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var img = angular.element('<img>').attr('src', dataUrl)\n          .css('visibility', 'hidden').css('position', 'fixed')\n          .css('max-width', 'none !important').css('max-height', 'none !important');\n\n        function success() {\n          var width = img[0].naturalWidth || img[0].clientWidth;\n          var height = img[0].naturalHeight || img[0].clientHeight;\n          img.remove();\n          file.$ngfWidth = width;\n          file.$ngfHeight = height;\n          deferred.resolve({width: width, height: height});\n        }\n\n        function error() {\n          img.remove();\n          deferred.reject('load error');\n        }\n\n        img.on('load', success);\n        img.on('error', error);\n\n        var secondsCounter = 0;\n        function checkLoadErrorInCaseOfNoCallback() {\n          $timeout(function () {\n            if (img[0].parentNode) {\n              if (img[0].clientWidth) {\n                success();\n              } else if (secondsCounter++ > 10) {\n                error();\n              } else {\n                checkLoadErrorInCaseOfNoCallback();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadErrorInCaseOfNoCallback();\n\n        angular.element(document.getElementsByTagName('body')[0]).append(img);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDimensionPromise = deferred.promise;\n    file.$ngfDimensionPromise['finally'](function () {\n      delete file.$ngfDimensionPromise;\n    });\n    return file.$ngfDimensionPromise;\n  };\n\n  upload.mediaDuration = function (file) {\n    if (file.$ngfDuration) {\n      var d = $q.defer();\n      $timeout(function () {\n        d.resolve(file.$ngfDuration);\n      });\n      return d.promise;\n    }\n    if (file.$ngfDurationPromise) return file.$ngfDurationPromise;\n\n    var deferred = $q.defer();\n    $timeout(function () {\n      if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {\n        deferred.reject('not media');\n        return;\n      }\n      upload.dataUrl(file).then(function (dataUrl) {\n        var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')\n          .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');\n\n        function success() {\n          var duration = el[0].duration;\n          file.$ngfDuration = duration;\n          el.remove();\n          deferred.resolve(duration);\n        }\n\n        function error() {\n          el.remove();\n          deferred.reject('load error');\n        }\n\n        el.on('loadedmetadata', success);\n        el.on('error', error);\n        \n        var count = 0;\n        function checkLoadError() {\n          count++;\n          $timeout(function () {\n            if (el[0].parentNode) {\n              if (el[0].duration) {\n                success();\n              } else if (count++ > 10) {\n                error();\n              } else {\n                checkLoadError();\n              }\n            }\n          }, 1000);\n        }\n\n        checkLoadError();\n\n        angular.element(document.body).append(el);\n      }, function () {\n        deferred.reject('load error');\n      });\n    });\n\n    file.$ngfDurationPromise = deferred.promise;\n    file.$ngfDurationPromise['finally'](function () {\n      delete file.$ngfDurationPromise;\n    });\n    return file.$ngfDurationPromise;\n  };\n  return upload;\n}\n]);\n"
  },
  {
    "path": "test/.bowerrc",
    "content": "{\n    \"directory\": \"bower_components\"\n}\n"
  },
  {
    "path": "test/bower.json",
    "content": "{\n  \"name\": \"web\",\n  \"private\": true,\n  \"dependencies\": {\n    \"chai\": \"~1.8.0\",\n    \"mocha\": \"~1.14.0\"\n  },\n  \"devDependencies\": {}\n}\n"
  },
  {
    "path": "test/index.html",
    "content": "<!doctype html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>Mocha Spec Runner</title>\n    <link rel=\"stylesheet\" href=\"bower_components/mocha/mocha.css\">\n</head>\n<body>\n    <div id=\"mocha\"></div>\n    <script src=\"bower_components/mocha/mocha.js\"></script>\n    <script>mocha.setup('bdd')</script>\n    <script src=\"bower_components/chai/chai.js\"></script>\n    <script>\n        var assert = chai.assert;\n        var expect = chai.expect;\n        var should = chai.should();\n    </script>\n\n    <!-- include source files here... -->\n\n    <!-- include spec files here... -->\n    <script src=\"spec/test.js\"></script>\n\n    <script>mocha.run()</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/spec/test.js",
    "content": "/* global describe, it */\n\n(function () {\n  'use strict';\n\n  describe('Give it some context', function () {\n    describe('maybe a bit more context here', function () {\n      it('should run here few assertions', function () {\n\n      });\n    });\n  });\n})();\n"
  }
]