Codebase list libjs-angular-file-upload / d08053d8-70e6-4d1e-acc9-7a883e59b141/main
New upstream snapshot. Debian Janitor 2 years ago
61 changed file(s) with 826 addition(s) and 21182 deletion(s). Raw diff Collapse all Expand all
+0
-22
.gitignore less more
0 node_modules
1 .tmp
2 bower_components
3 .settings
4 .metadata
5 *.war
6 RemoteSystemsTempFiles/
7 .DS_Store
8 .DS_Store?
9 ehthumbs.db
10 Thumbs.db
11 .Spotlight-V100
12 .Trashes
13 classes/
14 ._*
15 *.jar
16 release-local.sh
17 npm-debug.log
18 .idea/
19 target/
20 *.iml
21
+0
-30
.jshintrc less more
0 {
1 "node": true,
2 "browser": true,
3 "esnext": true,
4 "camelcase": true,
5 "curly": false,
6 "eqeqeq": true,
7 "eqnull": true,
8 "immed": true,
9 "indent": 4,
10 "latedef": true,
11 "newcap": true,
12 "noarg": true,
13 "quotmark": "single",
14 "undef": true,
15 "unused": true,
16 "trailing": true,
17 "smarttabs": true,
18 "jquery": true,
19 "evil": true,
20 "globals": {
21 "angular":false,
22 "FileAPI":false,
23 "ngFileUpload":true,
24 "FormData":true,
25 "Blob":true,
26 "ActiveXObject":false,
27 "$document":false
28 }
29 }
+0
-128
Gruntfile.js less more
0 'use strict';
1
2 module.exports = function (grunt) {
3 // Load grunt tasks automatically
4 require('load-grunt-tasks')(grunt);
5
6 grunt.initConfig({
7 pkg: grunt.file.readJSON('package.json'),
8 concat: {
9 all: {
10 options: {
11 process: function (content) {
12 return grunt.template.process(content);
13 }
14 },
15 files: {
16 'dist/ng-file-upload.js': ['src/upload.js', 'src/model.js', 'src/select.js', 'src/data-url.js',
17 'src/validate.js', 'src/resize.js', 'src/drop.js', 'src/exif.js'],
18 'dist/ng-file-upload-shim.js': ['src/shim-upload.js', 'src/shim-elem.js', 'src/shim-filereader.js'],
19 'dist/ng-file-upload-all.js': ['dist/ng-file-upload-shim.js', 'dist/ng-file-upload.js']
20 }
21 }
22 },
23 uglify: {
24 options: {
25 preserveComments: 'some',
26 banner: '/*! <%= pkg.version %> */\n'
27 },
28
29 build: {
30 files: [{
31 'dist/ng-file-upload.min.js': 'dist/ng-file-upload.js',
32 'dist/ng-file-upload-shim.min.js': 'dist/ng-file-upload-shim.js',
33 'dist/ng-file-upload-all.min.js': 'dist/ng-file-upload-all.js',
34 'dist/FileAPI.min.js': 'dist/FileAPI.js'
35 }]
36 }
37 },
38 copy: {
39 build: {
40 files: [{
41 expand: true,
42 cwd: 'dist/',
43 src: '*',
44 dest: 'demo/src/main/webapp/js/',
45 flatten: true,
46 filter: 'isFile'
47 }]
48 },
49 fileapi: {
50 files: {
51 'dist/FileAPI.flash.swf': 'src/FileAPI.flash.swf',
52 'dist/FileAPI.js': 'src/FileAPI.js'
53 }
54 },
55 bower: {
56 files: [{
57 expand: true,
58 cwd: 'dist/',
59 src: '*',
60 dest: '../angular-file-upload-bower/',
61 flatten: true,
62 filter: 'isFile'
63 }, {
64 expand: true,
65 cwd: 'dist/',
66 src: '*',
67 dest: '../angular-file-upload-shim-bower/',
68 flatten: true,
69 filter: 'isFile'
70 }]
71 }
72 },
73 serve: {
74 options: {
75 port: 9000
76 },
77 'path': 'demo/src/main/webapp'
78 },
79 watch: {
80 js: {
81 files: ['src/{,*/}*.js'],
82 tasks: ['jshint:all', 'concat:all', 'copy:build']
83 }
84 },
85 jshint: {
86 options: {
87 jshintrc: '.jshintrc',
88 reporter: require('jshint-stylish')
89 },
90 all: [
91 'Gruntfile.js',
92 'src/{,*/}*.js',
93 '!src/FileAPI*.*',
94 'test/spec/{,*/}*.js'
95 ]
96 },
97 replace: {
98 version: {
99 src: ['nuget/Package.nuspec', '../angular-file-upload-bower/package.js'],
100 overwrite: true,
101 replacements: [{
102 from: /"version" *: *".*"/g,
103 to: '"version": "<%= pkg.version %>"'
104 }, {
105 from: /<version>.*<\/version>/g,
106 to: '<version><%= pkg.version %></version>'
107 }]
108 }
109 },
110 clean: {
111 dist: {
112 files: [{
113 dot: true,
114 src: [
115 'dist',
116 '!dist/.git*'
117 ]
118 }]
119 }
120 }
121 });
122
123 grunt.registerTask('dev', ['jshint:all', 'concat:all', 'uglify', 'copy:build', 'watch']);
124 grunt.registerTask('default', ['jshint:all', 'clean:dist', 'concat:all',
125 'copy:fileapi', 'uglify', 'copy:build', 'copy:bower', 'replace:version']);
126
127 };
99
1010 Lightweight Angular directive to upload files.
1111
12 **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)
13
14 **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)
12 **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)
13
14 **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)
1515
1616
1717
3030 * [CORS](#cors)
3131 * [Amazon S3 Upload](#s3)
3232
33 ##<a name="features"></a> Features
33 ## <a name="features"></a> Features
3434 * file upload progress, cancel/abort
35 * file drag and drop (html5 only)
36 * image paste form clipboard and drag and drop from browser pages (html5 only).
35 * file drag and drop (html5 only)
36 * image paste from clipboard and drag and drop from browser pages (html5 only).
3737 * 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)
3838 * orientation fix for jpeg image files with exif orientation data
39 * resumable uploads: pause/resume upload (html5 only)
39 * resumable uploads: pause/resume upload (html5 only)
4040 * 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.
4141 * show thumbnail or preview of selected images/audio/videos
4242 * supports CORS and direct upload of file's binary data using `Upload.$http()`
4545 * HTML5 FileReader.readAsDataURL shim for IE8-9
4646 * 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)
4747
48 ##<a name="install"></a> Install
48 ## <a name="install"></a> Install
4949
5050 * <a name="manual"></a>**Manual**: download latest from [here](https://github.com/danialfarid/ng-file-upload-bower/releases/latest)
5151 * <a name="bower"></a>**Bower**:
5959 <script src="ng-file-upload(.min).js"></script>
6060 ```
6161
62 ##<a name="usage"></a> Usage
63
64 ###Samples:
62 ## <a name="usage"></a> Usage
63
64 ### Samples:
6565 * Upload with form submit and validations: [http://jsfiddle.net/danialfarid/maqbzv15/1118/](http://jsfiddle.net/danialfarid/maqbzv15/1118/)
6666 * Upload multiple files one by one on file select:
6767 [http://jsfiddle.net/danialfarid/2vq88rfs/136/](http://jsfiddle.net/danialfarid/2vq88rfs/136/)
8383 <form ng-app="fileUpload" ng-controller="MyCtrl" name="form">
8484 Single Image with validations
8585 <div class="button" ngf-select ng-model="file" name="file" ngf-pattern="'image/*'"
86 ngf-accept="'image/*'" ngf-max-size="20MB" ngf-min-height="100"
86 ngf-accept="'image/*'" ngf-max-size="20MB" ngf-min-height="100"
8787 ngf-resize="{width: 100, height: 100}">Select</div>
8888 Multiple files
8989 <div class="button" ngf-select ng-model="files" ngf-multiple="true">Select</div>
9696 <div class="button" ngf-select="uploadFiles($files)" multiple="multiple">Upload on file select</div>
9797 Drop File:
9898 <div ngf-drop="uploadFiles($files)" class="drop-box"
99 ngf-drag-over-class="'dragover'" ngf-multiple="true"
99 ngf-drag-over-class="'dragover'" ngf-multiple="true"
100100 ngf-pattern="'image/*,application/pdf'">Drop Images or PDFs files here</div>
101101 <div ngf-no-file-drop>File Drag/Drop is not supported for this browser</div>
102102
153153
154154 ```html
155155 <div|button|input type="file"|ngf-select|ngf-drop...
156 ngf-select="" or "upload($files, ...)" // called when files are selected or cleared
157 ngf-drop="" or "upload($files, ...)" // called when files being dropped
158 // You can use ng-model or ngf-change instead of specifying function for ngf-drop and ngf-select
159 // function parameters are the same as ngf-change
156 ngf-select="" or "upload($files, ...)" <!-- called when files are selected or cleared -->
157 ngf-drop="" or "upload($files, ...)" <!-- called when files being dropped
158 You can use ng-model or ngf-change instead of specifying function for ngf-drop and ngf-select
159 function parameters are the same as ngf-change -->
160160 ngf-change="upload($files, $file, $newFiles, $duplicateFiles, $invalidFiles, $event)"
161 // called when files are selected, dropped, or cleared
162 ng-model="myFiles" // binds the valid selected/dropped file or files to the scope model
163 // could be an array or single file depending on ngf-multiple and ngf-keep values.
164 ng-model-options="{updateOn: 'change click drop dropUrl paste', allowInvalid: false, debounce: 0}"
165 // updateOn could be used to disable resetting on click, or updating on paste, browser image drop, etc.
166 // allowInvalid default is false could allow invalid files in the model
167 // debouncing will postpone model update (miliseconds). See angular ng-model-options for more details.
168 ngf-model-invalid="invalidFile(s)" // binds the invalid selected/dropped file or files to this model.
169 ngf-before-model-change="beforeChange($files, ...)" // called after file select/drop and before
170 // model change, validation and resize is processed
171 ng-disabled="boolean" // disables this element
172 ngf-select-disabled="boolean" // default false, disables file select on this element
173 ngf-drop-disabled="boolean" // default false, disables file drop on this element
174 ngf-multiple="boolean" // default false, allows selecting multiple files
175 ngf-keep="true|false|'distinct'" // default false, keep the previous ng-model files and
176 // append the new files. "'distinct'" removes duplicate files
177 // $newFiles and $duplicateFiles are set in ngf-change/select/drop functions.
178 ngf-fix-orientation="boolean" //default false, would rotate the jpeg image files that have
179 // exif orientation data. See #745. Could be a boolean function like shouldFixOrientation($file)
180 // to decide wethere to fix that file or not.
181
182 *ngf-capture="'camera'" or "'other'" // allows mobile devices to capture using camera
183 *ngf-accept="'image/*'" // standard HTML accept attr, browser specific select popup window
184
185 +ngf-allow-dir="boolean" // default true, allow dropping files only for Chrome webkit browser
186 +ngf-include-dir="boolean" //default false, include directories in the dropped file array.
187 //You can detect if they are directory or not by checking the type === 'directory'.
188 +ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}"
161 <!-- called when files are selected, dropped, or cleared -->
162 ng-model="myFiles" <!-- binds the valid selected/dropped file or files to the scope model
163 could be an array or single file depending on ngf-multiple and ngf-keep values. -->
164 ngf-model-options="{updateOn: 'change click drop dropUrl paste', allowInvalid: false, debounce: 0}"
165 <!-- updateOn could be used to disable resetting on click, or updating on paste, browser
166 image drop, etc. allowInvalid default is false could allow invalid files in the model
167 debouncing will postpone model update (miliseconds). See angular ng-model-options for more details. -->
168 ngf-model-invalid="invalidFile(s)" <!-- binds the invalid selected/dropped file or files to this model. -->
169 ngf-before-model-change="beforeChange($files, ...)" <!-- called after file select/drop and before
170 model change, validation and resize is processed -->
171 ng-disabled="boolean" <!-- disables this element -->
172 ngf-select-disabled="boolean" <!-- default false, disables file select on this element -->
173 ngf-drop-disabled="boolean" <!-- default false, disables file drop on this element -->
174 ngf-multiple="boolean" <!-- default false, allows selecting multiple files -->
175 ngf-keep="true|false|'distinct'" <!-- default false, keep the previous ng-model files and
176 append the new files. "'distinct'" removes duplicate files
177 $newFiles and $duplicateFiles are set in ngf-change/select/drop functions. -->
178 ngf-fix-orientation="boolean" <!-- default false, would rotate the jpeg image files that have
179 exif orientation data. See #745. Could be a boolean function like shouldFixOrientation($file)
180 to decide wethere to fix that file or not. -->
181
182 *ngf-capture="'camera'" or "'other'" <!-- allows mobile devices to capture using camera -->
183 *ngf-accept="'image/*'" <!-- standard HTML accept attr, browser specific select popup window -->
184
185 +ngf-allow-dir="boolean" <!-- default true, allow dropping files only for Chrome webkit browser -->
186 +ngf-include-dir="boolean" <!-- default false, include directories in the dropped file array.
187 You can detect if they are directory or not by checking the type === 'directory'. -->
188 +ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}"
189189 or "'myDragOverClass'" or "calcDragOverClass($event)"
190 // default "dragover". drag over css class behaviour. could be a string, a function
191 // returning class name or a json object.
192 // accept/reject class only works in Chrome, validating only the file mime type.
193 // if pattern is not specified ngf-pattern will be used. See following docs for more info.
194 +ngf-drag="drag($isDragging, $class, $event)" // function called on drag over/leave events.
195 // $isDragging: boolean true if is dragging over(dragover), false if drag has left (dragleave)
196 // $class is the class that is being set for the element calculated by ngf-drag-over-class
197 +ngf-drop-available="dropSupported" // set the value of scope model to true or false based on file
198 // drag&drop support for this browser
199 +ngf-stop-propagation="boolean" // default false, whether to propagate drag/drop events.
200 +ngf-hide-on-drop-not-available="boolean" // default false, hides element if file drag&drop is not
201 +ngf-enable-firefox-paste="boolean" // *experimental* default false, enable firefox image paste by making element contenteditable
202
203 ngf-resize="{width: 100, height: 100, quality: .8, type: 'image/jpeg',
204 ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}"
205 or resizeOptions() // a function returning a promise which resolves into the options.
206 // resizes the image to the given width/height or ratio. Quality is optional between 0.1 and 1.0),
207 // type is optional convert it to the given image type format.
208 // centerCrop true will center crop the image if it does not fit within the given width/height or ratio.
209 // centerCrop false (default) will not crop the image and will fit it within the given width/height or ratio
210 // so the resulting image width (or height) could be less than given width (or height).
211 // pattern is to resize only the files that their name or type matches the pattern similar to ngf-pattern.
212 // restoreExif boolean default true, will restore exif info on the resized image.
190 <!-- default "dragover". drag over css class behaviour. could be a string, a function
191 returning class name or a json object.
192 accept/reject class only works in Chrome, validating only the file mime type.
193 if pattern is not specified ngf-pattern will be used. See following docs for more info. -->
194 +ngf-drag="drag($isDragging, $class, $event)" <!-- function called on drag over/leave events.
195 $isDragging: boolean true if is dragging over(dragover), false if drag has left (dragleave)
196 $class is the class that is being set for the element calculated by ngf-drag-over-class -->
197 +ngf-drop-available="dropSupported" <!-- set the value of scope model to true or false
198 based on file drag&drop support for this browser -->
199 +ngf-stop-propagation="boolean" <!-- default false, whether to propagate drag/drop events. -->
200 +ngf-hide-on-drop-not-available="boolean" <!-- default false, hides element if file drag&drop is not -->
201 +ngf-enable-firefox-paste="boolean" <!-- *experimental* default false, enable firefox image paste
202 by making element contenteditable -->
203
204 ngf-resize="{width: 100, height: 100, quality: .8, type: 'image/jpeg',
205 ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}"
206 or resizeOptions() <!-- a function returning a promise which resolves into the options.
207 resizes the image to the given width/height or ratio. Quality is optional between 0.1 and 1.0),
208 type is optional convert it to the given image type format.
209 centerCrop true will center crop the image if it does not fit within the given width/height or ratio.
210 centerCrop false (default) will not crop the image and will fit it within the given width/height or ratio
211 so the resulting image width (or height) could be less than given width (or height).
212 pattern is to resize only the files that their name or type matches the pattern similar to ngf-pattern.
213 restoreExif boolean default true, will restore exif info on the resized image. -->
213214 ngf-resize-if="$width > 1000 || $height > 1000" or "resizeCondition($file, $width, $height)"
214 // apply ngf-resize only if this function returns true. To filter specific images to be resized.
215 ngf-validate-after-resize="boolean" // default false, if true all validation will be run after
216 // the images are being resized, so any validation error before resize will be ignored.
217
218 //validations:
219 ngf-max-files="10" // maximum number of files allowed to be selected or dropped, validate error name: maxFiles
220 ngf-pattern="'.pdf,.jpg,video/*,!.jog'" // comma separated wildcard to filter file names and types allowed
221 // you can exclude specific files by ! at the beginning.
222 // validate error name: pattern
215 <!-- apply ngf-resize only if this function returns true. To filter specific images to be resized. -->
216 ngf-validate-after-resize="boolean" <!-- default false, if true all validation will be run after
217 the images are being resized, so any validation error before resize will be ignored. -->
218
219 <!-- validations: -->
220 ngf-max-files="10" <!-- maximum number of files allowed to be selected or dropped, validate error name: maxFiles -->
221 ngf-pattern="'.pdf,.jpg,video/*,!.jog'" <!-- comma separated wildcard to filter file names and
222 types allowed you can exclude specific files by ! at the beginning. validate error name: pattern -->
223223 ngf-min-size, ngf-max-size, ngf-max-total-size="100" in bytes or "'10KB'" or "'10MB'" or "'10GB'"
224 // validate as form.file.$error.maxSize=true and file.$error='maxSize'
225 // ngf-max-total-size is for multiple file select and validating the total size of all files.
224 <!-- validate as form.file.$error.maxSize=true and file.$error='maxSize'
225 ngf-max-total-size is for multiple file select and validating the total size of all files. -->
226226 ngf-min-height, ngf-max-height, ngf-min-width, ngf-max-width="1000" in pixels only images
227 // validate error name: maxHeight
228 ngf-ratio="8:10,1.6" // list of comma separated valid aspect ratio of images in float or 2:3 format
229 // validate error name: ratio
230 ngf-min-ratio, ngf-max-ratio="8:10" // min or max allowed aspect ratio for the image.
227 <!-- validate error names: minHeight, maxHeight, minWidth, maxWidth -->
228 ngf-ratio="8:10,1.6" <!-- list of comma separated valid aspect ratio of images in float or 2:3 format
229 validate error name: ratio -->
230 ngf-min-ratio, ngf-max-ratio="8:10" <!-- min or max allowed aspect ratio for the image. -->
231231 ngf-dimensions="$width > 1000 || $height > 1000" or "validateDimension($file, $width, $height)"
232 // validate the image dimensions, validate error name: dimensions
232 <!-- validate the image dimensions, validate error name: dimensions -->
233233 ngf-min-duration, ngf-max-duration="100.5" in seconds or "'10s'" or "'10m'" or "'10h'" only audio, video
234 // validate error name: maxDuration
234 <!-- validate error name: maxDuration -->
235235 ngf-duration="$duration > 1000" or "validateDuration($file, $duration)"
236 // validate the media duration, validate error name: duration
236 <!-- validate the media duration, validate error name: duration -->
237237
238238 ngf-validate="{size: {min: 10, max: '20MB'}, width: {min: 100, max:10000}, height: {min: 100, max: 300}
239239 ratio: '2x1', duration: {min: '10s', max: '5m'}, pattern: '.jpg'}"
240 shorthand form for above validations in one place.
241 ngf-validate-fn="validate($file)" // custom validation function, return boolean or string containing the error.
242 // validate error name: validateFn
243 ngf-validate-async-fn="validate($file)" // custom validation function, return a promise that resolve to
244 // boolean or string containing the error. validate error name: validateAsyncFn
245 ngf-validate-force="boolean" // default false, if true file.$error will be set if the dimension or duration
246 // values for validations cannot be calculated for example image load error or unsupported video by the browser.
247 // by default it would assume the file is valid if the duration or dimension cannot be calculated by the browser.
240 <!-- shorthand form for above validations in one place. -->
241 ngf-validate-fn="validate($file)" <!-- custom validation function, return boolean or string
242 containing the error. validate error name: validateFn -->
243 ngf-validate-async-fn="validate($file)" <!-- custom validation function, return a promise that
244 resolve to boolean or string containing the error. validate error name: validateAsyncFn -->
245 ngf-validate-force="boolean" <!-- default false, if true file.$error will be set if
246 the dimension or duration values for validations cannot be calculated for example
247 image load error or unsupported video by the browser. by default it would assume the file
248 is valid if the duration or dimension cannot be calculated by the browser. -->
249 ngf-ignore-invalid="'pattern maxSize'" <!-- ignore the files that fail the specified validations.
250 They will just be ignored and will not show up in ngf-model-invalid or make the form invalid.
251 space separated list of validate error names. -->
252 ngf-run-all-validations="boolean" <!-- default false. Runs all the specified validate directives.
253 By default once a validation fails for a file it would stop running other validations for that file. -->
248254
249255 >Upload/Drop</div>
250256
251257 <div|... ngf-no-file-drop>File Drag/drop is not supported</div>
252258
253 // filter to convert the file to base64 data url.
259 <!-- filter to convert the file to base64 data url. -->
254260 <a href="file | ngfDataUrl">image</a>
255261 ```
256262
257263 #### File preview
258264 ```html
259265 <img|audio|video|div
260 *ngf-src="file" //To preview the selected file, sets src attribute to the file data url.
261 *ngf-background="file" //sets background-image style to the file data url.
262 ngf-resize="{width: 20, height: 20, quality: 0.9}" // only for image resizes the image before setting it
263 // as src or background image. quality is optional.
264 ngf-no-object-url="true or false" // see #887 to force base64 url generation instead of object url. Default false
266 *ngf-src="file" <!-- To preview the selected file, sets src attribute to the file data url. -->
267 *ngf-background="file" <!-- sets background-image style to the file data url. -->
268 ngf-resize="{width: 20, height: 20, quality: 0.9}" <!-- only for image resizes the image
269 before setting it as src or background image. quality is optional. -->
270 ngf-no-object-url="true or false" <!-- see #887 to force base64 url generation instead of
271 object url. Default false -->
265272 >
266273
267274 <div|span|...
268 *ngf-thumbnail="file" //Generates a thumbnail version of the image file
275 *ngf-thumbnail="file" <!-- Generates a thumbnail version of the image file -->
269276 ngf-size="{width: 20, height: 20, quality: 0.9}" the image will be resized to this size
270 // if not specified will be resized to this element`s client width and height.
271 ngf-as-background="boolean" //if true it will set the background image style instead of src attribute.
277 <!-- if not specified will be resized to this element`s client width and height. -->
278 ngf-as-background="boolean" <!-- if true it will set the background image style instead of src attribute. -->
272279 >
273280 ```
274281
288295 *data: {key: file, otherInfo: uploadInfo},
289296 /*
290297 This is to accommodate server implementations expecting nested data object keys in .key or [key] format.
291 Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file
292 data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file */
298 Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file
299 data: {rec: {name: 'N', pic: file}}, objectKey: '.k' sent as: rec.name -> N, rec.pic -> file */
293300 objectKey: '[k]' or '.k' // default is '[k]'
294301 /*
295 This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or
302 This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or
296303 ''(multiple entries with same key) format.
297 Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],...
298 data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/
304 Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],...
305 data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/
299306 arrayKey: '[i]' or '[]' or '.i' or '' //default is '[i]'
300307 method: 'POST' or 'PUT'(html5), default POST,
301308 headers: {'Authorization': 'xxx'}, // only for html5
332339 /* cancel/abort the upload in progress. */
333340 upload.abort();
334341
335 /*
342 /*
336343 alternative way of uploading, send the file binary with the file's content-type.
337344 Could be used to upload files to CouchDB, imgur, etc... html5 FileReader is needed.
338345 This is equivalent to angular $http() but allow you to listen to the progress event for HTML5 browsers.*/
347354 /* Set the default values for ngf-select and ngf-drop directives*/
348355 Upload.setDefaults({ngfMinSize: 20000, ngfMaxSize:20000000, ...})
349356
350 // These two defaults could be decreased if you experience out of memory issues
357 // These two defaults could be decreased if you experience out of memory issues
351358 // or could be increased if your app needs to show many images on the page.
352359 // Each image in ngf-src, ngf-background or ngf-thumbnail is stored and referenced as a blob url
353360 // and will only be released if the max value of the followings is reached.
354361 Upload.defaults.blobUrlsMaxMemory = 268435456 // default max total size of files stored in blob urls.
355362 Upload.defaults.blobUrlsMaxQueueSize = 200 // default max number of blob urls stored by this application.
356363
357 /* Convert a single file or array of files to a single or array of
364 /* Convert a single file or array of files to a single or array of
358365 base64 data url representation of the file(s).
359366 Could be used to send file in base64 format inside json to the databases */
360367 Upload.base64DataUrl(files).then(function(urls){...});
369376 Upload.mediaDuration(file).then(function(durationInSeconds){...});
370377
371378 /* Resizes an image. Returns a promise */
372 //resizeIf(width, height) returns boolean
373 Upload.resize(file, width, height, quality, type, ratio, centerCrop, resizeIf).then(function(resizedFile){...});
379 // options: width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif
380 //resizeIf(width, height) returns boolean. See ngf-resize directive for more details of options.
381 Upload.resize(file, options).then(function(resizedFile){...});
374382
375383 /* returns boolean showing if image resize is supported by this browser*/
376384 Upload.isResizeSupported()
379387
380388 /* returns a file which will be uploaded with the newName instead of original file name */
381389 Upload.rename(file, newName)
382 /* converts the object to a Blob object with application/json content type
390 /* converts the object to a Blob object with application/json content type
383391 for jsob byte streaming support #359 (html5 only)*/
384392 Upload.jsonBlob(obj)
385393 /* converts the value to json to send data as json string. Same as angular.toJson(obj) */
405413 When any of the validation directives specified the form validation will take place and
406414 you can access the value of the validation using `myForm.myFileInputName.$error.<validate error name>`
407415 for example `form.file.$error.pattern`.
408 If multiple file selection is allowed you can specify `ngf-model-invalid="invalidFiles"` to assing the invalid files to
416 If multiple file selection is allowed you can specify `ngf-model-invalid="invalidFiles"` to assing the invalid files to
409417 a model and find the error of each individual file with `file.$error` and description of it with `file.$errorParam`.
410 You can use angular ng-model-options to allow invalid files to be set to the ng-model `ng-model-options="{allowInvalid: true}"`.
411
412 **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
413 send them all in one request . Non-html5 browsers due to flash limitation will upload each file one by one in a separate request.
418 You can use angular ngf-model-options to allow invalid files to be set to the ng-model `ngf-model-options="{allowInvalid: true}"`.
419
420 **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
421 send them all in one request . Non-html5 browsers due to flash limitation will upload each file one by one in a separate request.
414422 You should iterate over the files and send them one by one for a cross browser solution.
415423
416 **drag and drop styling**: For file drag and drop, `ngf-drag-over-class` could be used to style the drop zone.
424 **drag and drop styling**: For file drag and drop, `ngf-drag-over-class` could be used to style the drop zone.
417425 It can be a function that returns a class name based on the $event. Default is "dragover" string.
418 Only in chrome It could be a json object `{accept: 'a', 'reject': 'r', pattern: 'image/*', delay: 10}` that specify the
419 class name for the accepted or rejected drag overs. The `pattern` specified or `ngf-pattern` will be used to validate the file's `mime-type`
420 since that is the only property of the file that is reported by the browser on drag. So you cannot validate
426 Only in chrome It could be a json object `{accept: 'a', 'reject': 'r', pattern: 'image/*', delay: 10}` that specify the
427 class name for the accepted or rejected drag overs. The `pattern` specified or `ngf-pattern` will be used to validate the file's `mime-type`
428 since that is the only property of the file that is reported by the browser on drag. So you cannot validate
421429 the file name/extension, size or other validations on drag. There is also some limitation on some file types which are not reported by Chrome.
422430 `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).
423431
425433 If 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.
426434
427435 **Resumable Uploads:**
428 The plugin supports resumable uploads for large files.
436 The plugin supports resumable uploads for large files.
429437 On your server you need to keep track of what files are being uploaded and how much of the file is uploaded.
430438 * `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.
431 * `resumeSizeUrl` server endpoint to return uploaded file size so far on the server to be able to resume the upload from
432 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
439 * `resumeSizeUrl` server endpoint to return uploaded file size so far on the server to be able to resume the upload from
440 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
433441 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
434442 on the server so you can pass the file name or generated id for the file as a request parameter.<br/>
435 By default it will assume that the response
436 content is an integer or a json object with `size` integer property. If you return other formats from the endpoint you can specify
437 `resumeSizeResponseReader` function to return the size value from the response. Alternatively instead of `resumeSizeUrl` you can use
443 By default it will assume that the response
444 content is an integer or a json object with `size` integer property. If you return other formats from the endpoint you can specify
445 `resumeSizeResponseReader` function to return the size value from the response. Alternatively instead of `resumeSizeUrl` you can use
438446 `resumeSize` function which returns a promise that resolves to the size of the uploaded file so far.
439 Make sure when the file is fully uploaded without any error/abort this endpoint returns zero for the file size
440 if you want to let the user to upload the same file again. Or optionally you could have a restart endpoint to
447 Make sure when the file is fully uploaded without any error/abort this endpoint returns zero for the file size
448 if you want to let the user to upload the same file again. Or optionally you could have a restart endpoint to
441449 set that back to zero to allow re-uploading the same file.
442 * `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
450 * `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
443451 file size limitation and trying to upload the whole request before passing it for internal processing.<br/>
444 If this option is set the requests will have the following extra fields:
445 `_chunkSize`, `_currentChunkSize`, `_chunkNumber` (zero starting), and `_totalSize` to help the server to write the uploaded chunk to
452 If this option is set the requests will have the following extra fields:
453 `_chunkSize`, `_currentChunkSize`, `_chunkNumber` (zero starting), and `_totalSize` to help the server to write the uploaded chunk to
446454 the correct position.
447455 Uploading in chunks could slow down the overall upload time specially if the chunk size is too small.
448456 When you provide `resumeChunkSize` option one of the `resumeSizeUrl` or `resumeSize` is mandatory to know how much of the file is uploaded so far.
449
450
451
452
453 ##<a name="old_browsers"></a> Old browsers
457
458
459
460
461 ## <a name="old_browsers"></a> Old browsers
454462
455463 For browsers not supporting HTML5 FormData (IE8, IE9, ...) [FileAPI](https://github.com/mailru/FileAPI) module is used.
456464 **Note**: You need Flash installed on your browser since `FileAPI` uses Flash to upload files.
482490 * 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)
483491 * Older browsers won't allow `PUT` requests. [#261](https://github.com/danialfarid/ng-file-upload/issues/261)
484492
485 ##<a name="server"></a>Server Side
493 ## <a name="server"></a>Server Side
486494
487495 * <a name="java"></a>**Java**
488496 You 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/)
497505 provided by [Coshx Labs](http://www.coshx.com/).
498506 * **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)
499507 * <a name="php"></a>**PHP**
500 [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)
508 [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)
501509 * <a name="net"></a>**.Net**
502510 * [Demo](https://github.com/stewartm83/angular-fileupload-sample) showing how to use ng-file-upload with Asp.Net Web Api.
503 * 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)
504
505 ##<a name="cors"></a>CORS
511 * 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)
512
513 ## <a name="cors"></a>CORS
506514 To 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:
507515 ([sample java code](https://github.com/danialfarid/ng-file-upload/blob/master/demo/src/main/java/com/df/angularfileupload/CORSFilter.java))
508516 ```java
521529 ```
522530
523531 #### <a name="s3"></a>Amazon AWS S3 Upload
524 For Amazon authentication version 4 [see this comment](https://github.com/danialfarid/ng-file-upload/issues/1128#issuecomment-196203268)
532 For Amazon authentication version 4 [see this comment](https://github.com/danialfarid/ng-file-upload/issues/1128#issuecomment-196203268)
525533
526534 The <a href="https://angular-file-upload.appspot.com/" target="_blank">demo</a> page has an option to upload to S3.
527535 Here is a sample config options:
0 libjs-angular-file-upload (12.0.4+dfsg1-3) UNRELEASED; urgency=medium
0 libjs-angular-file-upload (12.2.13+git20190227.1.a4c4187-1) UNRELEASED; urgency=medium
11
22 * Use secure copyright file specification URI.
33 * Bump debhelper from old 9 to 12.
77 Repository-Browse.
88 * Update Vcs-* headers from URL redirect.
99 * Use canonical URL in Vcs-Git.
10 * New upstream snapshot.
1011
11 -- Debian Janitor <janitor@jelmer.uk> Mon, 06 Jan 2020 10:32:03 +0000
12 -- Debian Janitor <janitor@jelmer.uk> Sun, 26 Sep 2021 04:20:26 -0000
1213
1314 libjs-angular-file-upload (12.0.4+dfsg1-2) unstable; urgency=medium
1415
+0
-34
demo/C#/UploadController.js less more
0 (function () {
1 'use strict';
2
3 angular
4 .module('app')
5 .controller('UploadCtrl', UploadCtrl);
6
7 UploadCtrl.$inject = ['$location', '$upload'];
8
9 function UploadCtrl($location, $upload) {
10 /* jshint validthis:true */
11 var vm = this;
12 vm.title = 'UploadCtrl';
13
14 vm.onFileSelect = function ($files, user) {
15 //$files: an array of files selected, each file has name, size, and type.
16 for (var i = 0; i < $files.length; i++) {
17 var file = $files[i];
18 vm.upload = $upload.upload({
19 url: 'Uploads/UploadHandler.ashx',
20 data: { name: user.Name },
21 file: file, // or list of files ($files) for html5 only
22 }).progress(function (evt) {
23 //console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
24 }).success(function (data, status, headers, config) {
25 alert('Uploaded successfully ' + file.name);
26 }).error(function (err) {
27 alert('Error occured during upload');
28 });
29 }
30 };
31
32 }
33 })();
+0
-1
demo/C#/UploadHandler.ashx less more
0 <%@ WebHandler Language="C#" CodeBehind="UploadHandler.ashx.cs" Class="MyApp.Uploads.UploadHandler" %>
+0
-40
demo/C#/UploadHandler.ashx.cs less more
0 using System;
1 using System.Collections.Generic;
2 using System.Linq;
3 using System.Web;
4
5 namespace MyApp.Uploads
6 {
7 /// <summary>
8 /// Summary description for UploadHandler
9 /// </summary>
10 public class UploadHandler : IHttpHandler
11 {
12
13 public void ProcessRequest(HttpContext context)
14 {
15 if (context.Request.Files.Count > 0)
16 {
17 HttpFileCollection files = context.Request.Files;
18 var userName = context.Request.Form["name"];
19 for (int i = 0; i < files.Count; i++)
20 {
21 HttpPostedFile file = files[i];
22
23 string fname = context.Server.MapPath("Uploads\\" + userName.ToUpper() + "\\" + file.FileName);
24 file.SaveAs(fname);
25 }
26 }
27 context.Response.ContentType = "text/plain";
28 context.Response.Write("File/s uploaded successfully!");
29 }
30
31 public bool IsReusable
32 {
33 get
34 {
35 return false;
36 }
37 }
38 }
39 }
+0
-98
demo/pom.xml less more
0 <?xml version="1.0" encoding="UTF-8"?>
1 <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">
2
3 <modelVersion>4.0.0</modelVersion>
4 <packaging>war</packaging>
5 <version>0.0.1</version>
6 <artifactId>ng-file-upload-demo-server</artifactId>
7 <groupId>com.df.ng-file-upload.demo.server</groupId>
8
9 <properties>
10 <appengine.app.version>1</appengine.app.version>
11 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12 </properties>
13
14 <prerequisites>
15 <maven>3.1.0</maven>
16 </prerequisites>
17
18 <dependencies>
19 <dependency>
20 <groupId>commons-fileupload</groupId>
21 <artifactId>commons-fileupload</artifactId>
22 <version>1.2</version>
23 </dependency>
24
25 <!-- Compile/runtime dependencies -->
26 <dependency>
27 <groupId>com.google.appengine</groupId>
28 <artifactId>appengine-api-1.0-sdk</artifactId>
29 <version>1.9.18</version>
30 </dependency>
31 <dependency>
32 <groupId>com.google.appengine</groupId>
33 <artifactId>appengine-endpoints</artifactId>
34 <version>1.9.18</version>
35 </dependency>
36 <dependency>
37 <groupId>javax.servlet</groupId>
38 <artifactId>servlet-api</artifactId>
39 <version>2.5</version>
40 <scope>provided</scope>
41 </dependency>
42 <dependency>
43 <groupId>javax.inject</groupId>
44 <artifactId>javax.inject</artifactId>
45 <version>1</version>
46 </dependency>
47 </dependencies>
48
49 <build>
50 <!-- for hot reload of the web application-->
51 <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
52 <plugins>
53 <plugin>
54 <groupId>org.apache.maven.plugins</groupId>
55 <version>3.2</version>
56 <artifactId>maven-compiler-plugin</artifactId>
57 <configuration>
58 <source>1.7</source>
59 <target>1.7</target>
60 </configuration>
61 </plugin>
62 <plugin>
63 <groupId>org.apache.maven.plugins</groupId>
64 <artifactId>maven-war-plugin</artifactId>
65 <version>2.6</version>
66 <configuration>
67 <archiveClasses>true</archiveClasses>
68 <webResources>
69 &lt;!&ndash; in order to interpolate version from pom into appengine-web.xml &ndash;&gt;
70 <resource>
71 <directory>${basedir}/src/main/webapp/WEB-INF</directory>
72 <filtering>true</filtering>
73 <targetPath>WEB-INF</targetPath>
74 </resource>
75 </webResources>
76 </configuration>
77 </plugin>
78
79 <plugin>
80 <groupId>com.google.appengine</groupId>
81 <artifactId>appengine-maven-plugin</artifactId>
82 <version>1.9.18</version>
83 <configuration>
84 <enableJarClasses>false</enableJarClasses>
85 <!-- Comment in the below snippet to bind to all IPs instead of just localhost -->
86 <address>0.0.0.0</address>
87 <port>8888</port>
88 <fullScanSeconds>1</fullScanSeconds>
89 <jvmFlags>
90 <jvmFlag>-Xdebug</jvmFlag>
91 <jvmFlag>-Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n</jvmFlag>
92 </jvmFlags>
93 </configuration>
94 </plugin>
95 </plugins>
96 </build>
97 </project>
+0
-38
demo/src/main/java/com/df/angularfileupload/CORSFilter.java less more
0 package com.df.angularfileupload;
1
2 import java.io.IOException;
3
4 import javax.servlet.Filter;
5 import javax.servlet.FilterChain;
6 import javax.servlet.FilterConfig;
7 import javax.servlet.ServletException;
8 import javax.servlet.ServletRequest;
9 import javax.servlet.ServletResponse;
10 import javax.servlet.http.HttpServletRequest;
11 import javax.servlet.http.HttpServletResponse;
12
13 public class CORSFilter implements Filter {
14
15 @Override
16 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
17 ServletException {
18 HttpServletResponse httpResp = (HttpServletResponse) resp;
19 HttpServletRequest httpReq = (HttpServletRequest) req;
20
21 httpResp.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
22 httpResp.setHeader("Access-Control-Allow-Origin", "*");
23 if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) {
24 httpResp.setHeader("Access-Control-Allow-Headers",
25 httpReq.getHeader("Access-Control-Request-Headers"));
26 }
27 chain.doFilter(req, resp);
28 }
29
30 @Override
31 public void init(FilterConfig arg0) throws ServletException {
32 }
33
34 @Override
35 public void destroy() {
36 }
37 }
+0
-159
demo/src/main/java/com/df/angularfileupload/FileUpload.java less more
0 package com.df.angularfileupload;
1
2 import com.google.appengine.repackaged.org.joda.time.LocalDateTime;
3 import org.apache.commons.fileupload.FileItemIterator;
4 import org.apache.commons.fileupload.FileItemStream;
5 import org.apache.commons.fileupload.servlet.ServletFileUpload;
6
7 import javax.servlet.ServletException;
8 import javax.servlet.http.HttpServlet;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import java.io.BufferedReader;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.InputStreamReader;
15 import java.util.Enumeration;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.logging.Logger;
19
20 public class FileUpload extends HttpServlet {
21 private static final long serialVersionUID = -8244073279641189889L;
22 private final Logger log = Logger.getLogger(FileUpload.class.getName());
23
24 class SizeEntry {
25 public int size;
26 public LocalDateTime time;
27 }
28
29 static Map<String, SizeEntry> sizeMap = new ConcurrentHashMap<>();
30 int counter;
31
32 @Override
33 protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
34 try {
35 clearOldValuesInSizeMap();
36
37 String ipAddress = req.getHeader("X-FORWARDED-FOR");
38 if (ipAddress == null) {
39 ipAddress = req.getRemoteAddr();
40 }
41 if (req.getMethod().equalsIgnoreCase("GET")) {
42 if (req.getParameter("restart") != null) {
43 sizeMap.remove(ipAddress + req.getParameter("name"));
44 }
45 SizeEntry entry = sizeMap.get(ipAddress + req.getParameter("name"));
46 res.getWriter().write("{\"size\":" + (entry == null ? 0 : entry.size) + "}");
47 res.setContentType("application/json");
48 return;
49 }
50 req.setCharacterEncoding("utf-8");
51 if (!"OPTIONS".equalsIgnoreCase(req.getMethod()) && req.getParameter("errorCode") != null) {
52 // res.getWriter().write(req.getParameter("errorMessage"));
53 // res.getWriter().flush();
54 res.sendError(Integer.parseInt(req.getParameter("errorCode")), req.getParameter("errorMessage"));
55 return;
56 }
57 StringBuilder sb = new StringBuilder("{\"result\": [");
58
59 if (req.getHeader("Content-Type") != null
60 && req.getHeader("Content-Type").startsWith("multipart/form-data")) {
61 ServletFileUpload upload = new ServletFileUpload();
62
63 FileItemIterator iterator = upload.getItemIterator(req);
64
65 while (iterator.hasNext()) {
66 FileItemStream item = iterator.next();
67 sb.append("{");
68 sb.append("\"fieldName\":\"").append(item.getFieldName()).append("\",");
69 if (item.getName() != null) {
70 sb.append("\"name\":\"").append(item.getName()).append("\",");
71 }
72 if (item.getName() != null) {
73 sb.append("\"size\":\"").append(size(ipAddress + item.getName(), item.openStream())).append("\"");
74 } else {
75 sb.append("\"value\":\"").append(read(item.openStream()).replace("\"", "'")).append("\"");
76 }
77 sb.append("}");
78 if (iterator.hasNext()) {
79 sb.append(",");
80 }
81 }
82 } else {
83 sb.append("{\"size\":\"" + size(ipAddress, req.getInputStream()) + "\"}");
84 }
85
86 sb.append("]");
87 sb.append(", \"requestHeaders\": {");
88 @SuppressWarnings("unchecked")
89 Enumeration<String> headerNames = req.getHeaderNames();
90 while (headerNames.hasMoreElements()) {
91 String header = headerNames.nextElement();
92 sb.append("\"").append(header).append("\":\"").append(req.getHeader(header)).append("\"");
93 if (headerNames.hasMoreElements()) {
94 sb.append(",");
95 }
96 }
97 sb.append("}}");
98 res.setCharacterEncoding("utf-8");
99 res.getWriter().write(sb.toString());
100 } catch (Exception ex) {
101 throw new ServletException(ex);
102 }
103 }
104
105 private void clearOldValuesInSizeMap() {
106 if (counter++ == 100) {
107 for (Map.Entry<String, SizeEntry> entry : sizeMap.entrySet()) {
108 if (entry.getValue().time.isBefore(LocalDateTime.now().minusHours(1))) {
109 sizeMap.remove(entry.getKey());
110 }
111 }
112 counter = 0;
113 }
114 }
115
116 protected int size(String key, InputStream stream) {
117 int length = sizeMap.get(key) == null ? 0 : sizeMap.get(key).size;
118 try {
119 byte[] buffer = new byte[200000];
120 int size;
121 while ((size = stream.read(buffer)) != -1) {
122 length += size;
123 SizeEntry entry = new SizeEntry();
124 entry.size = length;
125 entry.time = LocalDateTime.now();
126 sizeMap.put(key, entry);
127 // for (int i = 0; i < size; i++) {
128 // System.out.print((char) buffer[i]);
129 // }
130 }
131 } catch (IOException e) {
132 throw new RuntimeException(e);
133 }
134 System.out.println(length);
135 return length;
136
137 }
138
139 protected String read(InputStream stream) {
140 StringBuilder sb = new StringBuilder();
141 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
142 try {
143 String line;
144 while ((line = reader.readLine()) != null) {
145 sb.append(line);
146 }
147 } catch (IOException e) {
148 throw new RuntimeException(e);
149 } finally {
150 try {
151 reader.close();
152 } catch (IOException e) {
153 //ignore
154 }
155 }
156 return sb.toString();
157 }
158 }
+0
-43
demo/src/main/java/com/df/angularfileupload/S3Signature.java less more
0 package com.df.angularfileupload;
1
2 import com.google.api.server.spi.IoUtil;
3 import com.google.appengine.repackaged.com.google.common.io.BaseEncoding;
4
5 import javax.crypto.Mac;
6 import javax.crypto.spec.SecretKeySpec;
7 import javax.servlet.ServletException;
8 import javax.servlet.http.HttpServlet;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import java.io.IOException;
12 import java.security.InvalidKeyException;
13 import java.security.NoSuchAlgorithmException;
14
15 public class S3Signature extends HttpServlet {
16 @Override
17 protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
18 String policy_document = IoUtil.readStream(req.getInputStream());
19 System.out.println(policy_document);
20 String policy = BaseEncoding.base64().encode(policy_document.getBytes("UTF-8")).
21 replaceAll("\n","").replaceAll("\r","");
22
23 Mac hmac;
24 try {
25 hmac = Mac.getInstance("HmacSHA1");
26 String aws_secret_key = req.getParameter("aws-secret-key");
27 System.out.println(aws_secret_key);
28
29 hmac.init(new SecretKeySpec(aws_secret_key.getBytes("UTF-8"), "HmacSHA1"));
30 String signature = BaseEncoding.base64().encode(
31 hmac.doFinal(policy.getBytes("UTF-8")))
32 .replaceAll("\n", "");
33 res.setStatus(HttpServletResponse.SC_OK);
34 res.setContentType("application/json");
35 res.getWriter().write("{\"signature\":\"" + signature + "\",\"policy\":\"" + policy + "\"}");
36 } catch (NoSuchAlgorithmException e) {
37 throw new RuntimeException(e);
38 } catch (InvalidKeyException e) {
39 throw new RuntimeException(e);
40 }
41 }
42 }
+0
-16
demo/src/main/resources/META-INF/jdoconfig.xml less more
0 <?xml version="1.0" encoding="utf-8"?>
1 <jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig http://java.sun.com/xml/ns/jdo/jdoconfig_3_0.xsd">
4
5 <persistence-manager-factory name="transactions-optional">
6 <property name="javax.jdo.PersistenceManagerFactoryClass"
7 value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
8 <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
9 <property name="javax.jdo.option.NontransactionalRead" value="true"/>
10 <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
11 <property name="javax.jdo.option.RetainValues" value="true"/>
12 <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
13 <property name="datanucleus.appengine.singletonPMFForName" value="true"/>
14 </persistence-manager-factory>
15 </jdoconfig>
+0
-15
demo/src/main/resources/META-INF/persistence.xml less more
0 <?xml version="1.0" encoding="UTF-8" ?>
1 <persistence xmlns="http://java.sun.com/xml/ns/persistence"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
4 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
5
6 <persistence-unit name="transactions-optional">
7 <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
8 <properties>
9 <property name="datanucleus.NontransactionalRead" value="true"/>
10 <property name="datanucleus.NontransactionalWrite" value="true"/>
11 <property name="datanucleus.ConnectionURL" value="appengine"/>
12 </properties>
13 </persistence-unit>
14 </persistence>
+0
-24
demo/src/main/resources/log4j.properties less more
0 # A default log4j configuration for log4j users.
1 #
2 # To use this configuration, deploy it into your application's WEB-INF/classes
3 # directory. You are also encouraged to edit it as you like.
4
5 # Configure the console as our one appender
6 log4j.appender.A1=org.apache.log4j.ConsoleAppender
7 log4j.appender.A1.layout=org.apache.log4j.PatternLayout
8 log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
9
10 # tighten logging on the DataNucleus Categories
11 log4j.category.DataNucleus.JDO=WARN, A1
12 log4j.category.DataNucleus.Persistence=WARN, A1
13 log4j.category.DataNucleus.Cache=WARN, A1
14 log4j.category.DataNucleus.MetaData=WARN, A1
15 log4j.category.DataNucleus.General=WARN, A1
16 log4j.category.DataNucleus.Utility=WARN, A1
17 log4j.category.DataNucleus.Transaction=WARN, A1
18 log4j.category.DataNucleus.Datastore=WARN, A1
19 log4j.category.DataNucleus.ClassLoading=WARN, A1
20 log4j.category.DataNucleus.Plugin=WARN, A1
21 log4j.category.DataNucleus.ValueGeneration=WARN, A1
22 log4j.category.DataNucleus.Enhancer=WARN, A1
23 log4j.category.DataNucleus.SchemaTool=WARN, A1
+0
-34
demo/src/main/webapp/WEB-INF/appengine-web.xml less more
0 <?xml version="1.0" encoding="utf-8"?>
1 <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
2 <application>angular-file-upload</application>
3 <version>9-0-0</version>
4
5 <!--
6 Allows App Engine to send multiple requests to one instance in parallel:
7 -->
8 <threadsafe>true</threadsafe>
9
10 <!-- Configure java.util.logging -->
11 <system-properties>
12 <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
13 </system-properties>
14
15 <!--
16 HTTP Sessions are disabled by default. To enable HTTP sessions specify:
17
18 <sessions-enabled>true</sessions-enabled>
19
20 It's possible to reduce request latency by configuring your application to
21 asynchronously write HTTP session data to the datastore:
22
23 <async-session-persistence enabled="true" />
24
25 With this feature enabled, there is a very small chance your app will see
26 stale session data. For details, see
27 http://code.google.com/appengine/docs/java/config/appconfig.html#Enabling_Sessions
28 -->
29 <static-files>
30 <exclude path="/*.html" />
31 </static-files>
32
33 </appengine-web-app>
+0
-13
demo/src/main/webapp/WEB-INF/logging.properties less more
0 # A default java.util.logging configuration.
1 # (All App Engine logging is through java.util.logging by default).
2 #
3 # To use this configuration, copy it into your application's WEB-INF
4 # folder and add the following to your appengine-web.xml:
5 #
6 # <system-properties>
7 # <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
8 # </system-properties>
9 #
10
11 # Set the default logging level for all loggers to WARNING
12 .level = WARNING
+0
-44
demo/src/main/webapp/WEB-INF/web.xml less more
0 <?xml version="1.0" encoding="utf-8" standalone="no"?>
1 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5"
3 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
4 <servlet>
5 <servlet-name>file_upload</servlet-name>
6 <servlet-class>com.df.angularfileupload.FileUpload</servlet-class>
7 </servlet>
8 <servlet-mapping>
9 <servlet-name>file_upload</servlet-name>
10 <url-pattern>/upload</url-pattern>
11 </servlet-mapping>
12 <servlet>
13 <servlet-name>s3_sign</servlet-name>
14 <servlet-class>com.df.angularfileupload.S3Signature</servlet-class>
15 </servlet>
16 <servlet-mapping>
17 <servlet-name>s3_sign</servlet-name>
18 <url-pattern>/s3sign</url-pattern>
19 </servlet-mapping>
20 <welcome-file-list>
21 <welcome-file>index.html</welcome-file>
22 </welcome-file-list>
23 <servlet>
24 <servlet-name>SystemServiceServlet</servlet-name>
25 <servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
26 <init-param>
27 <param-name>services</param-name>
28 <param-value />
29 </init-param>
30 </servlet>
31 <servlet-mapping>
32 <servlet-name>SystemServiceServlet</servlet-name>
33 <url-pattern>/_ah/spi/*</url-pattern>
34 </servlet-mapping>
35 <filter>
36 <filter-name>cors_filter</filter-name>
37 <filter-class>com.df.angularfileupload.CORSFilter</filter-class>
38 </filter>
39 <filter-mapping>
40 <filter-name>cors_filter</filter-name>
41 <url-pattern>*</url-pattern>
42 </filter-mapping>
43 </web-app>
+0
-222
demo/src/main/webapp/common.css less more
0 body {
1 font-family: Helvetica, arial, freesans, clean, sans-serif;
2 }
3
4 /* object {
5 border: 3px solid red;
6 } */
7
8 .upload-buttons input[type="file"] {
9 width: 6.3em \0/ IE9;
10 }
11
12 .upload-button {
13 Height: 26px;
14 line-height: 30px;
15 padding: 0 10px;
16 background: #CCC;
17 appearance: button;
18 -moz-appearance: button; /* Firefox */
19 -webkit-appearance: button; /* Safari and Chrome */
20 position: relative;
21 text-align: center;
22 top: 7px;
23 cursor: pointer;
24 }
25
26 .sel-file {
27 padding: 1px 5px;
28 font-size: smaller;
29 color: grey;
30 }
31
32 .response {
33 padding: 0;
34 padding-top: 10px;
35 margin: 3px 0;
36 clear: both;
37 list-style: none;
38 }
39
40 .response .sel-file li, .response .reqh {
41 color: blue;
42 padding-bottom: 5px;
43 }
44
45 fieldset {
46 border: 1px solid #DDD;
47 width: 620px;
48 padding: 10px;
49 line-height: 23px;
50 }
51
52 fieldset label {
53 /*font-size: smaller;*/
54 }
55
56 .progress {
57 display: inline-block;
58 width: 100px;
59 border: 3px groove #CCC;
60 }
61
62 .progress div {
63 font-size: smaller;
64 background: orange;
65 width: 0;
66 }
67
68 .drop-box {
69 background: #F8F8F8;
70 border: 5px dashed #DDD;
71 width: 170px;
72 text-align: center;
73 padding: 50px 10px;
74 margin-left: 10px;
75 }
76
77 .up-buttons {
78 float: right;
79 }
80
81 .drop-box.dragover {
82 border: 5px dashed blue;
83 }
84
85 .drop-box.dragover-err {
86 border: 5px dashed red;
87 }
88
89 /* for IE*/
90 .js-fileapi-wrapper {
91 display: inline-block;
92 vertical-align: middle;
93 }
94
95 button {
96 padding: 1px 5px;
97 font-size: smaller;
98 margin: 0 3px;
99 }
100
101 .ng-v {
102 float: right;
103 }
104
105 .thumb {
106 float: left;
107 width: 18px;
108 height: 18px;
109 padding-right: 10px;
110 }
111
112 form .thumb {
113 width: 24px;
114 height: 24px;
115 float: none;
116 position: relative;
117 top: 7px;
118 }
119
120 form .progress {
121 line-height: 15px;
122 }
123
124 .edit-area {
125 font-size: 14px;
126 background: black;
127 color: #f9f9f9;
128 padding: 5px 1px;
129 }
130
131 #htmlEdit {
132 margin-bottom: 25px;
133 }
134
135 .edit-div {
136 font-size: smaller;
137 }
138
139 .CodeMirror {
140 font-size: 14px;
141 border: 1px solid #ccc;
142 margin-bottom: 15px;
143 }
144
145 form button {
146 padding: 3px 10px;
147 font-weight: bold;
148 margin-top: 10px;
149 }
150
151 .sub {
152 font-size: smaller;
153 color: #777;
154 padding-top: 5px;
155 padding-left: 25px;
156 }
157
158 .err {
159 font-size: 12px;
160 color: #C53F00;
161 margin: 15px;
162 padding: 15px;
163 background-color: #F0F0F0;
164 border: 1px solid black;
165 }
166
167 .s3 {
168 font-size: smaller;
169 color: #333;
170 margin-left: 20px;
171 }
172
173 .s3 fieldset {
174 border: 1px solid #AAA;
175 }
176
177 .s3 label {
178 width: 180px;
179 display: inline-block;
180 }
181
182 .s3 input {
183 width: 300px;
184 }
185
186 .s3 .helper {
187 margin-left: 5px;
188 }
189
190 .howto {
191 margin-left: 10px;
192 line-height: 20px;
193 }
194
195 .server {
196 margin-bottom: 20px;
197 }
198
199 .srv-title {
200 font-weight: bold;
201 padding: 5px 0 10px 0;
202 }
203
204 :not(output):-moz-ui-invalid {
205 box-shadow: none;
206 }
207
208 .preview {
209 clear: both;
210 }
211
212 .preview img, .preview audio, .preview video {
213 max-width: 300px;
214 max-height: 150px;
215 float: right;
216 }
217
218 .custom {
219 font-size: 14px;
220 margin-left: 20px;
221 }
+0
-8
demo/src/main/webapp/crossdomain.xml less more
0 <?xml version="1.0" ?>
1 <cross-domain-policy>
2 <site-control permitted-cross-domain-policies="all" />
3 <allow-access-from domain="*" secure="false" />
4 <allow-access-from domain="angular-file-upload-cors.appspot.com" />
5 <allow-access-from domain="angular-file-upload-cors-srv.appspot.com" />
6 <allow-http-request-headers-from domain="*" headers="*" secure="false" />
7 </cross-domain-policy>
+0
-27
demo/src/main/webapp/donate.html less more
0 <!doctype html>
1 <html>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
4 <link type="text/css" rel="stylesheet" href="common.css">
5 <title>Angular file upload donate</title>
6 </head>
7
8 <body>
9 <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top" id="frm">
10 <input type="hidden" name="cmd" value="_donations">
11 <input type="hidden" name="business" value="danial.farid@gmail.com">
12 <input type="hidden" name="lc" value="CA">
13 <input type="hidden" name="item_name" value="angular-file-upload plugin">
14 <input type="hidden" name="no_note" value="0">
15 <input type="hidden" name="currency_code" value="USD">
16 <input type="hidden" name="bn" value="PP-DonationsBF:tea.jpg:NonHostedGuest">
17 <input type="image" src="img/tea.jpg" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
18 <img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
19 </form>
20 <script>
21 window.onload = function() {
22 document.getElementById("frm").submit();
23 }
24 </script>
25 </body>
26 </html>
demo/src/main/webapp/favicon.ico less more
Binary diff not shown
demo/src/main/webapp/img/tea.jpg less more
Binary diff not shown
demo/src/main/webapp/img/tea.png less more
Binary diff not shown
+0
-263
demo/src/main/webapp/index.html less more
0 <!doctype html>
1 <html>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
4 <meta name="viewport" content="width=device-width">
5 <link type="text/css" rel="stylesheet" href="common.css">
6 <title>Angular file upload sample</title>
7
8 <script type="text/javascript">
9 FileAPI = {
10 debug: true,
11 //forceLoad: true, html5: false //to debug flash in HTML5 browsers
12 //wrapInsideDiv: true, //experimental for fixing css issues
13 //only one of jsPath or jsUrl.
14 //jsPath: '/js/FileAPI.min.js/folder/',
15 //jsUrl: 'yourcdn.com/js/FileAPI.min.js',
16
17 //only one of staticPath or flashUrl.
18 //staticPath: '/flash/FileAPI.flash.swf/folder/'
19 //flashUrl: 'yourcdn.com/js/FileAPI.flash.swf'
20 };
21 </script>
22
23 <!-- <script src="//code.jquery.com/jquery-1.9.0.min.js"></script> -->
24 <script type="text/javascript">
25 // load angularjs specific version
26 var angularVersion = window.location.hash.substring(1);
27 if (angularVersion.indexOf('/') == 0) angularVersion = angularVersion.substring(1);
28 document.write('<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/' +
29 (angularVersion || '1.2.24') + '/angular.js"><\/script>');
30 </script>
31 <script src="js/ng-file-upload-shim.js"></script>
32 <script src="js/ng-file-upload.js"></script>
33 <script src="js/upload.js"></script>
34
35 <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.css">
36 <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.js"></script>
37 <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/htmlmixed/htmlmixed.min.js"></script>
38 <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/htmlembedded/htmlembedded.min.js"></script>
39 <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/xml/xml.min.js"></script>
40 <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/mode/javascript/javascript.min.js"></script>
41 </head>
42
43 <body ng-app="fileUpload" ng-controller="MyCtrl">
44 <div class="ng-v">
45 Angular Version: <input type="text" ng-model="angularVersion">
46 <input type="button" data-ng-click="changeAngularVersion()" value="Go">
47 </div>
48 <h2>Angular file upload Demo</h2>
49
50 <h3>
51 Visit <a href="https://github.com/danialfarid/ng-file-upload">ng-file-upload</a> on github
52 </h3>
53
54 <div class="upload-div">
55 <div class="edit-div">
56 <a ng-click="showEdit = !showEdit" href="javascript:void(0)">+ edit upload html</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
57 <a ng-show="showEdit" ng-click="confirm() && (editHtml = defaultHtml)" href="javascript:void(0)">reset to
58 default</a><br/><br/>
59
60 <div ng-show="showEdit" id="htmlEdit"></div>
61 </div>
62
63 <div class="upload-buttons">
64 <div id="editArea">
65 <div>
66 <div>
67 <form name="myForm">
68 <fieldset>
69 aaaa{{d}}
70 <legend>Upload on form submit</legend>
71 Username: <input type="text" name="userName" ng-model="username" size="39" required>
72 <i ng-show="myForm.userName.$error.required">*required</i><br>
73 Profile Picture: <input type="file" ngf-select ng-model="picFile" name="file" ngf-accept="'image/*'" required>
74 <i ng-show="myForm.file.$error.required">*required</i>
75 <br/>
76
77 <button ng-disabled="!myForm.$valid" ng-click="uploadPic(picFile)">Submit</button>
78 <img ngf-src="picFile" class="thumb">
79 <span class="progress" ng-show="picFile.progress >= 0">
80 <div style="width:{{picFile.progress}}%" ng-bind="picFile.progress + '%'"></div>
81 </span>
82 <span ng-show="picFile.result">Upload Successful</span>
83 </fieldset>
84 <br/>
85 </form>
86 </div>
87 <fieldset>
88 <legend>Play with options</legend>
89 <div class="up-buttons">
90 <div ngf-select ngf-drop ng-model="files" ngf-model-invalid="invalidFiles"
91 ng-model-options="modelOptionsObj"
92 ngf-multiple="multiple" ngf-pattern="pattern" ngf-accept="acceptSelect"
93 ng-disabled="disabled" ngf-capture="capture"
94 ngf-drag-over-class="dragOverClassObj"
95 ngf-validate="validateObj"
96 ngf-resize="resizeObj"
97 ngf-resize-if="resizeIfFn($file, $width, $height)"
98 ngf-dimensions="dimensionsFn($file, $width, $height)"
99 ngf-duration="durationFn($file, $duration)"
100 ngf-keep="keepDistinct ? 'distinct' : keep"
101 ngf-fix-orientation="orientation"
102 ngf-allow-dir="allowDir" class="drop-box" ngf-drop-available="dropAvailable">Select File
103 <span ng-show="dropAvailable"> or Drop File</span>
104 </div>
105 <br/>
106 <div ngf-drop ng-model="files" ngf-model-invalid="invalidFiles"
107 ng-model-options="modelOptionsObj"
108 ngf-multiple="multiple" ngf-pattern="'image/*'"
109 ng-disabled="disabled"
110 ngf-drag-over-class="dragOverClassObj"
111 ngf-validate="validateObj"
112 ngf-resize="resizeObj"
113 ngf-resize-if="resizeIfFn($file, $width, $height)"
114 ngf-dimensions="dimensionsFn($file, $width, $height)"
115 ngf-duration="durationFn($file, $duration)"
116 ngf-keep="keepDistinct ? 'distinct' : keep"
117 ngf-enable-firefox-paste="true"
118 ngf-fix-orientation="orientation"
119 ngf-allow-dir="allowDir" class="drop-box" ng-show="dropAvailable">
120 <span>Paste or Drop Image from browser</span></div>
121 </div>
122 <div class="custom">
123 <label>accept (for select browser dependent): <input type="text" ng-model="acceptSelect"></label><br/>
124 <label>ngf-capture (for mobile): <input type="text" ng-model="capture"></label><br/>
125 <label>ngf-pattern (validate file model): <input type="text" ng-model="pattern"></label><br/>
126 <label>ngf-validate: <input type="text" ng-model="validate" size="49"></label><br/>
127 <label>ngf-drag-over-class (chrome): <input size="31" type="text" ng-model="dragOverClass"></label><br/>
128 <label>ng-model-options: <input type="text" size="43" ng-model="modelOptions"></label><br/>
129 <label>ngf-resize: <input type="text" size="43" ng-model="resize"></label><br/>
130 <label>ngf-resize-if: <input type="text" size="43" ng-model="resizeIf"></label><br/>
131 <label>ngf-dimensions: <input type="text" size="43" ng-model="dimensions"></label><br/>
132 <label>ngf-duration: <input type="text" size="43" ng-model="duration"></label><br/>
133 <label><input type="checkbox" ng-model="multiple">ngf-multiple (allow multiple files)</label>
134 <label><input type="checkbox" ng-model="disabled">ng-disabled</label><br/>
135 <label><input type="checkbox" ng-model="allowDir">ngf-allow-dir (allow directory drop Chrome
136 only)</label><br/>
137 <label><input type="checkbox" ng-model="keep">ngf-keep (keep the previous model values in
138 ng-model)</label><br/>
139 <label><input type="checkbox" ng-model="keepDistinct">ngf-keep="distinct" (do not allow
140 duplicates)</label><br/>
141 <label><input type="checkbox" ng-model="orientation">ngf-fix-orientation (for exif jpeg files)</label><br/>
142 <label>Upload resumable chunk size: <input type="text" ng-model="chunkSize"></label><br/>
143 </div>
144
145 <div class="preview">
146 <div>Preview image/audio/video:</div>
147 <img ngf-src="!files[0].$error && files[0]">
148 <audio controls ngf-src="!files[0].$error && files[0]"></audio>
149 <video controls ngf-src="!files[0].$error && files[0]"></video>
150 </div>
151 </fieldset>
152 <br/>
153 </div>
154 </div>
155 </div>
156 <ul style="clear:both" class="response">
157 <li class="sel-file" ng-repeat="f in files">
158 <div>
159 <img ngf-thumbnail="!f.$error && f" class="thumb">
160 <span class="progress" ng-show="f.progress >= 0">
161 <div style="width:{{f.progress}}%">{{f.progress}}%</div>
162 </span>
163 <button class="button" ng-click="f.upload.abort();f.upload.aborted=true"
164 ng-show="f.upload != null && f.progress < 100 && !f.upload.aborted">
165 Abort<span ng-show="isResumeSupported">/Pause</span>
166 </button>
167 <button class="button" ng-click="upload(f, true);f.upload.aborted=false"
168 ng-show="isResumeSupported && f.upload != null && f.upload.aborted">Resume
169 </button>
170 <button class="button" ng-click="restart(f);f.upload.aborted=false"
171 ng-show="isResumeSupported && f.upload != null && (f.progress == 100 || f.upload.aborted)">Restart
172 </button>
173 {{f.name}} - size: {{f.size}}B - type: {{f.type}}
174 <a ng-show="f.result" href="javascript:void(0)" ng-click="f.showDetail = !f.showDetail">details</a>
175
176 <div ng-show="f.showDetail">
177 <br/>
178
179 <div data-ng-show="f.result.result == null">{{f.result}}</div>
180 <ul>
181 <li ng-repeat="item in f.result.result">
182 <div data-ng-show="item.name">file name: {{item.name}}</div>
183 <div data-ng-show="item.fieldName">name: {{item.fieldName}}</div>
184 <div data-ng-show="item.size">size on the serve: {{item.size}}</div>
185 <div data-ng-show="item.value">value: {{item.value}}</div>
186 </li>
187 </ul>
188 <div data-ng-show="f.result.requestHeaders" class="reqh">request headers: {{f.result.requestHeaders}}</div>
189 </div>
190 </div>
191 </li>
192 <li class="sel-file" ng-repeat="f in invalidFiles">
193 <div>Invalid File: {{f.$error}} {{f.$errorParam}}, {{f.name}} - size: {{f.size}}B - type:
194 {{f.type}}
195 </div>
196 </li>
197 </ul>
198
199 <br/>
200
201 <div style="clear:both" class="err" ng-show="errorMsg != null">{{errorMsg}}</div>
202 </div>
203
204 <div style="clear:both" class="server">
205 <div class="srv-title">How to upload to the server:</div>
206
207 <div class="howto">
208 <label><input type="radio" name="howToSend" ng-model="howToSend" value="1" ng-init="howToSend = 1">Upload.upload():
209 multipart/form-data upload cross browser</label>
210 <br/>
211 <label><input type="radio" name="howToSend" ng-model="howToSend" value="2"
212 ng-disabled="usingFlash">Upload.http(): binary content with file's
213 Content-Type</label>
214
215 <div class="sub">Can be used to upload files directory into <a
216 href="https://github.com/danialfarid/angular-file-upload/issues/88">CouchDB</a>,
217 <a href="https://github.com/danialfarid/angular-file-upload/issues/87">imgur</a>, etc... without multipart form
218 data (HTML5 FileReader browsers only)<br/>
219 </div>
220 <label><input type="radio" name="howToSend" ng-model="howToSend" value="3">Amazon S3 bucket upload</label>
221
222 <form ng-show="howToSend==3" class="s3" id="s3form" action="http://localhost:8888" onsubmit="return false">
223 <br/>
224 Provide S3 upload parameters. <a href="http://aws.amazon.com/articles/1434/" target="_blank">Click here for
225 detailed documentation</a><br/></br>
226 <label>S3 upload url including bucket name:</label> <input type="text" ng-model="s3url"> <br/>
227 <label>AWSAccessKeyId:</label> <input type="Text" name="AWSAccessKeyId" ng-model="AWSAccessKeyId"> <br/>
228 <label>acl (private or public):</label> <input type="text" ng-model="acl" value="private"><br/>
229 <label>success_action_redirect:</label> <input type="text" ng-model="success_action_redirect"><br/>
230 <label>policy:</label> <input type="text" ng-model="policy"><br/>
231 <label>signature:</label> <input type="text" ng-model="signature"><br/>
232 <br/>
233 <button ng-click="showHelper=!showHelper">S3 Policy signing helper (Optional)</button>
234 <br/><br/>
235
236 <div class="helper" ng-show="showHelper">
237 If you don't have your policy and signature you can use this tool to generate them by providing these two fields
238 and clicking on sign<br/>
239 <label>AWS Secret Key:</label> <input type="text" ng-model="AWSSecretKey"><br/>
240 <label>JSON policy:</label><br/> <textarea type="text" ng-model="jsonPolicy" rows=10 cols=70></textarea>
241 <button ng-click="generateSignature()">Sign</button>
242 </div>
243 </form>
244 <br/>
245 <br/>
246
247 <div ng-show="howToSend != 3">
248 <input type="checkbox" ng-model="generateErrorOnServer">Return server error with http code: <input type="text"
249 ng-model="serverErrorCode"
250 size="5"> and
251 message: <input type="text" ng-model="serverErrorMsg">
252 <br/>
253 </div>
254 <br/>
255 </div>
256 </div>
257 <a style="position:fixed;bottom:28px;right:10px;font-size:smaller;" target="_blank"
258 href="https://angular-file-upload.appspot.com/donate.html">donate</a>
259 <a style="position:fixed;bottom:45px;right:10px;font-size:smaller;"
260 href="https://github.com/danialfarid/angular-file-upload/issues/new">Feedback/Issues</a>
261 </body>
262 </html>
+0
-4313
demo/src/main/webapp/js/FileAPI.js less more
0 /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git
1 * 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.
2 */
3
4 /*
5 * JavaScript Canvas to Blob 2.0.5
6 * https://github.com/blueimp/JavaScript-Canvas-to-Blob
7 *
8 * Copyright 2012, Sebastian Tschan
9 * https://blueimp.net
10 *
11 * Licensed under the MIT license:
12 * http://www.opensource.org/licenses/MIT
13 *
14 * Based on stackoverflow user Stoive's code snippet:
15 * http://stackoverflow.com/q/4998908
16 */
17
18 /*jslint nomen: true, regexp: true */
19 /*global window, atob, Blob, ArrayBuffer, Uint8Array */
20
21 (function (window) {
22 'use strict';
23 var CanvasPrototype = window.HTMLCanvasElement &&
24 window.HTMLCanvasElement.prototype,
25 hasBlobConstructor = window.Blob && (function () {
26 try {
27 return Boolean(new Blob());
28 } catch (e) {
29 return false;
30 }
31 }()),
32 hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
33 (function () {
34 try {
35 return new Blob([new Uint8Array(100)]).size === 100;
36 } catch (e) {
37 return false;
38 }
39 }()),
40 BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
41 window.MozBlobBuilder || window.MSBlobBuilder,
42 dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
43 window.ArrayBuffer && window.Uint8Array && function (dataURI) {
44 var byteString,
45 arrayBuffer,
46 intArray,
47 i,
48 mimeString,
49 bb;
50 if (dataURI.split(',')[0].indexOf('base64') >= 0) {
51 // Convert base64 to raw binary data held in a string:
52 byteString = atob(dataURI.split(',')[1]);
53 } else {
54 // Convert base64/URLEncoded data component to raw binary data:
55 byteString = decodeURIComponent(dataURI.split(',')[1]);
56 }
57 // Write the bytes of the string to an ArrayBuffer:
58 arrayBuffer = new ArrayBuffer(byteString.length);
59 intArray = new Uint8Array(arrayBuffer);
60 for (i = 0; i < byteString.length; i += 1) {
61 intArray[i] = byteString.charCodeAt(i);
62 }
63 // Separate out the mime component:
64 mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
65 // Write the ArrayBuffer (or ArrayBufferView) to a blob:
66 if (hasBlobConstructor) {
67 return new Blob(
68 [hasArrayBufferViewSupport ? intArray : arrayBuffer],
69 {type: mimeString}
70 );
71 }
72 bb = new BlobBuilder();
73 bb.append(arrayBuffer);
74 return bb.getBlob(mimeString);
75 };
76 if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
77 if (CanvasPrototype.mozGetAsFile) {
78 CanvasPrototype.toBlob = function (callback, type, quality) {
79 if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
80 callback(dataURLtoBlob(this.toDataURL(type, quality)));
81 } else {
82 callback(this.mozGetAsFile('blob', type));
83 }
84 };
85 } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
86 CanvasPrototype.toBlob = function (callback, type, quality) {
87 callback(dataURLtoBlob(this.toDataURL(type, quality)));
88 };
89 }
90 }
91 window.dataURLtoBlob = dataURLtoBlob;
92 })(window);
93
94 /*jslint evil: true */
95 /*global window, URL, webkitURL, ActiveXObject */
96
97 (function (window, undef){
98 'use strict';
99
100 var
101 gid = 1,
102 noop = function (){},
103
104 document = window.document,
105 doctype = document.doctype || {},
106 userAgent = window.navigator.userAgent,
107
108 // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
109 apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
110
111 Blob = window.Blob,
112 File = window.File,
113 FileReader = window.FileReader,
114 FormData = window.FormData,
115
116
117 XMLHttpRequest = window.XMLHttpRequest,
118 jQuery = window.jQuery,
119
120 html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
121 && !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
122
123 cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
124
125 chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
126
127 // https://github.com/blueimp/JavaScript-Canvas-to-Blob
128 dataURLtoBlob = window.dataURLtoBlob,
129
130
131 _rimg = /img/i,
132 _rcanvas = /canvas/i,
133 _rimgcanvas = /img|canvas/i,
134 _rinput = /input/i,
135 _rdata = /^data:[^,]+,/,
136
137 _toString = {}.toString,
138
139
140 Math = window.Math,
141
142 _SIZE_CONST = function (pow){
143 pow = new window.Number(Math.pow(1024, pow));
144 pow.from = function (sz){ return Math.round(sz * this); };
145 return pow;
146 },
147
148 _elEvents = {}, // element event listeners
149 _infoReader = [], // list of file info processors
150
151 _readerEvents = 'abort progress error load loadend',
152 _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
153
154 currentTarget = 'currentTarget', // for minimize
155 preventDefault = 'preventDefault', // and this too
156
157 _isArray = function (ar) {
158 return ar && ('length' in ar);
159 },
160
161 /**
162 * Iterate over a object or array
163 */
164 _each = function (obj, fn, ctx){
165 if( obj ){
166 if( _isArray(obj) ){
167 for( var i = 0, n = obj.length; i < n; i++ ){
168 if( i in obj ){
169 fn.call(ctx, obj[i], i, obj);
170 }
171 }
172 }
173 else {
174 for( var key in obj ){
175 if( obj.hasOwnProperty(key) ){
176 fn.call(ctx, obj[key], key, obj);
177 }
178 }
179 }
180 }
181 },
182
183 /**
184 * Merge the contents of two or more objects together into the first object
185 */
186 _extend = function (dst){
187 var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
188 for( ; i < args.length; i++ ){
189 _each(args[i], _ext);
190 }
191 return dst;
192 },
193
194 /**
195 * Add event listener
196 */
197 _on = function (el, type, fn){
198 if( el ){
199 var uid = api.uid(el);
200
201 if( !_elEvents[uid] ){
202 _elEvents[uid] = {};
203 }
204
205 var isFileReader = (FileReader && el) && (el instanceof FileReader);
206 _each(type.split(/\s+/), function (type){
207 if( jQuery && !isFileReader){
208 jQuery.event.add(el, type, fn);
209 } else {
210 if( !_elEvents[uid][type] ){
211 _elEvents[uid][type] = [];
212 }
213
214 _elEvents[uid][type].push(fn);
215
216 if( el.addEventListener ){ el.addEventListener(type, fn, false); }
217 else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
218 else { el['on'+type] = fn; }
219 }
220 });
221 }
222 },
223
224
225 /**
226 * Remove event listener
227 */
228 _off = function (el, type, fn){
229 if( el ){
230 var uid = api.uid(el), events = _elEvents[uid] || {};
231
232 var isFileReader = (FileReader && el) && (el instanceof FileReader);
233 _each(type.split(/\s+/), function (type){
234 if( jQuery && !isFileReader){
235 jQuery.event.remove(el, type, fn);
236 }
237 else {
238 var fns = events[type] || [], i = fns.length;
239
240 while( i-- ){
241 if( fns[i] === fn ){
242 fns.splice(i, 1);
243 break;
244 }
245 }
246
247 if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
248 else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
249 else { el['on'+type] = null; }
250 }
251 });
252 }
253 },
254
255
256 _one = function(el, type, fn){
257 _on(el, type, function _(evt){
258 _off(el, type, _);
259 fn(evt);
260 });
261 },
262
263
264 _fixEvent = function (evt){
265 if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
266 if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
267 return evt;
268 },
269
270
271 _supportInputAttr = function (attr){
272 var input = document.createElement('input');
273 input.setAttribute('type', "file");
274 return attr in input;
275 },
276
277 /**
278 * FileAPI (core object)
279 */
280 api = {
281 version: '2.0.7',
282
283 cors: false,
284 html5: true,
285 media: false,
286 formData: true,
287 multiPassResize: true,
288
289 debug: false,
290 pingUrl: false,
291 multiFlash: false,
292 flashAbortTimeout: 0,
293 withCredentials: true,
294
295 staticPath: './dist/',
296
297 flashUrl: 0, // @default: './FileAPI.flash.swf'
298 flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
299
300 postNameConcat: function (name, idx){
301 return name + (idx != null ? '['+ idx +']' : '');
302 },
303
304 ext2mime: {
305 jpg: 'image/jpeg'
306 , tif: 'image/tiff'
307 , txt: 'text/plain'
308 },
309
310 // Fallback for flash
311 accept: {
312 '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'
313 , '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'
314 , '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'
315 },
316
317 uploadRetry : 0,
318 networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
319
320 chunkSize : 0,
321 chunkUploadRetry : 0,
322 chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
323
324 KB: _SIZE_CONST(1),
325 MB: _SIZE_CONST(2),
326 GB: _SIZE_CONST(3),
327 TB: _SIZE_CONST(4),
328
329 EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
330
331 expando: 'fileapi' + (new Date).getTime(),
332
333 uid: function (obj){
334 return obj
335 ? (obj[api.expando] = obj[api.expando] || api.uid())
336 : (++gid, api.expando + gid)
337 ;
338 },
339
340 log: function (){
341 // ngf fix for IE8 #1071
342 if( api.debug && api._supportConsoleLog ){
343 if( api._supportConsoleLogApply ){
344 console.log.apply(console, arguments);
345 }
346 else {
347 console.log([].join.call(arguments, ' '));
348 }
349 }
350 },
351
352 /**
353 * Create new image
354 *
355 * @param {String} [src]
356 * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element
357 * @returns {HTMLElement}
358 */
359 newImage: function (src, fn){
360 var img = document.createElement('img');
361 if( fn ){
362 api.event.one(img, 'error load', function (evt){
363 fn(evt.type == 'error', img);
364 img = null;
365 });
366 }
367 img.src = src;
368 return img;
369 },
370
371 /**
372 * Get XHR
373 * @returns {XMLHttpRequest}
374 */
375 getXHR: function (){
376 var xhr;
377
378 if( XMLHttpRequest ){
379 xhr = new XMLHttpRequest;
380 }
381 else if( window.ActiveXObject ){
382 try {
383 xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
384 } catch (e) {
385 xhr = new ActiveXObject('Microsoft.XMLHTTP');
386 }
387 }
388
389 return xhr;
390 },
391
392 isArray: _isArray,
393
394 support: {
395 dnd: cors && ('ondrop' in document.createElement('div')),
396 cors: cors,
397 html5: html5,
398 chunked: chunked,
399 dataURI: true,
400 accept: _supportInputAttr('accept'),
401 multiple: _supportInputAttr('multiple')
402 },
403
404 event: {
405 on: _on
406 , off: _off
407 , one: _one
408 , fix: _fixEvent
409 },
410
411
412 throttle: function(fn, delay) {
413 var id, args;
414
415 return function _throttle(){
416 args = arguments;
417
418 if( !id ){
419 fn.apply(window, args);
420 id = setTimeout(function (){
421 id = 0;
422 fn.apply(window, args);
423 }, delay);
424 }
425 };
426 },
427
428
429 F: function (){},
430
431
432 parseJSON: function (str){
433 var json;
434 if( window.JSON && JSON.parse ){
435 json = JSON.parse(str);
436 }
437 else {
438 json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
439 }
440 return json;
441 },
442
443
444 trim: function (str){
445 str = String(str);
446 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
447 },
448
449 /**
450 * Simple Defer
451 * @return {Object}
452 */
453 defer: function (){
454 var
455 list = []
456 , result
457 , error
458 , defer = {
459 resolve: function (err, res){
460 defer.resolve = noop;
461 error = err || false;
462 result = res;
463
464 while( res = list.shift() ){
465 res(error, result);
466 }
467 },
468
469 then: function (fn){
470 if( error !== undef ){
471 fn(error, result);
472 } else {
473 list.push(fn);
474 }
475 }
476 };
477
478 return defer;
479 },
480
481 queue: function (fn){
482 var
483 _idx = 0
484 , _length = 0
485 , _fail = false
486 , _end = false
487 , queue = {
488 inc: function (){
489 _length++;
490 },
491
492 next: function (){
493 _idx++;
494 setTimeout(queue.check, 0);
495 },
496
497 check: function (){
498 (_idx >= _length) && !_fail && queue.end();
499 },
500
501 isFail: function (){
502 return _fail;
503 },
504
505 fail: function (){
506 !_fail && fn(_fail = true);
507 },
508
509 end: function (){
510 if( !_end ){
511 _end = true;
512 fn();
513 }
514 }
515 }
516 ;
517 return queue;
518 },
519
520
521 /**
522 * For each object
523 *
524 * @param {Object|Array} obj
525 * @param {Function} fn
526 * @param {*} [ctx]
527 */
528 each: _each,
529
530
531 /**
532 * Async for
533 * @param {Array} array
534 * @param {Function} callback
535 */
536 afor: function (array, callback){
537 var i = 0, n = array.length;
538
539 if( _isArray(array) && n-- ){
540 (function _next(){
541 callback(n != i && _next, array[i], i++);
542 })();
543 }
544 else {
545 callback(false);
546 }
547 },
548
549
550 /**
551 * Merge the contents of two or more objects together into the first object
552 *
553 * @param {Object} dst
554 * @return {Object}
555 */
556 extend: _extend,
557
558
559 /**
560 * Is file?
561 * @param {File} file
562 * @return {Boolean}
563 */
564 isFile: function (file){
565 return _toString.call(file) === '[object File]';
566 },
567
568
569 /**
570 * Is blob?
571 * @param {Blob} blob
572 * @returns {Boolean}
573 */
574 isBlob: function (blob) {
575 return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
576 },
577
578
579 /**
580 * Is canvas element
581 *
582 * @param {HTMLElement} el
583 * @return {Boolean}
584 */
585 isCanvas: function (el){
586 return el && _rcanvas.test(el.nodeName);
587 },
588
589
590 getFilesFilter: function (filter){
591 filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
592 return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
593 },
594
595
596
597 /**
598 * Read as DataURL
599 *
600 * @param {File|Element} file
601 * @param {Function} fn
602 */
603 readAsDataURL: function (file, fn){
604 if( api.isCanvas(file) ){
605 _emit(file, fn, 'load', api.toDataURL(file));
606 }
607 else {
608 _readAs(file, fn, 'DataURL');
609 }
610 },
611
612
613 /**
614 * Read as Binary string
615 *
616 * @param {File} file
617 * @param {Function} fn
618 */
619 readAsBinaryString: function (file, fn){
620 if( _hasSupportReadAs('BinaryString') ){
621 _readAs(file, fn, 'BinaryString');
622 } else {
623 // Hello IE10!
624 _readAs(file, function (evt){
625 if( evt.type == 'load' ){
626 try {
627 // dataURL -> binaryString
628 evt.result = api.toBinaryString(evt.result);
629 } catch (e){
630 evt.type = 'error';
631 evt.message = e.toString();
632 }
633 }
634 fn(evt);
635 }, 'DataURL');
636 }
637 },
638
639
640 /**
641 * Read as ArrayBuffer
642 *
643 * @param {File} file
644 * @param {Function} fn
645 */
646 readAsArrayBuffer: function(file, fn){
647 _readAs(file, fn, 'ArrayBuffer');
648 },
649
650
651 /**
652 * Read as text
653 *
654 * @param {File} file
655 * @param {String} encoding
656 * @param {Function} [fn]
657 */
658 readAsText: function(file, encoding, fn){
659 if( !fn ){
660 fn = encoding;
661 encoding = 'utf-8';
662 }
663
664 _readAs(file, fn, 'Text', encoding);
665 },
666
667
668 /**
669 * Convert image or canvas to DataURL
670 *
671 * @param {Element} el Image or Canvas element
672 * @param {String} [type] mime-type
673 * @return {String}
674 */
675 toDataURL: function (el, type){
676 if( typeof el == 'string' ){
677 return el;
678 }
679 else if( el.toDataURL ){
680 return el.toDataURL(type || 'image/png');
681 }
682 },
683
684
685 /**
686 * Canvert string, image or canvas to binary string
687 *
688 * @param {String|Element} val
689 * @return {String}
690 */
691 toBinaryString: function (val){
692 return window.atob(api.toDataURL(val).replace(_rdata, ''));
693 },
694
695
696 /**
697 * Read file or DataURL as ImageElement
698 *
699 * @param {File|String} file
700 * @param {Function} fn
701 * @param {Boolean} [progress]
702 */
703 readAsImage: function (file, fn, progress){
704 if( api.isFile(file) ){
705 if( apiURL ){
706 /** @namespace apiURL.createObjectURL */
707 var data = apiURL.createObjectURL(file);
708 if( data === undef ){
709 _emit(file, fn, 'error');
710 }
711 else {
712 api.readAsImage(data, fn, progress);
713 }
714 }
715 else {
716 api.readAsDataURL(file, function (evt){
717 if( evt.type == 'load' ){
718 api.readAsImage(evt.result, fn, progress);
719 }
720 else if( progress || evt.type == 'error' ){
721 _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
722 }
723 });
724 }
725 }
726 else if( api.isCanvas(file) ){
727 _emit(file, fn, 'load', file);
728 }
729 else if( _rimg.test(file.nodeName) ){
730 if( file.complete ){
731 _emit(file, fn, 'load', file);
732 }
733 else {
734 var events = 'error abort load';
735 _one(file, events, function _fn(evt){
736 if( evt.type == 'load' && apiURL ){
737 /** @namespace apiURL.revokeObjectURL */
738 apiURL.revokeObjectURL(file.src);
739 }
740
741 _off(file, events, _fn);
742 _emit(file, fn, evt, file);
743 });
744 }
745 }
746 else if( file.iframe ){
747 _emit(file, fn, { type: 'error' });
748 }
749 else {
750 // Created image
751 var img = api.newImage(file.dataURL || file);
752 api.readAsImage(img, fn, progress);
753 }
754 },
755
756
757 /**
758 * Make file by name
759 *
760 * @param {String} name
761 * @return {Array}
762 */
763 checkFileObj: function (name){
764 var file = {}, accept = api.accept;
765
766 if( typeof name == 'object' ){
767 file = name;
768 }
769 else {
770 file.name = (name + '').split(/\\|\//g).pop();
771 }
772
773 if( file.type == null ){
774 file.type = file.name.split('.').pop();
775 }
776
777 _each(accept, function (ext, type){
778 ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
779 if( ext.test(file.type) || api.ext2mime[file.type] ){
780 file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
781 }
782 });
783
784 return file;
785 },
786
787
788 /**
789 * Get drop files
790 *
791 * @param {Event} evt
792 * @param {Function} callback
793 */
794 getDropFiles: function (evt, callback){
795 var
796 files = []
797 , dataTransfer = _getDataTransfer(evt)
798 , entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
799 , queue = api.queue(function (){ callback(files); })
800 ;
801
802 _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
803 queue.inc();
804
805 try {
806 if( entrySupport ){
807 _readEntryAsFiles(item, function (err, entryFiles){
808 if( err ){
809 api.log('[err] getDropFiles:', err);
810 } else {
811 files.push.apply(files, entryFiles);
812 }
813 queue.next();
814 });
815 }
816 else {
817 _isRegularFile(item, function (yes){
818 yes && files.push(item);
819 queue.next();
820 });
821 }
822 }
823 catch( err ){
824 queue.next();
825 api.log('[err] getDropFiles: ', err);
826 }
827 });
828
829 queue.check();
830 },
831
832
833 /**
834 * Get file list
835 *
836 * @param {HTMLInputElement|Event} input
837 * @param {String|Function} [filter]
838 * @param {Function} [callback]
839 * @return {Array|Null}
840 */
841 getFiles: function (input, filter, callback){
842 var files = [];
843
844 if( callback ){
845 api.filterFiles(api.getFiles(input), filter, callback);
846 return null;
847 }
848
849 if( input.jquery ){
850 // jQuery object
851 input.each(function (){
852 files = files.concat(api.getFiles(this));
853 });
854 input = files;
855 files = [];
856 }
857
858 if( typeof filter == 'string' ){
859 filter = api.getFilesFilter(filter);
860 }
861
862 if( input.originalEvent ){
863 // jQuery event
864 input = _fixEvent(input.originalEvent);
865 }
866 else if( input.srcElement ){
867 // IE Event
868 input = _fixEvent(input);
869 }
870
871
872 if( input.dataTransfer ){
873 // Drag'n'Drop
874 input = input.dataTransfer;
875 }
876 else if( input.target ){
877 // Event
878 input = input.target;
879 }
880
881 if( input.files ){
882 // Input[type="file"]
883 files = input.files;
884
885 if( !html5 ){
886 // Partial support for file api
887 files[0].blob = input;
888 files[0].iframe = true;
889 }
890 }
891 else if( !html5 && isInputFile(input) ){
892 if( api.trim(input.value) ){
893 files = [api.checkFileObj(input.value)];
894 files[0].blob = input;
895 files[0].iframe = true;
896 }
897 }
898 else if( _isArray(input) ){
899 files = input;
900 }
901
902 return api.filter(files, function (file){ return !filter || filter.test(file.name); });
903 },
904
905
906 /**
907 * Get total file size
908 * @param {Array} files
909 * @return {Number}
910 */
911 getTotalSize: function (files){
912 var size = 0, i = files && files.length;
913 while( i-- ){
914 size += files[i].size;
915 }
916 return size;
917 },
918
919
920 /**
921 * Get image information
922 *
923 * @param {File} file
924 * @param {Function} fn
925 */
926 getInfo: function (file, fn){
927 var info = {}, readers = _infoReader.concat();
928
929 if( api.isFile(file) ){
930 (function _next(){
931 var reader = readers.shift();
932 if( reader ){
933 if( reader.test(file.type) ){
934 reader(file, function (err, res){
935 if( err ){
936 fn(err);
937 }
938 else {
939 _extend(info, res);
940 _next();
941 }
942 });
943 }
944 else {
945 _next();
946 }
947 }
948 else {
949 fn(false, info);
950 }
951 })();
952 }
953 else {
954 fn('not_support_info', info);
955 }
956 },
957
958
959 /**
960 * Add information reader
961 *
962 * @param {RegExp} mime
963 * @param {Function} fn
964 */
965 addInfoReader: function (mime, fn){
966 fn.test = function (type){ return mime.test(type); };
967 _infoReader.push(fn);
968 },
969
970
971 /**
972 * Filter of array
973 *
974 * @param {Array} input
975 * @param {Function} fn
976 * @return {Array}
977 */
978 filter: function (input, fn){
979 var result = [], i = 0, n = input.length, val;
980
981 for( ; i < n; i++ ){
982 if( i in input ){
983 val = input[i];
984 if( fn.call(val, val, i, input) ){
985 result.push(val);
986 }
987 }
988 }
989
990 return result;
991 },
992
993
994 /**
995 * Filter files
996 *
997 * @param {Array} files
998 * @param {Function} eachFn
999 * @param {Function} resultFn
1000 */
1001 filterFiles: function (files, eachFn, resultFn){
1002 if( files.length ){
1003 // HTML5 or Flash
1004 var queue = files.concat(), file, result = [], deleted = [];
1005
1006 (function _next(){
1007 if( queue.length ){
1008 file = queue.shift();
1009 api.getInfo(file, function (err, info){
1010 (eachFn(file, err ? false : info) ? result : deleted).push(file);
1011 _next();
1012 });
1013 }
1014 else {
1015 resultFn(result, deleted);
1016 }
1017 })();
1018 }
1019 else {
1020 resultFn([], files);
1021 }
1022 },
1023
1024
1025 upload: function (options){
1026 options = _extend({
1027 jsonp: 'callback'
1028 , prepare: api.F
1029 , beforeupload: api.F
1030 , upload: api.F
1031 , fileupload: api.F
1032 , fileprogress: api.F
1033 , filecomplete: api.F
1034 , progress: api.F
1035 , complete: api.F
1036 , pause: api.F
1037 , imageOriginal: true
1038 , chunkSize: api.chunkSize
1039 , chunkUploadRetry: api.chunkUploadRetry
1040 , uploadRetry: api.uploadRetry
1041 }, options);
1042
1043
1044 if( options.imageAutoOrientation && !options.imageTransform ){
1045 options.imageTransform = { rotate: 'auto' };
1046 }
1047
1048
1049 var
1050 proxyXHR = new api.XHR(options)
1051 , dataArray = this._getFilesDataArray(options.files)
1052 , _this = this
1053 , _total = 0
1054 , _loaded = 0
1055 , _nextFile
1056 , _complete = false
1057 ;
1058
1059
1060 // calc total size
1061 _each(dataArray, function (data){
1062 _total += data.size;
1063 });
1064
1065 // Array of files
1066 proxyXHR.files = [];
1067 _each(dataArray, function (data){
1068 proxyXHR.files.push(data.file);
1069 });
1070
1071 // Set upload status props
1072 proxyXHR.total = _total;
1073 proxyXHR.loaded = 0;
1074 proxyXHR.filesLeft = dataArray.length;
1075
1076 // emit "beforeupload" event
1077 options.beforeupload(proxyXHR, options);
1078
1079 // Upload by file
1080 _nextFile = function (){
1081 var
1082 data = dataArray.shift()
1083 , _file = data && data.file
1084 , _fileLoaded = false
1085 , _fileOptions = _simpleClone(options)
1086 ;
1087
1088 proxyXHR.filesLeft = dataArray.length;
1089
1090 if( _file && _file.name === api.expando ){
1091 _file = null;
1092 api.log('[warn] FileAPI.upload() — called without files');
1093 }
1094
1095 if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1096 // Mark active job
1097 _complete = false;
1098
1099 // Set current upload file
1100 proxyXHR.currentFile = _file;
1101
1102 // Prepare file options
1103 if (_file && options.prepare(_file, _fileOptions) === false) {
1104 _nextFile.call(_this);
1105 return;
1106 }
1107 _fileOptions.file = _file;
1108
1109 _this._getFormData(_fileOptions, data, function (form){
1110 if( !_loaded ){
1111 // emit "upload" event
1112 options.upload(proxyXHR, options);
1113 }
1114
1115 var xhr = new api.XHR(_extend({}, _fileOptions, {
1116
1117 upload: _file ? function (){
1118 // emit "fileupload" event
1119 options.fileupload(_file, xhr, _fileOptions);
1120 } : noop,
1121
1122 progress: _file ? function (evt){
1123 if( !_fileLoaded ){
1124 // For ignore the double calls.
1125 _fileLoaded = (evt.loaded === evt.total);
1126
1127 // emit "fileprogress" event
1128 options.fileprogress({
1129 type: 'progress'
1130 , total: data.total = evt.total
1131 , loaded: data.loaded = evt.loaded
1132 }, _file, xhr, _fileOptions);
1133
1134 // emit "progress" event
1135 options.progress({
1136 type: 'progress'
1137 , total: _total
1138 , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1139 }, _file, xhr, _fileOptions);
1140 }
1141 } : noop,
1142
1143 complete: function (err){
1144 _each(_xhrPropsExport, function (name){
1145 proxyXHR[name] = xhr[name];
1146 });
1147
1148 if( _file ){
1149 data.total = (data.total || data.size);
1150 data.loaded = data.total;
1151
1152 if( !err ) {
1153 // emulate 100% "progress"
1154 this.progress(data);
1155
1156 // fixed throttle event
1157 _fileLoaded = true;
1158
1159 // bytes loaded
1160 _loaded += data.size; // data.size != data.total, it's desirable fix this
1161 proxyXHR.loaded = _loaded;
1162 }
1163
1164 // emit "filecomplete" event
1165 options.filecomplete(err, xhr, _file, _fileOptions);
1166 }
1167
1168 // upload next file
1169 setTimeout(function () {_nextFile.call(_this);}, 0);
1170 }
1171 })); // xhr
1172
1173
1174 // ...
1175 proxyXHR.abort = function (current){
1176 if (!current) { dataArray.length = 0; }
1177 this.current = current;
1178 xhr.abort();
1179 };
1180
1181 // Start upload
1182 xhr.send(form);
1183 });
1184 }
1185 else {
1186 var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1187 options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1188 // Mark done state
1189 _complete = true;
1190 }
1191 };
1192
1193
1194 // Next tick
1195 setTimeout(_nextFile, 0);
1196
1197
1198 // Append more files to the existing request
1199 // first - add them to the queue head/tail
1200 proxyXHR.append = function (files, first) {
1201 files = api._getFilesDataArray([].concat(files));
1202
1203 _each(files, function (data) {
1204 _total += data.size;
1205 proxyXHR.files.push(data.file);
1206 if (first) {
1207 dataArray.unshift(data);
1208 } else {
1209 dataArray.push(data);
1210 }
1211 });
1212
1213 proxyXHR.statusText = "";
1214
1215 if( _complete ){
1216 _nextFile.call(_this);
1217 }
1218 };
1219
1220
1221 // Removes file from queue by file reference and returns it
1222 proxyXHR.remove = function (file) {
1223 var i = dataArray.length, _file;
1224 while( i-- ){
1225 if( dataArray[i].file == file ){
1226 _file = dataArray.splice(i, 1);
1227 _total -= _file.size;
1228 }
1229 }
1230 return _file;
1231 };
1232
1233 return proxyXHR;
1234 },
1235
1236
1237 _getFilesDataArray: function (data){
1238 var files = [], oFiles = {};
1239
1240 if( isInputFile(data) ){
1241 var tmp = api.getFiles(data);
1242 oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1243 }
1244 else if( _isArray(data) && isInputFile(data[0]) ){
1245 _each(data, function (input){
1246 oFiles[input.name || 'file'] = api.getFiles(input);
1247 });
1248 }
1249 else {
1250 oFiles = data;
1251 }
1252
1253 _each(oFiles, function add(file, name){
1254 if( _isArray(file) ){
1255 _each(file, function (file){
1256 add(file, name);
1257 });
1258 }
1259 else if( file && (file.name || file.image) ){
1260 files.push({
1261 name: name
1262 , file: file
1263 , size: file.size
1264 , total: file.size
1265 , loaded: 0
1266 });
1267 }
1268 });
1269
1270 if( !files.length ){
1271 // Create fake `file` object
1272 files.push({ file: { name: api.expando } });
1273 }
1274
1275 return files;
1276 },
1277
1278
1279 _getFormData: function (options, data, fn){
1280 var
1281 file = data.file
1282 , name = data.name
1283 , filename = file.name
1284 , filetype = file.type
1285 , trans = api.support.transform && options.imageTransform
1286 , Form = new api.Form
1287 , queue = api.queue(function (){ fn(Form); })
1288 , isOrignTrans = trans && _isOriginTransform(trans)
1289 , postNameConcat = api.postNameConcat
1290 ;
1291
1292 // Append data
1293 _each(options.data, function add(val, name){
1294 if( typeof val == 'object' ){
1295 _each(val, function (v, i){
1296 add(v, postNameConcat(name, i));
1297 });
1298 }
1299 else {
1300 Form.append(name, val);
1301 }
1302 });
1303
1304 (function _addFile(file/**Object*/){
1305 if( file.image ){ // This is a FileAPI.Image
1306 queue.inc();
1307
1308 file.toData(function (err, image){
1309 // @todo: error
1310 filename = filename || (new Date).getTime()+'.png';
1311
1312 _addFile(image);
1313 queue.next();
1314 });
1315 }
1316 else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1317 queue.inc();
1318
1319 if( isOrignTrans ){
1320 // Convert to array for transform function
1321 trans = [trans];
1322 }
1323
1324 api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1325 if( isOrignTrans && !err ){
1326 if( !dataURLtoBlob && !api.flashEngine ){
1327 // Canvas.toBlob or Flash not supported, use multipart
1328 Form.multipart = true;
1329 }
1330
1331 Form.append(name, images[0], filename, trans[0].type || filetype);
1332 }
1333 else {
1334 var addOrigin = 0;
1335
1336 if( !err ){
1337 _each(images, function (image, idx){
1338 if( !dataURLtoBlob && !api.flashEngine ){
1339 Form.multipart = true;
1340 }
1341
1342 if( !trans[idx].postName ){
1343 addOrigin = 1;
1344 }
1345
1346 Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1347 });
1348 }
1349
1350 if( err || options.imageOriginal ){
1351 Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1352 }
1353 }
1354
1355 queue.next();
1356 });
1357 }
1358 else if( filename !== api.expando ){
1359 Form.append(name, file, filename);
1360 }
1361 })(file);
1362
1363 queue.check();
1364 },
1365
1366
1367 reset: function (inp, notRemove){
1368 var parent, clone;
1369
1370 if( jQuery ){
1371 clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1372 if( !notRemove ){
1373 jQuery(inp).remove();
1374 }
1375 } else {
1376 parent = inp.parentNode;
1377 clone = parent.insertBefore(inp.cloneNode(true), inp);
1378 clone.value = '';
1379
1380 if( !notRemove ){
1381 parent.removeChild(inp);
1382 }
1383
1384 _each(_elEvents[api.uid(inp)], function (fns, type){
1385 _each(fns, function (fn){
1386 _off(inp, type, fn);
1387 _on(clone, type, fn);
1388 });
1389 });
1390 }
1391
1392 return clone;
1393 },
1394
1395
1396 /**
1397 * Load remote file
1398 *
1399 * @param {String} url
1400 * @param {Function} fn
1401 * @return {XMLHttpRequest}
1402 */
1403 load: function (url, fn){
1404 var xhr = api.getXHR();
1405 if( xhr ){
1406 xhr.open('GET', url, true);
1407
1408 if( xhr.overrideMimeType ){
1409 xhr.overrideMimeType('text/plain; charset=x-user-defined');
1410 }
1411
1412 _on(xhr, 'progress', function (/**Event*/evt){
1413 /** @namespace evt.lengthComputable */
1414 if( evt.lengthComputable ){
1415 fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1416 }
1417 });
1418
1419 xhr.onreadystatechange = function(){
1420 if( xhr.readyState == 4 ){
1421 xhr.onreadystatechange = null;
1422 if( xhr.status == 200 ){
1423 url = url.split('/');
1424 /** @namespace xhr.responseBody */
1425 var file = {
1426 name: url[url.length-1]
1427 , size: xhr.getResponseHeader('Content-Length')
1428 , type: xhr.getResponseHeader('Content-Type')
1429 };
1430 file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1431 fn({ type: 'load', result: file }, xhr);
1432 }
1433 else {
1434 fn({ type: 'error' }, xhr);
1435 }
1436 }
1437 };
1438 xhr.send(null);
1439 } else {
1440 fn({ type: 'error' });
1441 }
1442
1443 return xhr;
1444 },
1445
1446 encode64: function (str){
1447 var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1448
1449 if( typeof str !== 'string' ){
1450 str = String(str);
1451 }
1452
1453 while( i < str.length ){
1454 //all three "& 0xff" added below are there to fix a known bug
1455 //with bytes returned by xhr.responseText
1456 var
1457 byte1 = str.charCodeAt(i++) & 0xff
1458 , byte2 = str.charCodeAt(i++) & 0xff
1459 , byte3 = str.charCodeAt(i++) & 0xff
1460 , enc1 = byte1 >> 2
1461 , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1462 , enc3, enc4
1463 ;
1464
1465 if( isNaN(byte2) ){
1466 enc3 = enc4 = 64;
1467 } else {
1468 enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1469 enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1470 }
1471
1472 outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1473 }
1474
1475 return outStr;
1476 }
1477
1478 } // api
1479 ;
1480
1481
1482 function _emit(target, fn, name, res, ext){
1483 var evt = {
1484 type: name.type || name
1485 , target: target
1486 , result: res
1487 };
1488 _extend(evt, ext);
1489 fn(evt);
1490 }
1491
1492
1493 function _hasSupportReadAs(as){
1494 return FileReader && !!FileReader.prototype['readAs'+as];
1495 }
1496
1497
1498 function _readAs(file, fn, as, encoding){
1499 if( api.isBlob(file) && _hasSupportReadAs(as) ){
1500 var Reader = new FileReader;
1501
1502 // Add event listener
1503 _on(Reader, _readerEvents, function _fn(evt){
1504 var type = evt.type;
1505 if( type == 'progress' ){
1506 _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1507 }
1508 else if( type == 'loadend' ){
1509 _off(Reader, _readerEvents, _fn);
1510 Reader = null;
1511 }
1512 else {
1513 _emit(file, fn, evt, evt.target.result);
1514 }
1515 });
1516
1517
1518 try {
1519 // ReadAs ...
1520 if( encoding ){
1521 Reader['readAs'+as](file, encoding);
1522 }
1523 else {
1524 Reader['readAs'+as](file);
1525 }
1526 }
1527 catch (err){
1528 _emit(file, fn, 'error', undef, { error: err.toString() });
1529 }
1530 }
1531 else {
1532 _emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });
1533 }
1534 }
1535
1536
1537 function _isRegularFile(file, callback){
1538 // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1539 if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1540 if( FileReader ){
1541 try {
1542 var Reader = new FileReader();
1543
1544 _one(Reader, _readerEvents, function (evt){
1545 var isFile = evt.type != 'error';
1546 callback(isFile);
1547 if( isFile ){
1548 Reader.abort();
1549 }
1550 });
1551
1552 Reader.readAsDataURL(file);
1553 } catch( err ){
1554 callback(false);
1555 }
1556 }
1557 else {
1558 callback(null);
1559 }
1560 }
1561 else {
1562 callback(true);
1563 }
1564 }
1565
1566
1567 function _getAsEntry(item){
1568 var entry;
1569 if( item.getAsEntry ){ entry = item.getAsEntry(); }
1570 else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1571 return entry;
1572 }
1573
1574
1575 function _readEntryAsFiles(entry, callback){
1576 if( !entry ){
1577 // error
1578 callback('invalid entry');
1579 }
1580 else if( entry.isFile ){
1581 // Read as file
1582 entry.file(function(file){
1583 // success
1584 file.fullPath = entry.fullPath;
1585 callback(false, [file]);
1586 }, function (err){
1587 // error
1588 callback('FileError.code: '+err.code);
1589 });
1590 }
1591 else if( entry.isDirectory ){
1592 var reader = entry.createReader(), result = [];
1593
1594 reader.readEntries(function(entries){
1595 // success
1596 api.afor(entries, function (next, entry){
1597 _readEntryAsFiles(entry, function (err, files){
1598 if( err ){
1599 api.log(err);
1600 }
1601 else {
1602 result = result.concat(files);
1603 }
1604
1605 if( next ){
1606 next();
1607 }
1608 else {
1609 callback(false, result);
1610 }
1611 });
1612 });
1613 }, function (err){
1614 // error
1615 callback('directory_reader: ' + err);
1616 });
1617 }
1618 else {
1619 _readEntryAsFiles(_getAsEntry(entry), callback);
1620 }
1621 }
1622
1623
1624 function _simpleClone(obj){
1625 var copy = {};
1626 _each(obj, function (val, key){
1627 if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1628 val = _extend({}, val);
1629 }
1630 copy[key] = val;
1631 });
1632 return copy;
1633 }
1634
1635
1636 function isInputFile(el){
1637 return _rinput.test(el && el.tagName);
1638 }
1639
1640
1641 function _getDataTransfer(evt){
1642 return (evt.originalEvent || evt || '').dataTransfer || {};
1643 }
1644
1645
1646 function _isOriginTransform(trans){
1647 var key;
1648 for( key in trans ){
1649 if( trans.hasOwnProperty(key) ){
1650 if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1651 return true;
1652 }
1653 }
1654 }
1655 return false;
1656 }
1657
1658
1659 // Add default image info reader
1660 api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1661 if( !file.__dimensions ){
1662 var defer = file.__dimensions = api.defer();
1663
1664 api.readAsImage(file, function (evt){
1665 var img = evt.target;
1666 defer.resolve(evt.type == 'load' ? false : 'error', {
1667 width: img.width
1668 , height: img.height
1669 });
1670 img.src = api.EMPTY_PNG;
1671 img = null;
1672 });
1673 }
1674
1675 file.__dimensions.then(callback);
1676 });
1677
1678
1679 /**
1680 * Drag'n'Drop special event
1681 *
1682 * @param {HTMLElement} el
1683 * @param {Function} onHover
1684 * @param {Function} onDrop
1685 */
1686 api.event.dnd = function (el, onHover, onDrop){
1687 var _id, _type;
1688
1689 if( !onDrop ){
1690 onDrop = onHover;
1691 onHover = api.F;
1692 }
1693
1694 if( FileReader ){
1695 // Hover
1696 _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1697 var
1698 types = _getDataTransfer(evt).types
1699 , i = types && types.length
1700 , debounceTrigger = false
1701 ;
1702
1703 while( i-- ){
1704 if( ~types[i].indexOf('File') ){
1705 evt[preventDefault]();
1706
1707 if( _type !== evt.type ){
1708 _type = evt.type; // Store current type of event
1709
1710 if( _type != 'dragleave' ){
1711 onHover.call(evt[currentTarget], true, evt);
1712 }
1713
1714 debounceTrigger = true;
1715 }
1716
1717 break; // exit from "while"
1718 }
1719 }
1720
1721 if( debounceTrigger ){
1722 clearTimeout(_id);
1723 _id = setTimeout(function (){
1724 onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1725 }, 50);
1726 }
1727 });
1728
1729
1730 // Drop
1731 _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1732 evt[preventDefault]();
1733
1734 _type = 0;
1735 onHover.call(evt[currentTarget], false, evt);
1736
1737 api.getDropFiles(evt, function (files){
1738 onDrop.call(evt[currentTarget], files, evt);
1739 });
1740 });
1741 }
1742 else {
1743 api.log("Drag'n'Drop -- not supported");
1744 }
1745 };
1746
1747
1748 /**
1749 * Remove drag'n'drop
1750 * @param {HTMLElement} el
1751 * @param {Function} onHover
1752 * @param {Function} onDrop
1753 */
1754 api.event.dnd.off = function (el, onHover, onDrop){
1755 _off(el, 'dragenter dragleave dragover', onHover.ff);
1756 _off(el, 'drop', onDrop.ff);
1757 };
1758
1759
1760 // Support jQuery
1761 if( jQuery && !jQuery.fn.dnd ){
1762 jQuery.fn.dnd = function (onHover, onDrop){
1763 return this.each(function (){
1764 api.event.dnd(this, onHover, onDrop);
1765 });
1766 };
1767
1768 jQuery.fn.offdnd = function (onHover, onDrop){
1769 return this.each(function (){
1770 api.event.dnd.off(this, onHover, onDrop);
1771 });
1772 };
1773 }
1774
1775 // @export
1776 window.FileAPI = _extend(api, window.FileAPI);
1777
1778
1779 // Debug info
1780 api.log('FileAPI: ' + api.version);
1781 api.log('protocol: ' + window.location.protocol);
1782 api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1783
1784
1785 // @detect 'x-ua-compatible'
1786 _each(document.getElementsByTagName('meta'), function (meta){
1787 if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1788 api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1789 }
1790 });
1791
1792
1793 // configuration
1794 try {
1795 api._supportConsoleLog = !!console.log;
1796 api._supportConsoleLogApply = !!console.log.apply;
1797 } catch (err) {}
1798
1799 if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1800 if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1801 if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1802 })(window, void 0);
1803
1804 /*global window, FileAPI, document */
1805
1806 (function (api, document, undef) {
1807 'use strict';
1808
1809 var
1810 min = Math.min,
1811 round = Math.round,
1812 getCanvas = function () { return document.createElement('canvas'); },
1813 support = false,
1814 exifOrientation = {
1815 8: 270
1816 , 3: 180
1817 , 6: 90
1818 , 7: 270
1819 , 4: 180
1820 , 5: 90
1821 }
1822 ;
1823
1824 try {
1825 support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1826 }
1827 catch (e){}
1828
1829
1830 function Image(file){
1831 if( file instanceof Image ){
1832 var img = new Image(file.file);
1833 api.extend(img.matrix, file.matrix);
1834 return img;
1835 }
1836 else if( !(this instanceof Image) ){
1837 return new Image(file);
1838 }
1839
1840 this.file = file;
1841 this.size = file.size || 100;
1842
1843 this.matrix = {
1844 sx: 0,
1845 sy: 0,
1846 sw: 0,
1847 sh: 0,
1848 dx: 0,
1849 dy: 0,
1850 dw: 0,
1851 dh: 0,
1852 resize: 0, // min, max OR preview
1853 deg: 0,
1854 quality: 1, // jpeg quality
1855 filter: 0
1856 };
1857 }
1858
1859
1860 Image.prototype = {
1861 image: true,
1862 constructor: Image,
1863
1864 set: function (attrs){
1865 api.extend(this.matrix, attrs);
1866 return this;
1867 },
1868
1869 crop: function (x, y, w, h){
1870 if( w === undef ){
1871 w = x;
1872 h = y;
1873 x = y = 0;
1874 }
1875 return this.set({ sx: x, sy: y, sw: w, sh: h || w });
1876 },
1877
1878 resize: function (w, h, strategy){
1879 if( /min|max/.test(h) ){
1880 strategy = h;
1881 h = w;
1882 }
1883
1884 return this.set({ dw: w, dh: h || w, resize: strategy });
1885 },
1886
1887 preview: function (w, h){
1888 return this.resize(w, h || w, 'preview');
1889 },
1890
1891 rotate: function (deg){
1892 return this.set({ deg: deg });
1893 },
1894
1895 filter: function (filter){
1896 return this.set({ filter: filter });
1897 },
1898
1899 overlay: function (images){
1900 return this.set({ overlay: images });
1901 },
1902
1903 clone: function (){
1904 return new Image(this);
1905 },
1906
1907 _load: function (image, fn){
1908 var self = this;
1909
1910 if( /img|video/i.test(image.nodeName) ){
1911 fn.call(self, null, image);
1912 }
1913 else {
1914 api.readAsImage(image, function (evt){
1915 fn.call(self, evt.type != 'load', evt.result);
1916 });
1917 }
1918 },
1919
1920 _apply: function (image, fn){
1921 var
1922 canvas = getCanvas()
1923 , m = this.getMatrix(image)
1924 , ctx = canvas.getContext('2d')
1925 , width = image.videoWidth || image.width
1926 , height = image.videoHeight || image.height
1927 , deg = m.deg
1928 , dw = m.dw
1929 , dh = m.dh
1930 , w = width
1931 , h = height
1932 , filter = m.filter
1933 , copy // canvas copy
1934 , buffer = image
1935 , overlay = m.overlay
1936 , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1937 , renderImageToCanvas = api.renderImageToCanvas
1938 ;
1939
1940 // Normalize angle
1941 deg = deg - Math.floor(deg/360)*360;
1942
1943 // For `renderImageToCanvas`
1944 image._type = this.file.type;
1945
1946 while(m.multipass && min(w/dw, h/dh) > 2 ){
1947 w = (w/2 + 0.5)|0;
1948 h = (h/2 + 0.5)|0;
1949
1950 copy = getCanvas();
1951 copy.width = w;
1952 copy.height = h;
1953
1954 if( buffer !== image ){
1955 renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1956 buffer = copy;
1957 }
1958 else {
1959 buffer = copy;
1960 renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1961 m.sx = m.sy = m.sw = m.sh = 0;
1962 }
1963 }
1964
1965
1966 canvas.width = (deg % 180) ? dh : dw;
1967 canvas.height = (deg % 180) ? dw : dh;
1968
1969 canvas.type = m.type;
1970 canvas.quality = m.quality;
1971
1972 ctx.rotate(deg * Math.PI / 180);
1973 renderImageToCanvas(ctx.canvas, buffer
1974 , m.sx, m.sy
1975 , m.sw || buffer.width
1976 , m.sh || buffer.height
1977 , (deg == 180 || deg == 270 ? -dw : 0)
1978 , (deg == 90 || deg == 180 ? -dh : 0)
1979 , dw, dh
1980 );
1981 dw = canvas.width;
1982 dh = canvas.height;
1983
1984 // Apply overlay
1985 overlay && api.each([].concat(overlay), function (over){
1986 queue.inc();
1987 // preload
1988 var img = new window.Image, fn = function (){
1989 var
1990 x = over.x|0
1991 , y = over.y|0
1992 , w = over.w || img.width
1993 , h = over.h || img.height
1994 , rel = over.rel
1995 ;
1996
1997 // center | right | left
1998 x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
1999
2000 // center | bottom | top
2001 y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
2002
2003 api.event.off(img, 'error load abort', fn);
2004
2005 try {
2006 ctx.globalAlpha = over.opacity || 1;
2007 ctx.drawImage(img, x, y, w, h);
2008 }
2009 catch (er){}
2010
2011 queue.next();
2012 };
2013
2014 api.event.on(img, 'error load abort', fn);
2015 img.src = over.src;
2016
2017 if( img.complete ){
2018 fn();
2019 }
2020 });
2021
2022 if( filter ){
2023 queue.inc();
2024 Image.applyFilter(canvas, filter, queue.next);
2025 }
2026
2027 queue.check();
2028 },
2029
2030 getMatrix: function (image){
2031 var
2032 m = api.extend({}, this.matrix)
2033 , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
2034 , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2035 , dw = m.dw = m.dw || sw
2036 , dh = m.dh = m.dh || sh
2037 , sf = sw/sh, df = dw/dh
2038 , strategy = m.resize
2039 ;
2040
2041 if( strategy == 'preview' ){
2042 if( dw != sw || dh != sh ){
2043 // Make preview
2044 var w, h;
2045
2046 if( df >= sf ){
2047 w = sw;
2048 h = w / df;
2049 } else {
2050 h = sh;
2051 w = h * df;
2052 }
2053
2054 if( w != sw || h != sh ){
2055 m.sx = ~~((sw - w)/2);
2056 m.sy = ~~((sh - h)/2);
2057 sw = w;
2058 sh = h;
2059 }
2060 }
2061 }
2062 else if( strategy ){
2063 if( !(sw > dw || sh > dh) ){
2064 dw = sw;
2065 dh = sh;
2066 }
2067 else if( strategy == 'min' ){
2068 dw = round(sf < df ? min(sw, dw) : dh*sf);
2069 dh = round(sf < df ? dw/sf : min(sh, dh));
2070 }
2071 else {
2072 dw = round(sf >= df ? min(sw, dw) : dh*sf);
2073 dh = round(sf >= df ? dw/sf : min(sh, dh));
2074 }
2075 }
2076
2077 m.sw = sw;
2078 m.sh = sh;
2079 m.dw = dw;
2080 m.dh = dh;
2081 m.multipass = api.multiPassResize;
2082 return m;
2083 },
2084
2085 _trans: function (fn){
2086 this._load(this.file, function (err, image){
2087 if( err ){
2088 fn(err);
2089 }
2090 else {
2091 try {
2092 this._apply(image, fn);
2093 } catch (err){
2094 api.log('[err] FileAPI.Image.fn._apply:', err);
2095 fn(err);
2096 }
2097 }
2098 });
2099 },
2100
2101
2102 get: function (fn){
2103 if( api.support.transform ){
2104 var _this = this, matrix = _this.matrix;
2105
2106 if( matrix.deg == 'auto' ){
2107 api.getInfo(_this.file, function (err, info){
2108 // rotate by exif orientation
2109 matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2110 _this._trans(fn);
2111 });
2112 }
2113 else {
2114 _this._trans(fn);
2115 }
2116 }
2117 else {
2118 fn('not_support_transform');
2119 }
2120
2121 return this;
2122 },
2123
2124
2125 toData: function (fn){
2126 return this.get(fn);
2127 }
2128
2129 };
2130
2131
2132 Image.exifOrientation = exifOrientation;
2133
2134
2135 Image.transform = function (file, transform, autoOrientation, fn){
2136 function _transform(err, img){
2137 // img -- info object
2138 var
2139 images = {}
2140 , queue = api.queue(function (err){
2141 fn(err, images);
2142 })
2143 ;
2144
2145 if( !err ){
2146 api.each(transform, function (params, name){
2147 if( !queue.isFail() ){
2148 var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2149
2150 if( isFn ){
2151 params(img, ImgTrans);
2152 }
2153 else if( params.width ){
2154 ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2155 }
2156 else {
2157 if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2158 ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2159 }
2160 }
2161
2162 if( params.crop ){
2163 var crop = params.crop;
2164 ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2165 }
2166
2167 if( params.rotate === undef && autoOrientation ){
2168 params.rotate = 'auto';
2169 }
2170
2171 ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2172
2173 if( !isFn ){
2174 ImgTrans.set({
2175 deg: params.rotate
2176 , overlay: params.overlay
2177 , filter: params.filter
2178 , quality: params.quality || 1
2179 });
2180 }
2181
2182 queue.inc();
2183 ImgTrans.toData(function (err, image){
2184 if( err ){
2185 queue.fail();
2186 }
2187 else {
2188 images[name] = image;
2189 queue.next();
2190 }
2191 });
2192 }
2193 });
2194 }
2195 else {
2196 queue.fail();
2197 }
2198 }
2199
2200
2201 // @todo: Оло-ло, нужно рефакторить это место
2202 if( file.width ){
2203 _transform(false, file);
2204 } else {
2205 api.getInfo(file, _transform);
2206 }
2207 };
2208
2209
2210 // @const
2211 api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2212 api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2213 Image[x+'_'+y] = i*3 + j;
2214 Image[y+'_'+x] = i*3 + j;
2215 });
2216 });
2217
2218
2219 /**
2220 * Trabsform element to canvas
2221 *
2222 * @param {Image|HTMLVideoElement} el
2223 * @returns {Canvas}
2224 */
2225 Image.toCanvas = function(el){
2226 var canvas = document.createElement('canvas');
2227 canvas.width = el.videoWidth || el.width;
2228 canvas.height = el.videoHeight || el.height;
2229 canvas.getContext('2d').drawImage(el, 0, 0);
2230 return canvas;
2231 };
2232
2233
2234 /**
2235 * Create image from DataURL
2236 * @param {String} dataURL
2237 * @param {Object} size
2238 * @param {Function} callback
2239 */
2240 Image.fromDataURL = function (dataURL, size, callback){
2241 var img = api.newImage(dataURL);
2242 api.extend(img, size);
2243 callback(img);
2244 };
2245
2246
2247 /**
2248 * Apply filter (caman.js)
2249 *
2250 * @param {Canvas|Image} canvas
2251 * @param {String|Function} filter
2252 * @param {Function} doneFn
2253 */
2254 Image.applyFilter = function (canvas, filter, doneFn){
2255 if( typeof filter == 'function' ){
2256 filter(canvas, doneFn);
2257 }
2258 else if( window.Caman ){
2259 // http://camanjs.com/guides/
2260 window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2261 if( typeof filter == 'string' ){
2262 this[filter]();
2263 }
2264 else {
2265 api.each(filter, function (val, method){
2266 this[method](val);
2267 }, this);
2268 }
2269 this.render(doneFn);
2270 });
2271 }
2272 };
2273
2274
2275 /**
2276 * For load-image-ios.js
2277 */
2278 api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2279 try {
2280 return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2281 } catch (ex) {
2282 api.log('renderImageToCanvas failed');
2283 throw ex;
2284 }
2285 };
2286
2287
2288 // @export
2289 api.support.canvas = api.support.transform = support;
2290 api.Image = Image;
2291 })(FileAPI, document);
2292
2293 /*
2294 * JavaScript Load Image iOS scaling fixes 1.0.3
2295 * https://github.com/blueimp/JavaScript-Load-Image
2296 *
2297 * Copyright 2013, Sebastian Tschan
2298 * https://blueimp.net
2299 *
2300 * iOS image scaling fixes based on
2301 * https://github.com/stomita/ios-imagefile-megapixel
2302 *
2303 * Licensed under the MIT license:
2304 * http://www.opensource.org/licenses/MIT
2305 */
2306
2307 /*jslint nomen: true, bitwise: true */
2308 /*global FileAPI, window, document */
2309
2310 (function (factory) {
2311 'use strict';
2312 factory(FileAPI);
2313 }(function (loadImage) {
2314 'use strict';
2315
2316 // Only apply fixes on the iOS platform:
2317 if (!window.navigator || !window.navigator.platform ||
2318 !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2319 return;
2320 }
2321
2322 var originalRenderMethod = loadImage.renderImageToCanvas;
2323
2324 // Detects subsampling in JPEG images:
2325 loadImage.detectSubsampling = function (img) {
2326 var canvas,
2327 context;
2328 if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2329 canvas = document.createElement('canvas');
2330 canvas.width = canvas.height = 1;
2331 context = canvas.getContext('2d');
2332 context.drawImage(img, -img.width + 1, 0);
2333 // subsampled image becomes half smaller in rendering size.
2334 // check alpha channel value to confirm image is covering edge pixel or not.
2335 // if alpha value is 0 image is not covering, hence subsampled.
2336 return context.getImageData(0, 0, 1, 1).data[3] === 0;
2337 }
2338 return false;
2339 };
2340
2341 // Detects vertical squash in JPEG images:
2342 loadImage.detectVerticalSquash = function (img, subsampled) {
2343 var naturalHeight = img.naturalHeight || img.height,
2344 canvas = document.createElement('canvas'),
2345 context = canvas.getContext('2d'),
2346 data,
2347 sy,
2348 ey,
2349 py,
2350 alpha;
2351 if (subsampled) {
2352 naturalHeight /= 2;
2353 }
2354 canvas.width = 1;
2355 canvas.height = naturalHeight;
2356 context.drawImage(img, 0, 0);
2357 data = context.getImageData(0, 0, 1, naturalHeight).data;
2358 // search image edge pixel position in case it is squashed vertically:
2359 sy = 0;
2360 ey = naturalHeight;
2361 py = naturalHeight;
2362 while (py > sy) {
2363 alpha = data[(py - 1) * 4 + 3];
2364 if (alpha === 0) {
2365 ey = py;
2366 } else {
2367 sy = py;
2368 }
2369 py = (ey + sy) >> 1;
2370 }
2371 return (py / naturalHeight) || 1;
2372 };
2373
2374 // Renders image to canvas while working around iOS image scaling bugs:
2375 // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2376 loadImage.renderImageToCanvas = function (
2377 canvas,
2378 img,
2379 sourceX,
2380 sourceY,
2381 sourceWidth,
2382 sourceHeight,
2383 destX,
2384 destY,
2385 destWidth,
2386 destHeight
2387 ) {
2388 if (img._type === 'image/jpeg') {
2389 var context = canvas.getContext('2d'),
2390 tmpCanvas = document.createElement('canvas'),
2391 tileSize = 1024,
2392 tmpContext = tmpCanvas.getContext('2d'),
2393 subsampled,
2394 vertSquashRatio,
2395 tileX,
2396 tileY;
2397 tmpCanvas.width = tileSize;
2398 tmpCanvas.height = tileSize;
2399 context.save();
2400 subsampled = loadImage.detectSubsampling(img);
2401 if (subsampled) {
2402 sourceX /= 2;
2403 sourceY /= 2;
2404 sourceWidth /= 2;
2405 sourceHeight /= 2;
2406 }
2407 vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2408 if (subsampled || vertSquashRatio !== 1) {
2409 sourceY *= vertSquashRatio;
2410 destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2411 destHeight = Math.ceil(
2412 tileSize * destHeight / sourceHeight / vertSquashRatio
2413 );
2414 destY = 0;
2415 tileY = 0;
2416 while (tileY < sourceHeight) {
2417 destX = 0;
2418 tileX = 0;
2419 while (tileX < sourceWidth) {
2420 tmpContext.clearRect(0, 0, tileSize, tileSize);
2421 tmpContext.drawImage(
2422 img,
2423 sourceX,
2424 sourceY,
2425 sourceWidth,
2426 sourceHeight,
2427 -tileX,
2428 -tileY,
2429 sourceWidth,
2430 sourceHeight
2431 );
2432 context.drawImage(
2433 tmpCanvas,
2434 0,
2435 0,
2436 tileSize,
2437 tileSize,
2438 destX,
2439 destY,
2440 destWidth,
2441 destHeight
2442 );
2443 tileX += tileSize;
2444 destX += destWidth;
2445 }
2446 tileY += tileSize;
2447 destY += destHeight;
2448 }
2449 context.restore();
2450 return canvas;
2451 }
2452 }
2453 return originalRenderMethod(
2454 canvas,
2455 img,
2456 sourceX,
2457 sourceY,
2458 sourceWidth,
2459 sourceHeight,
2460 destX,
2461 destY,
2462 destWidth,
2463 destHeight
2464 );
2465 };
2466
2467 }));
2468
2469 /*global window, FileAPI */
2470
2471 (function (api, window){
2472 "use strict";
2473
2474 var
2475 document = window.document
2476 , FormData = window.FormData
2477 , Form = function (){ this.items = []; }
2478 , encodeURIComponent = window.encodeURIComponent
2479 ;
2480
2481
2482 Form.prototype = {
2483
2484 append: function (name, blob, file, type){
2485 this.items.push({
2486 name: name
2487 , blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2488 , file: blob && (file || blob.name)
2489 , type: blob && (type || blob.type)
2490 });
2491 },
2492
2493 each: function (fn){
2494 var i = 0, n = this.items.length;
2495 for( ; i < n; i++ ){
2496 fn.call(this, this.items[i]);
2497 }
2498 },
2499
2500 toData: function (fn, options){
2501 // allow chunked transfer if we have only one file to send
2502 // flag is used below and in XHR._send
2503 options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2504
2505 if( !api.support.html5 ){
2506 api.log('FileAPI.Form.toHtmlData');
2507 this.toHtmlData(fn);
2508 }
2509 else if( !api.formData || this.multipart || !FormData ){
2510 api.log('FileAPI.Form.toMultipartData');
2511 this.toMultipartData(fn);
2512 }
2513 else if( options._chunked ){
2514 api.log('FileAPI.Form.toPlainData');
2515 this.toPlainData(fn);
2516 }
2517 else {
2518 api.log('FileAPI.Form.toFormData');
2519 this.toFormData(fn);
2520 }
2521 },
2522
2523 _to: function (data, complete, next, arg){
2524 var queue = api.queue(function (){
2525 complete(data);
2526 });
2527
2528 this.each(function (file){
2529 next(file, data, queue, arg);
2530 });
2531
2532 queue.check();
2533 },
2534
2535
2536 toHtmlData: function (fn){
2537 this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2538 var blob = file.blob, hidden;
2539
2540 if( file.file ){
2541 api.reset(blob, true);
2542 // set new name
2543 blob.name = file.name;
2544 blob.disabled = false;
2545 data.appendChild(blob);
2546 }
2547 else {
2548 hidden = document.createElement('input');
2549 hidden.name = file.name;
2550 hidden.type = 'hidden';
2551 hidden.value = blob;
2552 data.appendChild(hidden);
2553 }
2554 });
2555 },
2556
2557 toPlainData: function (fn){
2558 this._to({}, fn, function (file, data, queue){
2559 if( file.file ){
2560 data.type = file.file;
2561 }
2562
2563 if( file.blob.toBlob ){
2564 // canvas
2565 queue.inc();
2566 _convertFile(file, function (file, blob){
2567 data.name = file.name;
2568 data.file = blob;
2569 data.size = blob.length;
2570 data.type = file.type;
2571 queue.next();
2572 });
2573 }
2574 else if( file.file ){
2575 // file
2576 data.name = file.blob.name;
2577 data.file = file.blob;
2578 data.size = file.blob.size;
2579 data.type = file.type;
2580 }
2581 else {
2582 // additional data
2583 if( !data.params ){
2584 data.params = [];
2585 }
2586 data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2587 }
2588
2589 data.start = -1;
2590 data.end = data.file && data.file.FileAPIReadPosition || -1;
2591 data.retry = 0;
2592 });
2593 },
2594
2595 toFormData: function (fn){
2596 this._to(new FormData, fn, function (file, data, queue){
2597 if( file.blob && file.blob.toBlob ){
2598 queue.inc();
2599 _convertFile(file, function (file, blob){
2600 data.append(file.name, blob, file.file);
2601 queue.next();
2602 });
2603 }
2604 else if( file.file ){
2605 data.append(file.name, file.blob, file.file);
2606 }
2607 else {
2608 data.append(file.name, file.blob);
2609 }
2610
2611 if( file.file ){
2612 data.append('_'+file.name, file.file);
2613 }
2614 });
2615 },
2616
2617
2618 toMultipartData: function (fn){
2619 this._to([], fn, function (file, data, queue, boundary){
2620 queue.inc();
2621 _convertFile(file, function (file, blob){
2622 data.push(
2623 '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2624 + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2625 + '\r\n'
2626 + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2627 + '\r\n')
2628 );
2629 queue.next();
2630 }, true);
2631 }, api.expando);
2632 }
2633 };
2634
2635
2636 function _convertFile(file, fn, useBinaryString){
2637 var blob = file.blob, filename = file.file;
2638
2639 if( filename ){
2640 if( !blob.toDataURL ){
2641 // The Blob is not an image.
2642 api.readAsBinaryString(blob, function (evt){
2643 if( evt.type == 'load' ){
2644 fn(file, evt.result);
2645 }
2646 });
2647 return;
2648 }
2649
2650 var
2651 mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2652 , type = mime[file.type] ? file.type : 'image/png'
2653 , ext = mime[type] || '.png'
2654 , quality = blob.quality || 1
2655 ;
2656
2657 if( !filename.match(new RegExp(ext+'$', 'i')) ){
2658 // Does not change the current extension, but add a new one.
2659 filename += ext.replace('?', '');
2660 }
2661
2662 file.file = filename;
2663 file.type = type;
2664
2665 if( !useBinaryString && blob.toBlob ){
2666 blob.toBlob(function (blob){
2667 fn(file, blob);
2668 }, type, quality);
2669 }
2670 else {
2671 fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2672 }
2673 }
2674 else {
2675 fn(file, blob);
2676 }
2677 }
2678
2679
2680 // @export
2681 api.Form = Form;
2682 })(FileAPI, window);
2683
2684 /*global window, FileAPI, Uint8Array */
2685
2686 (function (window, api){
2687 "use strict";
2688
2689 var
2690 noop = function (){}
2691 , document = window.document
2692
2693 , XHR = function (options){
2694 this.uid = api.uid();
2695 this.xhr = {
2696 abort: noop
2697 , getResponseHeader: noop
2698 , getAllResponseHeaders: noop
2699 };
2700 this.options = options;
2701 },
2702
2703 _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2704 ;
2705
2706
2707 XHR.prototype = {
2708 status: 0,
2709 statusText: '',
2710 constructor: XHR,
2711
2712 getResponseHeader: function (name){
2713 return this.xhr.getResponseHeader(name);
2714 },
2715
2716 getAllResponseHeaders: function (){
2717 return this.xhr.getAllResponseHeaders() || {};
2718 },
2719
2720 end: function (status, statusText){
2721 var _this = this, options = _this.options;
2722
2723 _this.end =
2724 _this.abort = noop;
2725 _this.status = status;
2726
2727 if( statusText ){
2728 _this.statusText = statusText;
2729 }
2730
2731 api.log('xhr.end:', status, statusText);
2732 options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2733
2734 if( _this.xhr && _this.xhr.node ){
2735 setTimeout(function (){
2736 var node = _this.xhr.node;
2737 try { node.parentNode.removeChild(node); } catch (e){}
2738 try { delete window[_this.uid]; } catch (e){}
2739 window[_this.uid] = _this.xhr.node = null;
2740 }, 9);
2741 }
2742 },
2743
2744 abort: function (){
2745 this.end(0, 'abort');
2746
2747 if( this.xhr ){
2748 this.xhr.aborted = true;
2749 this.xhr.abort();
2750 }
2751 },
2752
2753 send: function (FormData){
2754 var _this = this, options = this.options;
2755
2756 FormData.toData(function (data){
2757 // Start uploading
2758 options.upload(options, _this);
2759 _this._send.call(_this, options, data);
2760 }, options);
2761 },
2762
2763 _send: function (options, data){
2764 var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2765
2766 api.log('XHR._send:', data);
2767
2768 if( !options.cache ){
2769 // No cache
2770 url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2771 }
2772
2773 if( data.nodeName ){
2774 var jsonp = options.jsonp;
2775
2776 // prepare callback in GET
2777 url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2778
2779 // legacy
2780 options.upload(options, _this);
2781
2782 var
2783 onPostMessage = function (evt){
2784 if( ~url.indexOf(evt.origin) ){
2785 try {
2786 var result = api.parseJSON(evt.data);
2787 if( result.id == uid ){
2788 complete(result.status, result.statusText, result.response);
2789 }
2790 } catch( err ){
2791 complete(0, err.message);
2792 }
2793 }
2794 },
2795
2796 // jsonp-callack
2797 complete = window[uid] = function (status, statusText, response){
2798 _this.readyState = 4;
2799 _this.responseText = response;
2800 _this.end(status, statusText);
2801
2802 api.event.off(window, 'message', onPostMessage);
2803 window[uid] = xhr = transport = window[onloadFuncName] = null;
2804 }
2805 ;
2806
2807 _this.xhr.abort = function (){
2808 try {
2809 if( transport.stop ){ transport.stop(); }
2810 else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2811 else { transport.contentWindow.document.execCommand('Stop'); }
2812 }
2813 catch (er) {}
2814 complete(0, "abort");
2815 };
2816
2817 api.event.on(window, 'message', onPostMessage);
2818
2819 window[onloadFuncName] = function (){
2820 try {
2821 var
2822 win = transport.contentWindow
2823 , doc = win.document
2824 , result = win.result || api.parseJSON(doc.body.innerHTML)
2825 ;
2826 complete(result.status, result.statusText, result.response);
2827 } catch (e){
2828 api.log('[transport.onload]', e);
2829 }
2830 };
2831
2832 xhr = document.createElement('div');
2833 xhr.innerHTML = '<form target="'+ uid +'" action="'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2834 + '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2835 + (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2836 + '</form>'
2837 ;
2838
2839 // get form-data & transport
2840 var
2841 form = xhr.getElementsByTagName('form')[0]
2842 , transport = xhr.getElementsByTagName('iframe')[0]
2843 ;
2844
2845 form.appendChild(data);
2846
2847 api.log(form.parentNode.innerHTML);
2848
2849 // append to DOM
2850 document.body.appendChild(xhr);
2851
2852 // keep a reference to node-transport
2853 _this.xhr.node = xhr;
2854
2855 // send
2856 _this.readyState = 2; // loaded
2857 form.submit();
2858 form = null;
2859 }
2860 else {
2861 // Clean url
2862 url = url.replace(/([a-z]+)=(\?)&?/i, '');
2863
2864 // html5
2865 if (this.xhr && this.xhr.aborted) {
2866 api.log("Error: already aborted");
2867 return;
2868 }
2869 xhr = _this.xhr = api.getXHR();
2870
2871 if (data.params) {
2872 url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2873 }
2874
2875 xhr.open('POST', url, true);
2876
2877 if( api.withCredentials ){
2878 xhr.withCredentials = "true";
2879 }
2880
2881 if( !options.headers || !options.headers['X-Requested-With'] ){
2882 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2883 }
2884
2885 api.each(options.headers, function (val, key){
2886 xhr.setRequestHeader(key, val);
2887 });
2888
2889
2890 if ( options._chunked ) {
2891 // chunked upload
2892 if( xhr.upload ){
2893 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2894 if (!data.retry) {
2895 // show progress only for correct chunk uploads
2896 options.progress({
2897 type: evt.type
2898 , total: data.size
2899 , loaded: data.start + evt.loaded
2900 , totalSize: data.size
2901 }, _this, options);
2902 }
2903 }, 100), false);
2904 }
2905
2906 xhr.onreadystatechange = function (){
2907 var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2908
2909 _this.status = xhr.status;
2910 _this.statusText = xhr.statusText;
2911 _this.readyState = xhr.readyState;
2912
2913 if( xhr.readyState == 4 ){
2914 try {
2915 for( var k in _xhrResponsePostfix ){
2916 _this['response'+k] = xhr['response'+k];
2917 }
2918 }catch(_){}
2919 xhr.onreadystatechange = null;
2920
2921 if (!xhr.status || xhr.status - 201 > 0) {
2922 api.log("Error: " + xhr.status);
2923 // some kind of error
2924 // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2925 // up - server error
2926 if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2927 // let's try again the same chunk
2928 // only applicable for recoverable error codes 500 && 416
2929 var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2930
2931 // inform about recoverable problems
2932 options.pause(data.file, options);
2933
2934 // smart restart if server reports about the last known byte
2935 api.log("X-Last-Known-Byte: " + lkb);
2936 if (lkb) {
2937 data.end = lkb;
2938 } else {
2939 data.end = data.start - 1;
2940 if (416 == xhr.status) {
2941 data.end = data.end - options.chunkSize;
2942 }
2943 }
2944
2945 setTimeout(function () {
2946 _this._send(options, data);
2947 }, delay);
2948 } else {
2949 // no mo retries
2950 _this.end(xhr.status);
2951 }
2952 } else {
2953 // success
2954 data.retry = 0;
2955
2956 if (data.end == data.size - 1) {
2957 // finished
2958 _this.end(xhr.status);
2959 } else {
2960 // next chunk
2961
2962 // shift position if server reports about the last known byte
2963 api.log("X-Last-Known-Byte: " + lkb);
2964 if (lkb) {
2965 data.end = lkb;
2966 }
2967 data.file.FileAPIReadPosition = data.end;
2968
2969 setTimeout(function () {
2970 _this._send(options, data);
2971 }, 0);
2972 }
2973 }
2974
2975 xhr = null;
2976 }
2977 };
2978
2979 data.start = data.end + 1;
2980 data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2981
2982 // Retrieve a slice of file
2983 var
2984 file = data.file
2985 , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2986 ;
2987
2988 if( data.size && !slice.size ){
2989 setTimeout(function (){
2990 _this.end(-1);
2991 });
2992 } else {
2993 xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2994 xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2995 xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2996
2997 xhr.send(slice);
2998 }
2999
3000 file = slice = null;
3001 } else {
3002 // single piece upload
3003 if( xhr.upload ){
3004 // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3005 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3006 options.progress(evt, _this, options);
3007 }, 100), false);
3008 }
3009
3010 xhr.onreadystatechange = function (){
3011 _this.status = xhr.status;
3012 _this.statusText = xhr.statusText;
3013 _this.readyState = xhr.readyState;
3014
3015 if( xhr.readyState == 4 ){
3016 for( var k in _xhrResponsePostfix ){
3017 _this['response'+k] = xhr['response'+k];
3018 }
3019 xhr.onreadystatechange = null;
3020
3021 if (!xhr.status || xhr.status > 201) {
3022 api.log("Error: " + xhr.status);
3023 if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3024 options.retry = (options.retry || 0) + 1;
3025 var delay = api.networkDownRetryTimeout;
3026
3027 // inform about recoverable problems
3028 options.pause(options.file, options);
3029
3030 setTimeout(function () {
3031 _this._send(options, data);
3032 }, delay);
3033 } else {
3034 //success
3035 _this.end(xhr.status);
3036 }
3037 } else {
3038 //success
3039 _this.end(xhr.status);
3040 }
3041
3042 xhr = null;
3043 }
3044 };
3045
3046 if( api.isArray(data) ){
3047 // multipart
3048 xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3049 var rawData = data.join('') +'--_'+ api.expando +'--';
3050
3051 /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3052 if( xhr.sendAsBinary ){
3053 xhr.sendAsBinary(rawData);
3054 }
3055 else {
3056 var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3057 xhr.send(new Uint8Array(bytes).buffer);
3058
3059 }
3060 } else {
3061 // FormData
3062 xhr.send(data);
3063 }
3064 }
3065 }
3066 }
3067 };
3068
3069
3070 // @export
3071 api.XHR = XHR;
3072 })(window, FileAPI);
3073
3074 /**
3075 * @class FileAPI.Camera
3076 * @author RubaXa <trash@rubaxa.org>
3077 * @support Chrome 21+, FF 18+, Opera 12+
3078 */
3079
3080 /*global window, FileAPI, jQuery */
3081 /** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3082 (function (window, api){
3083 "use strict";
3084
3085 var
3086 URL = window.URL || window.webkitURL,
3087
3088 document = window.document,
3089 navigator = window.navigator,
3090
3091 getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3092
3093 html5 = !!getMedia
3094 ;
3095
3096
3097 // Support "media"
3098 api.support.media = html5;
3099
3100
3101 var Camera = function (video){
3102 this.video = video;
3103 };
3104
3105
3106 Camera.prototype = {
3107 isActive: function (){
3108 return !!this._active;
3109 },
3110
3111
3112 /**
3113 * Start camera streaming
3114 * @param {Function} callback
3115 */
3116 start: function (callback){
3117 var
3118 _this = this
3119 , video = _this.video
3120 , _successId
3121 , _failId
3122 , _complete = function (err){
3123 _this._active = !err;
3124 clearTimeout(_failId);
3125 clearTimeout(_successId);
3126 // api.event.off(video, 'loadedmetadata', _complete);
3127 callback && callback(err, _this);
3128 }
3129 ;
3130
3131 getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3132 // Success
3133 _this.stream = stream;
3134
3135 // api.event.on(video, 'loadedmetadata', function (){
3136 // _complete(null);
3137 // });
3138
3139 // Set camera stream
3140 video.src = URL.createObjectURL(stream);
3141
3142 // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3143 // See crbug.com/110938.
3144 _successId = setInterval(function (){
3145 if( _detectVideoSignal(video) ){
3146 _complete(null);
3147 }
3148 }, 1000);
3149
3150 _failId = setTimeout(function (){
3151 _complete('timeout');
3152 }, 5000);
3153
3154 // Go-go-go!
3155 video.play();
3156 }, _complete/*error*/);
3157 },
3158
3159
3160 /**
3161 * Stop camera streaming
3162 */
3163 stop: function (){
3164 try {
3165 this._active = false;
3166 this.video.pause();
3167 this.stream.stop();
3168 } catch( err ){ }
3169 },
3170
3171
3172 /**
3173 * Create screenshot
3174 * @return {FileAPI.Camera.Shot}
3175 */
3176 shot: function (){
3177 return new Shot(this.video);
3178 }
3179 };
3180
3181
3182 /**
3183 * Get camera element from container
3184 *
3185 * @static
3186 * @param {HTMLElement} el
3187 * @return {Camera}
3188 */
3189 Camera.get = function (el){
3190 return new Camera(el.firstChild);
3191 };
3192
3193
3194 /**
3195 * Publish camera element into container
3196 *
3197 * @static
3198 * @param {HTMLElement} el
3199 * @param {Object} options
3200 * @param {Function} [callback]
3201 */
3202 Camera.publish = function (el, options, callback){
3203 if( typeof options == 'function' ){
3204 callback = options;
3205 options = {};
3206 }
3207
3208 // Dimensions of "camera"
3209 options = api.extend({}, {
3210 width: '100%'
3211 , height: '100%'
3212 , start: true
3213 }, options);
3214
3215
3216 if( el.jquery ){
3217 // Extract first element, from jQuery collection
3218 el = el[0];
3219 }
3220
3221
3222 var doneFn = function (err){
3223 if( err ){
3224 callback(err);
3225 }
3226 else {
3227 // Get camera
3228 var cam = Camera.get(el);
3229 if( options.start ){
3230 cam.start(callback);
3231 }
3232 else {
3233 callback(null, cam);
3234 }
3235 }
3236 };
3237
3238
3239 el.style.width = _px(options.width);
3240 el.style.height = _px(options.height);
3241
3242
3243 if( api.html5 && html5 ){
3244 // Create video element
3245 var video = document.createElement('video');
3246
3247 // Set dimensions
3248 video.style.width = _px(options.width);
3249 video.style.height = _px(options.height);
3250
3251 // Clean container
3252 if( window.jQuery ){
3253 jQuery(el).empty();
3254 } else {
3255 el.innerHTML = '';
3256 }
3257
3258 // Add "camera" to container
3259 el.appendChild(video);
3260
3261 // end
3262 doneFn();
3263 }
3264 else {
3265 Camera.fallback(el, options, doneFn);
3266 }
3267 };
3268
3269
3270 Camera.fallback = function (el, options, callback){
3271 callback('not_support_camera');
3272 };
3273
3274
3275 /**
3276 * @class FileAPI.Camera.Shot
3277 */
3278 var Shot = function (video){
3279 var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
3280 var shot = api.Image(canvas);
3281 shot.type = 'image/png';
3282 shot.width = canvas.width;
3283 shot.height = canvas.height;
3284 shot.size = canvas.width * canvas.height * 4;
3285 return shot;
3286 };
3287
3288
3289 /**
3290 * Add "px" postfix, if value is a number
3291 *
3292 * @private
3293 * @param {*} val
3294 * @return {String}
3295 */
3296 function _px(val){
3297 return val >= 0 ? val + 'px' : val;
3298 }
3299
3300
3301 /**
3302 * @private
3303 * @param {HTMLVideoElement} video
3304 * @return {Boolean}
3305 */
3306 function _detectVideoSignal(video){
3307 var canvas = document.createElement('canvas'), ctx, res = false;
3308 try {
3309 ctx = canvas.getContext('2d');
3310 ctx.drawImage(video, 0, 0, 1, 1);
3311 res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3312 }
3313 catch( e ){}
3314 return res;
3315 }
3316
3317
3318 // @export
3319 Camera.Shot = Shot;
3320 api.Camera = Camera;
3321 })(window, FileAPI);
3322
3323 /**
3324 * FileAPI fallback to Flash
3325 *
3326 * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
3327 */
3328
3329 /*global window, ActiveXObject, FileAPI */
3330 (function (window, jQuery, api) {
3331 "use strict";
3332
3333 var
3334 document = window.document
3335 , location = window.location
3336 , navigator = window.navigator
3337 , _each = api.each
3338 ;
3339
3340
3341 api.support.flash = (function (){
3342 var mime = navigator.mimeTypes, has = false;
3343
3344 if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3345 has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3346 }
3347 else {
3348 try {
3349 has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3350 }
3351 catch(er){
3352 api.log('Flash -- does not supported.');
3353 }
3354 }
3355
3356 if( has && /^file:/i.test(location) ){
3357 api.log('[warn] Flash does not work on `file:` protocol.');
3358 }
3359
3360 return has;
3361 })();
3362
3363
3364 api.support.flash
3365 && (0
3366 || !api.html5 || !api.support.html5
3367 || (api.cors && !api.support.cors)
3368 || (api.media && !api.support.media)
3369 )
3370 && (function (){
3371 var
3372 _attr = api.uid()
3373 , _retry = 0
3374 , _files = {}
3375 , _rhttp = /^https?:/i
3376
3377 , flash = {
3378 _fn: {},
3379
3380
3381 /**
3382 * Publish flash-object
3383 *
3384 * @param {HTMLElement} el
3385 * @param {String} id
3386 * @param {Object} [opts]
3387 */
3388 publish: function (el, id, opts){
3389 opts = opts || {};
3390 el.innerHTML = _makeFlashHTML({
3391 id: id
3392 , src: _getUrl(api.flashUrl, 'r=' + api.version)
3393 // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3394 , wmode: opts.camera ? '' : 'transparent'
3395 , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3396 + '&flashId='+ id
3397 + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3398 + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3399 + '&timeout='+api.flashAbortTimeout
3400 + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3401 + '&debug='+(api.debug?"1":"")
3402 }, opts);
3403 },
3404
3405
3406 /**
3407 * Initialization & preload flash object
3408 */
3409 init: function (){
3410 var child = document.body && document.body.firstChild;
3411
3412 if( child ){
3413 do {
3414 if( child.nodeType == 1 ){
3415 api.log('FlashAPI.state: awaiting');
3416
3417 var dummy = document.createElement('div');
3418
3419 dummy.id = '_' + _attr;
3420
3421 _css(dummy, {
3422 top: 1
3423 , right: 1
3424 , width: 5
3425 , height: 5
3426 , position: 'absolute'
3427 , zIndex: 1e6+'' // set max zIndex
3428 });
3429
3430 child.parentNode.insertBefore(dummy, child);
3431 flash.publish(dummy, _attr);
3432
3433 return;
3434 }
3435 }
3436 while( child = child.nextSibling );
3437 }
3438
3439 if( _retry < 10 ){
3440 setTimeout(flash.init, ++_retry*50);
3441 }
3442 },
3443
3444
3445 ready: function (){
3446 api.log('FlashAPI.state: ready');
3447
3448 flash.ready = api.F;
3449 flash.isReady = true;
3450 flash.patch();
3451 flash.patchCamera && flash.patchCamera();
3452 api.event.on(document, 'mouseover', flash.mouseover);
3453 api.event.on(document, 'click', function (evt){
3454 if( flash.mouseover(evt) ){
3455 evt.preventDefault
3456 ? evt.preventDefault()
3457 : (evt.returnValue = true)
3458 ;
3459 }
3460 });
3461 },
3462
3463
3464 getEl: function (){
3465 return document.getElementById('_'+_attr);
3466 },
3467
3468
3469 getWrapper: function (node){
3470 do {
3471 if( /js-fileapi-wrapper/.test(node.className) ){
3472 return node;
3473 }
3474 }
3475 while( (node = node.parentNode) && (node !== document.body) );
3476 },
3477
3478 disableMouseover: false,
3479
3480 mouseover: function (evt){
3481 if (!flash.disableMouseover) {
3482 var target = api.event.fix(evt).target;
3483
3484 if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3485 var
3486 state = target.getAttribute(_attr)
3487 , wrapper = flash.getWrapper(target)
3488 ;
3489
3490 if( api.multiFlash ){
3491 // check state:
3492 // i — published
3493 // i — initialization
3494 // r — ready
3495 if( state == 'i' || state == 'r' ){
3496 // publish fail
3497 return false;
3498 }
3499 else if( state != 'p' ){
3500 // set "init" state
3501 target.setAttribute(_attr, 'i');
3502
3503 var dummy = document.createElement('div');
3504
3505 if( !wrapper ){
3506 api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3507 return;
3508 }
3509
3510 _css(dummy, {
3511 top: 0
3512 , left: 0
3513 , width: target.offsetWidth
3514 , height: target.offsetHeight
3515 , zIndex: 1e6+'' // set max zIndex
3516 , position: 'absolute'
3517 });
3518
3519 wrapper.appendChild(dummy);
3520 flash.publish(dummy, api.uid());
3521
3522 // set "publish" state
3523 target.setAttribute(_attr, 'p');
3524 }
3525
3526 return true;
3527 }
3528 else if( wrapper ){
3529 // Use one flash element
3530 var box = _getDimensions(wrapper);
3531 _css(flash.getEl(), box);
3532
3533 // Set current input
3534 flash.curInp = target;
3535 }
3536 }
3537 else if( !/object|embed/i.test(target.nodeName) ){
3538 _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3539 }
3540 }
3541 },
3542
3543 onEvent: function (evt){
3544 var type = evt.type;
3545
3546 if( type == 'ready' ){
3547 try {
3548 // set "ready" state
3549 flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3550 } catch (e){
3551 }
3552
3553 flash.ready();
3554 setTimeout(function (){ flash.mouseenter(evt); }, 50);
3555 return true;
3556 }
3557 else if( type === 'ping' ){
3558 api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3559 }
3560 else if( type === 'log' ){
3561 api.log('(flash -> js).log:', evt.target);
3562 }
3563 else if( type in flash ){
3564 setTimeout(function (){
3565 api.log('FlashAPI.event.'+evt.type+':', evt);
3566 flash[type](evt);
3567 }, 1);
3568 }
3569 },
3570 mouseDown: function(evt) {
3571 flash.disableMouseover = true;
3572 },
3573 cancel: function(evt) {
3574 flash.disableMouseover = false;
3575 },
3576 mouseenter: function (evt){
3577 var node = flash.getInput(evt.flashId);
3578
3579 if( node ){
3580 // Set multiple mode
3581 flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3582
3583
3584 // Set files filter
3585 var accept = [], exts = {};
3586
3587 _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3588 api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3589 exts[ext] = 1;
3590 });
3591 });
3592
3593 _each(exts, function (i, ext){
3594 accept.push( ext );
3595 });
3596
3597 flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3598 }
3599 },
3600
3601
3602 get: function (id){
3603 return document[id] || window[id] || document.embeds[id];
3604 },
3605
3606
3607 getInput: function (id){
3608 if( api.multiFlash ){
3609 try {
3610 var node = flash.getWrapper(flash.get(id));
3611 if( node ){
3612 return node.getElementsByTagName('input')[0];
3613 }
3614 } catch (e){
3615 api.log('[err] Can not find "input" by flashId:', id, e);
3616 }
3617 } else {
3618 return flash.curInp;
3619 }
3620 },
3621
3622
3623 select: function (evt){
3624 try {
3625 var
3626 inp = flash.getInput(evt.flashId)
3627 , uid = api.uid(inp)
3628 , files = evt.target.files
3629 , event
3630 ;
3631 _each(files, function (file){
3632 api.checkFileObj(file);
3633 });
3634
3635 _files[uid] = files;
3636
3637 if( document.createEvent ){
3638 event = document.createEvent('Event');
3639 event.files = files;
3640 event.initEvent('change', true, true);
3641 inp.dispatchEvent(event);
3642 }
3643 else if( jQuery ){
3644 jQuery(inp).trigger({ type: 'change', files: files });
3645 }
3646 else {
3647 event = document.createEventObject();
3648 event.files = files;
3649 inp.fireEvent('onchange', event);
3650 }
3651 } finally {
3652 flash.disableMouseover = false;
3653 }
3654 },
3655
3656 interval: null,
3657 cmd: function (id, name, data, last) {
3658 if (flash.uploadInProgress && flash.readInProgress) {
3659 setTimeout(function() {
3660 flash.cmd(id, name, data, last);
3661 }, 100);
3662 } else {
3663 this.cmdFn(id, name, data, last);
3664 }
3665 },
3666
3667 cmdFn: function(id, name, data, last) {
3668 try {
3669 api.log('(js -> flash).'+name+':', data);
3670 return flash.get(id.flashId || id).cmd(name, data);
3671 } catch (e){
3672 api.log('(js -> flash).onError:', e);
3673 if( !last ){
3674 // try again
3675 setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3676 }
3677 }
3678 },
3679
3680 patch: function (){
3681 api.flashEngine = true;
3682
3683 // FileAPI
3684 _inherit(api, {
3685 readAsDataURL: function (file, callback){
3686 if( _isHtmlFile(file) ){
3687 this.parent.apply(this, arguments);
3688 }
3689 else {
3690 api.log('FlashAPI.readAsBase64');
3691 flash.readInProgress = true;
3692 flash.cmd(file, 'readAsBase64', {
3693 id: file.id,
3694 callback: _wrap(function _(err, base64){
3695 flash.readInProgress = false;
3696 _unwrap(_);
3697
3698 api.log('FlashAPI.readAsBase64:', err);
3699
3700 callback({
3701 type: err ? 'error' : 'load'
3702 , error: err
3703 , result: 'data:'+ file.type +';base64,'+ base64
3704 });
3705 })
3706 });
3707 }
3708 },
3709
3710 readAsText: function (file, encoding, callback){
3711 if( callback ){
3712 api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3713 } else {
3714 callback = encoding;
3715 }
3716
3717 api.readAsDataURL(file, function (evt){
3718 if( evt.type == 'load' ){
3719 try {
3720 evt.result = window.atob(evt.result.split(';base64,')[1]);
3721 } catch( err ){
3722 evt.type = 'error';
3723 evt.error = err.toString();
3724 }
3725 }
3726 callback(evt);
3727 });
3728 },
3729
3730 getFiles: function (input, filter, callback){
3731 if( callback ){
3732 api.filterFiles(api.getFiles(input), filter, callback);
3733 return null;
3734 }
3735
3736 var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3737
3738
3739 if( !files ){
3740 // Файлов нету, вызываем родительский метод
3741 return this.parent.apply(this, arguments);
3742 }
3743
3744
3745 if( filter ){
3746 filter = api.getFilesFilter(filter);
3747 files = api.filter(files, function (file){ return filter.test(file.name); });
3748 }
3749
3750 return files;
3751 },
3752
3753
3754 getInfo: function (file, fn){
3755 if( _isHtmlFile(file) ){
3756 this.parent.apply(this, arguments);
3757 }
3758 else if( file.isShot ){
3759 fn(null, file.info = {
3760 width: file.width,
3761 height: file.height
3762 });
3763 }
3764 else {
3765 if( !file.__info ){
3766 var defer = file.__info = api.defer();
3767
3768 // flash.cmd(file, 'getFileInfo', {
3769 // id: file.id
3770 // , callback: _wrap(function _(err, info){
3771 // _unwrap(_);
3772 // defer.resolve(err, file.info = info);
3773 // })
3774 // });
3775 defer.resolve(null, file.info = null);
3776
3777 }
3778
3779 file.__info.then(fn);
3780 }
3781 }
3782 });
3783
3784
3785 // FileAPI.Image
3786 api.support.transform = true;
3787 api.Image && _inherit(api.Image.prototype, {
3788 get: function (fn, scaleMode){
3789 this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3790 return this.parent(fn);
3791 },
3792
3793 _load: function (file, fn){
3794 api.log('FlashAPI.Image._load:', file);
3795
3796 if( _isHtmlFile(file) ){
3797 this.parent.apply(this, arguments);
3798 }
3799 else {
3800 var _this = this;
3801 api.getInfo(file, function (err){
3802 fn.call(_this, err, file);
3803 });
3804 }
3805 },
3806
3807 _apply: function (file, fn){
3808 api.log('FlashAPI.Image._apply:', file);
3809
3810 if( _isHtmlFile(file) ){
3811 this.parent.apply(this, arguments);
3812 }
3813 else {
3814 var m = this.getMatrix(file.info), doneFn = fn;
3815
3816 flash.cmd(file, 'imageTransform', {
3817 id: file.id
3818 , matrix: m
3819 , callback: _wrap(function _(err, base64){
3820 api.log('FlashAPI.Image._apply.callback:', err);
3821 _unwrap(_);
3822
3823 if( err ){
3824 doneFn(err);
3825 }
3826 else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3827 _makeFlashImage({
3828 width: (m.deg % 180) ? m.dh : m.dw
3829 , height: (m.deg % 180) ? m.dw : m.dh
3830 , scale: m.scaleMode
3831 }, base64, doneFn);
3832 }
3833 else {
3834 if( m.filter ){
3835 doneFn = function (err, img){
3836 if( err ){
3837 fn(err);
3838 }
3839 else {
3840 api.Image.applyFilter(img, m.filter, function (){
3841 fn(err, this.canvas);
3842 });
3843 }
3844 };
3845 }
3846
3847 api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3848 }
3849 })
3850 });
3851 }
3852 },
3853
3854 toData: function (fn){
3855 var
3856 file = this.file
3857 , info = file.info
3858 , matrix = this.getMatrix(info)
3859 ;
3860 api.log('FlashAPI.Image.toData');
3861
3862 if( _isHtmlFile(file) ){
3863 this.parent.apply(this, arguments);
3864 }
3865 else {
3866 if( matrix.deg == 'auto' ){
3867 matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3868 }
3869
3870 fn.call(this, !file.info, {
3871 id: file.id
3872 , flashId: file.flashId
3873 , name: file.name
3874 , type: file.type
3875 , matrix: matrix
3876 });
3877 }
3878 }
3879 });
3880
3881
3882 api.Image && _inherit(api.Image, {
3883 fromDataURL: function (dataURL, size, callback){
3884 if( !api.support.dataURI || dataURL.length > 3e4 ){
3885 _makeFlashImage(
3886 api.extend({ scale: 'exactFit' }, size)
3887 , dataURL.replace(/^data:[^,]+,/, '')
3888 , function (err, el){ callback(el); }
3889 );
3890 }
3891 else {
3892 this.parent(dataURL, size, callback);
3893 }
3894 }
3895 });
3896
3897 // FileAPI.Form
3898 _inherit(api.Form.prototype, {
3899 toData: function (fn){
3900 var items = this.items, i = items.length;
3901
3902 for( ; i--; ){
3903 if( items[i].file && _isHtmlFile(items[i].blob) ){
3904 return this.parent.apply(this, arguments);
3905 }
3906 }
3907
3908 api.log('FlashAPI.Form.toData');
3909 fn(items);
3910 }
3911 });
3912
3913
3914 // FileAPI.XHR
3915 _inherit(api.XHR.prototype, {
3916 _send: function (options, formData){
3917 if(
3918 formData.nodeName
3919 || formData.append && api.support.html5
3920 || api.isArray(formData) && (typeof formData[0] === 'string')
3921 ){
3922 // HTML5, Multipart or IFrame
3923 return this.parent.apply(this, arguments);
3924 }
3925
3926
3927 var
3928 data = {}
3929 , files = {}
3930 , _this = this
3931 , flashId
3932 , fileId
3933 ;
3934
3935 _each(formData, function (item){
3936 if( item.file ){
3937 files[item.name] = item = _getFileDescr(item.blob);
3938 fileId = item.id;
3939 flashId = item.flashId;
3940 }
3941 else {
3942 data[item.name] = item.blob;
3943 }
3944 });
3945
3946 if( !fileId ){
3947 flashId = _attr;
3948 }
3949
3950 if( !flashId ){
3951 api.log('[err] FlashAPI._send: flashId -- undefined');
3952 return this.parent.apply(this, arguments);
3953 }
3954 else {
3955 api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3956 }
3957
3958 _this.xhr = {
3959 headers: {},
3960 abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3961 getResponseHeader: function (name){ return this.headers[name]; },
3962 getAllResponseHeaders: function (){ return this.headers; }
3963 };
3964
3965 var queue = api.queue(function (){
3966 flash.uploadInProgress = true;
3967 flash.cmd(flashId, 'upload', {
3968 url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3969 , data: data
3970 , files: fileId ? files : null
3971 , headers: options.headers || {}
3972 , callback: _wrap(function upload(evt){
3973 var type = evt.type, result = evt.result;
3974
3975 api.log('FlashAPI.upload.'+type);
3976
3977 if( type == 'progress' ){
3978 evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3979 evt.lengthComputable = true;
3980 options.progress(evt);
3981 }
3982 else if( type == 'complete' ){
3983 flash.uploadInProgress = false;
3984 _unwrap(upload);
3985
3986 if( typeof result == 'string' ){
3987 _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3988 }
3989
3990 _this.end(evt.status || 200);
3991 }
3992 else if( type == 'abort' || type == 'error' ){
3993 flash.uploadInProgress = false;
3994 _this.end(evt.status || 0, evt.message);
3995 _unwrap(upload);
3996 }
3997 })
3998 });
3999 });
4000
4001
4002 // #2174: FileReference.load() call while FileReference.upload() or vice versa
4003 _each(files, function (file){
4004 queue.inc();
4005 api.getInfo(file, queue.next);
4006 });
4007
4008 queue.check();
4009 }
4010 });
4011 }
4012 }
4013 ;
4014
4015
4016 function _makeFlashHTML(opts){
4017 return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4018 + '<param name="movie" value="#src#" />'
4019 + '<param name="flashvars" value="#flashvars#" />'
4020 + '<param name="swliveconnect" value="true" />'
4021 + '<param name="allowscriptaccess" value="always" />'
4022 + '<param name="allownetworking" value="all" />'
4023 + '<param name="menu" value="false" />'
4024 + '<param name="wmode" value="#wmode#" />'
4025 + '<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>'
4026 + '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4027 ;
4028 }
4029
4030
4031 function _css(el, css){
4032 if( el && el.style ){
4033 var key, val;
4034 for( key in css ){
4035 val = css[key];
4036 if( typeof val == 'number' ){
4037 val += 'px';
4038 }
4039 try { el.style[key] = val; } catch (e) {}
4040 }
4041
4042 }
4043 }
4044
4045
4046 function _inherit(obj, methods){
4047 _each(methods, function (fn, name){
4048 var prev = obj[name];
4049 obj[name] = function (){
4050 this.parent = prev;
4051 return fn.apply(this, arguments);
4052 };
4053 });
4054 }
4055
4056 function _isHtmlFile(file){
4057 return file && !file.flashId;
4058 }
4059
4060 function _wrap(fn){
4061 var id = fn.wid = api.uid();
4062 flash._fn[id] = fn;
4063 return 'FileAPI.Flash._fn.'+id;
4064 }
4065
4066
4067 function _unwrap(fn){
4068 try {
4069 flash._fn[fn.wid] = null;
4070 delete flash._fn[fn.wid];
4071 }
4072 catch(e){}
4073 }
4074
4075
4076 function _getUrl(url, params){
4077 if( !_rhttp.test(url) ){
4078 if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4079 var path = location.pathname;
4080 path = path.substr(0, path.lastIndexOf('/'));
4081 url = (path +'/'+ url).replace('/./', '/');
4082 }
4083
4084 if( '//' != url.substr(0, 2) ){
4085 url = '//' + location.host + url;
4086 }
4087
4088 if( !_rhttp.test(url) ){
4089 url = location.protocol + url;
4090 }
4091 }
4092
4093 if( params ){
4094 url += (/\?/.test(url) ? '&' : '?') + params;
4095 }
4096
4097 return url;
4098 }
4099
4100
4101 function _makeFlashImage(opts, base64, fn){
4102 var
4103 key
4104 , flashId = api.uid()
4105 , el = document.createElement('div')
4106 , attempts = 10
4107 ;
4108
4109 for( key in opts ){
4110 el.setAttribute(key, opts[key]);
4111 el[key] = opts[key];
4112 }
4113
4114 _css(el, opts);
4115
4116 opts.width = '100%';
4117 opts.height = '100%';
4118
4119 el.innerHTML = _makeFlashHTML(api.extend({
4120 id: flashId
4121 , src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4122 , wmode: 'opaque'
4123 , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4124 _unwrap(_);
4125 if( --attempts > 0 ){
4126 _setImage();
4127 }
4128 return true;
4129 })
4130 }, opts));
4131
4132 function _setImage(){
4133 try {
4134 // Get flash-object by id
4135 var img = flash.get(flashId);
4136 img.setImage(base64);
4137 } catch (e){
4138 api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4139 }
4140 }
4141
4142 fn(false, el);
4143 el = null;
4144 }
4145
4146
4147 function _getFileDescr(file){
4148 return {
4149 id: file.id
4150 , name: file.name
4151 , matrix: file.matrix
4152 , flashId: file.flashId
4153 };
4154 }
4155
4156
4157 function _getDimensions(el){
4158 var
4159 box = el.getBoundingClientRect()
4160 , body = document.body
4161 , docEl = (el && el.ownerDocument).documentElement
4162 ;
4163
4164 function getOffset(obj) {
4165 var left, top;
4166 left = top = 0;
4167 if (obj.offsetParent) {
4168 do {
4169 left += obj.offsetLeft;
4170 top += obj.offsetTop;
4171 } while (obj = obj.offsetParent);
4172 }
4173 return {
4174 left : left,
4175 top : top
4176 };
4177 };
4178
4179 return {
4180 top: getOffset(el).top
4181 , left: getOffset(el).left
4182 , width: el.offsetWidth
4183 , height: el.offsetHeight
4184 };
4185 }
4186
4187 // @export
4188 api.Flash = flash;
4189
4190
4191 // Check dataURI support
4192 api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4193 api.support.dataURI = !(img.width != 1 || img.height != 1);
4194 flash.init();
4195 });
4196 })();
4197 })(window, window.jQuery, FileAPI);
4198
4199 /**
4200 * FileAPI fallback to Flash
4201 *
4202 * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
4203 */
4204
4205 /*global window, FileAPI */
4206 (function (window, jQuery, api) {
4207 "use strict";
4208
4209 var _each = api.each,
4210 _cameraQueue = [];
4211
4212
4213 if (api.support.flash && (api.media && !api.support.media)) {
4214 (function () {
4215
4216 function _wrap(fn) {
4217 var id = fn.wid = api.uid();
4218 api.Flash._fn[id] = fn;
4219 return 'FileAPI.Flash._fn.' + id;
4220 }
4221
4222
4223 function _unwrap(fn) {
4224 try {
4225 api.Flash._fn[fn.wid] = null;
4226 delete api.Flash._fn[fn.wid];
4227 } catch (e) {
4228 }
4229 }
4230
4231 var flash = api.Flash;
4232 api.extend(api.Flash, {
4233
4234 patchCamera: function () {
4235 api.Camera.fallback = function (el, options, callback) {
4236 var camId = api.uid();
4237 api.log('FlashAPI.Camera.publish: ' + camId);
4238 flash.publish(el, camId, api.extend(options, {
4239 camera: true,
4240 onEvent: _wrap(function _(evt) {
4241 if (evt.type === 'camera') {
4242 _unwrap(_);
4243
4244 if (evt.error) {
4245 api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4246 callback(evt.error);
4247 } else {
4248 api.log('FlashAPI.Camera.publish.success: ' + camId);
4249 callback(null);
4250 }
4251 }
4252 })
4253 }));
4254 };
4255 // Run
4256 _each(_cameraQueue, function (args) {
4257 api.Camera.fallback.apply(api.Camera, args);
4258 });
4259 _cameraQueue = [];
4260
4261
4262 // FileAPI.Camera:proto
4263 api.extend(api.Camera.prototype, {
4264 _id: function () {
4265 return this.video.id;
4266 },
4267
4268 start: function (callback) {
4269 var _this = this;
4270 flash.cmd(this._id(), 'camera.on', {
4271 callback: _wrap(function _(evt) {
4272 _unwrap(_);
4273
4274 if (evt.error) {
4275 api.log('FlashAPI.camera.on.error: ' + evt.error);
4276 callback(evt.error, _this);
4277 } else {
4278 api.log('FlashAPI.camera.on.success: ' + _this._id());
4279 _this._active = true;
4280 callback(null, _this);
4281 }
4282 })
4283 });
4284 },
4285
4286 stop: function () {
4287 this._active = false;
4288 flash.cmd(this._id(), 'camera.off');
4289 },
4290
4291 shot: function () {
4292 api.log('FlashAPI.Camera.shot:', this._id());
4293
4294 var shot = api.Flash.cmd(this._id(), 'shot', {});
4295 shot.type = 'image/png';
4296 shot.flashId = this._id();
4297 shot.isShot = true;
4298
4299 return new api.Camera.Shot(shot);
4300 }
4301 });
4302 }
4303 });
4304
4305 api.Camera.fallback = function () {
4306 _cameraQueue.push(arguments);
4307 };
4308
4309 }());
4310 }
4311 }(window, window.jQuery, FileAPI));
4312 if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
+0
-2801
demo/src/main/webapp/js/ng-file-upload-all.js less more
0 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
2 * progress, resize, thumbnail, preview, validation and CORS
3 * FileAPI Flash shim for old browsers not supporting FormData
4 * @author Danial <danial.farid@gmail.com>
5 * @version 12.0.4
6 */
7
8 (function () {
9 /** @namespace FileAPI.noContentTimeout */
10
11 function patchXHR(fnName, newFn) {
12 window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
13 }
14
15 function redefineProp(xhr, prop, fn) {
16 try {
17 Object.defineProperty(xhr, prop, {get: fn});
18 } catch (e) {/*ignore*/
19 }
20 }
21
22 if (!window.FileAPI) {
23 window.FileAPI = {};
24 }
25
26 if (!window.XMLHttpRequest) {
27 throw 'AJAX is not supported. XMLHttpRequest is not defined.';
28 }
29
30 FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
31 if (FileAPI.shouldLoad) {
32 var initializeUploadListener = function (xhr) {
33 if (!xhr.__listeners) {
34 if (!xhr.upload) xhr.upload = {};
35 xhr.__listeners = [];
36 var origAddEventListener = xhr.upload.addEventListener;
37 xhr.upload.addEventListener = function (t, fn) {
38 xhr.__listeners[t] = fn;
39 if (origAddEventListener) origAddEventListener.apply(this, arguments);
40 };
41 }
42 };
43
44 patchXHR('open', function (orig) {
45 return function (m, url, b) {
46 initializeUploadListener(this);
47 this.__url = url;
48 try {
49 orig.apply(this, [m, url, b]);
50 } catch (e) {
51 if (e.message.indexOf('Access is denied') > -1) {
52 this.__origError = e;
53 orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
54 }
55 }
56 };
57 });
58
59 patchXHR('getResponseHeader', function (orig) {
60 return function (h) {
61 return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
62 };
63 });
64
65 patchXHR('getAllResponseHeaders', function (orig) {
66 return function () {
67 return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
68 };
69 });
70
71 patchXHR('abort', function (orig) {
72 return function () {
73 return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
74 };
75 });
76
77 patchXHR('setRequestHeader', function (orig) {
78 return function (header, value) {
79 if (header === '__setXHR_') {
80 initializeUploadListener(this);
81 var val = value(this);
82 // fix for angular < 1.2.0
83 if (val instanceof Function) {
84 val(this);
85 }
86 } else {
87 this.__requestHeaders = this.__requestHeaders || {};
88 this.__requestHeaders[header] = value;
89 orig.apply(this, arguments);
90 }
91 };
92 });
93
94 patchXHR('send', function (orig) {
95 return function () {
96 var xhr = this;
97 if (arguments[0] && arguments[0].__isFileAPIShim) {
98 var formData = arguments[0];
99 var config = {
100 url: xhr.__url,
101 jsonp: false, //removes the callback form param
102 cache: true, //removes the ?fileapiXXX in the url
103 complete: function (err, fileApiXHR) {
104 if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
105 // this error seems to be fine the file is being uploaded properly.
106 err = null;
107 }
108 xhr.__completed = true;
109 if (!err && xhr.__listeners.load)
110 xhr.__listeners.load({
111 type: 'load',
112 loaded: xhr.__loaded,
113 total: xhr.__total,
114 target: xhr,
115 lengthComputable: true
116 });
117 if (!err && xhr.__listeners.loadend)
118 xhr.__listeners.loadend({
119 type: 'loadend',
120 loaded: xhr.__loaded,
121 total: xhr.__total,
122 target: xhr,
123 lengthComputable: true
124 });
125 if (err === 'abort' && xhr.__listeners.abort)
126 xhr.__listeners.abort({
127 type: 'abort',
128 loaded: xhr.__loaded,
129 total: xhr.__total,
130 target: xhr,
131 lengthComputable: true
132 });
133 if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
134 return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
135 });
136 if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
137 return fileApiXHR.statusText;
138 });
139 redefineProp(xhr, 'readyState', function () {
140 return 4;
141 });
142 if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
143 return fileApiXHR.response;
144 });
145 var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
146 redefineProp(xhr, 'responseText', function () {
147 return resp;
148 });
149 redefineProp(xhr, 'response', function () {
150 return resp;
151 });
152 if (err) redefineProp(xhr, 'err', function () {
153 return err;
154 });
155 xhr.__fileApiXHR = fileApiXHR;
156 if (xhr.onreadystatechange) xhr.onreadystatechange();
157 if (xhr.onload) xhr.onload();
158 },
159 progress: function (e) {
160 e.target = xhr;
161 if (xhr.__listeners.progress) xhr.__listeners.progress(e);
162 xhr.__total = e.total;
163 xhr.__loaded = e.loaded;
164 if (e.total === e.loaded) {
165 // fix flash issue that doesn't call complete if there is no response text from the server
166 var _this = this;
167 setTimeout(function () {
168 if (!xhr.__completed) {
169 xhr.getAllResponseHeaders = function () {
170 };
171 _this.complete(null, {status: 204, statusText: 'No Content'});
172 }
173 }, FileAPI.noContentTimeout || 10000);
174 }
175 },
176 headers: xhr.__requestHeaders
177 };
178 config.data = {};
179 config.files = {};
180 for (var i = 0; i < formData.data.length; i++) {
181 var item = formData.data[i];
182 if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
183 config.files[item.key] = item.val;
184 } else {
185 config.data[item.key] = item.val;
186 }
187 }
188
189 setTimeout(function () {
190 if (!FileAPI.hasFlash) {
191 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
192 }
193 xhr.__fileApiXHR = FileAPI.upload(config);
194 }, 1);
195 } else {
196 if (this.__origError) {
197 throw this.__origError;
198 }
199 orig.apply(xhr, arguments);
200 }
201 };
202 });
203 window.XMLHttpRequest.__isFileAPIShim = true;
204 window.FormData = FormData = function () {
205 return {
206 append: function (key, val, name) {
207 if (val.__isFileAPIBlobShim) {
208 val = val.data[0];
209 }
210 this.data.push({
211 key: key,
212 val: val,
213 name: name
214 });
215 },
216 data: [],
217 __isFileAPIShim: true
218 };
219 };
220
221 window.Blob = Blob = function (b) {
222 return {
223 data: b,
224 __isFileAPIBlobShim: true
225 };
226 };
227 }
228
229 })();
230
231 (function () {
232 /** @namespace FileAPI.forceLoad */
233 /** @namespace window.FileAPI.jsUrl */
234 /** @namespace window.FileAPI.jsPath */
235
236 function isInputTypeFile(elem) {
237 return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
238 }
239
240 function hasFlash() {
241 try {
242 var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
243 if (fo) return true;
244 } catch (e) {
245 if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
246 }
247 return false;
248 }
249
250 function getOffset(obj) {
251 var left = 0, top = 0;
252
253 if (window.jQuery) {
254 return jQuery(obj).offset();
255 }
256
257 if (obj.offsetParent) {
258 do {
259 left += (obj.offsetLeft - obj.scrollLeft);
260 top += (obj.offsetTop - obj.scrollTop);
261 obj = obj.offsetParent;
262 } while (obj);
263 }
264 return {
265 left: left,
266 top: top
267 };
268 }
269
270 if (FileAPI.shouldLoad) {
271 FileAPI.hasFlash = hasFlash();
272
273 //load FileAPI
274 if (FileAPI.forceLoad) {
275 FileAPI.html5 = false;
276 }
277
278 if (!FileAPI.upload) {
279 var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
280 if (window.FileAPI.jsUrl) {
281 jsUrl = window.FileAPI.jsUrl;
282 } else if (window.FileAPI.jsPath) {
283 basePath = window.FileAPI.jsPath;
284 } else {
285 for (i = 0; i < allScripts.length; i++) {
286 src = allScripts[i].src;
287 index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
288 if (index > -1) {
289 basePath = src.substring(0, index + 1);
290 break;
291 }
292 }
293 }
294
295 if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
296 script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
297 document.getElementsByTagName('head')[0].appendChild(script);
298 }
299
300 FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
301 if (!hasFlash()) {
302 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
303 }
304 var fixInputStyle = function () {
305 var label = fileElem.parent();
306 if (elem.attr('disabled')) {
307 if (label) label.removeClass('js-fileapi-wrapper');
308 } else {
309 if (!fileElem.attr('__ngf_flash_')) {
310 fileElem.unbind('change');
311 fileElem.unbind('click');
312 fileElem.bind('change', function (evt) {
313 fileApiChangeFn.apply(this, [evt]);
314 changeFn.apply(this, [evt]);
315 });
316 fileElem.attr('__ngf_flash_', 'true');
317 }
318 label.addClass('js-fileapi-wrapper');
319 if (!isInputTypeFile(elem)) {
320 label.css('position', 'absolute')
321 .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
322 .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
323 .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
324 .css('overflow', 'hidden').css('z-index', '900000')
325 .css('visibility', 'visible');
326 fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
327 .css('position', 'absolute').css('top', '0px').css('left', '0px');
328 }
329 }
330 };
331
332 elem.bind('mouseenter', fixInputStyle);
333
334 var fileApiChangeFn = function (evt) {
335 var files = FileAPI.getFiles(evt);
336 //just a double check for #233
337 for (var i = 0; i < files.length; i++) {
338 if (files[i].size === undefined) files[i].size = 0;
339 if (files[i].name === undefined) files[i].name = 'file';
340 if (files[i].type === undefined) files[i].type = 'undefined';
341 }
342 if (!evt.target) {
343 evt.target = {};
344 }
345 evt.target.files = files;
346 // if evt.target.files is not writable use helper field
347 if (evt.target.files !== files) {
348 evt.__files_ = files;
349 }
350 (evt.__files_ || evt.target.files).item = function (i) {
351 return (evt.__files_ || evt.target.files)[i] || null;
352 };
353 };
354 };
355
356 FileAPI.disableFileInput = function (elem, disable) {
357 if (disable) {
358 elem.removeClass('js-fileapi-wrapper');
359 } else {
360 elem.addClass('js-fileapi-wrapper');
361 }
362 };
363 }
364 })();
365
366 if (!window.FileReader) {
367 window.FileReader = function () {
368 var _this = this, loadStarted = false;
369 this.listeners = {};
370 this.addEventListener = function (type, fn) {
371 _this.listeners[type] = _this.listeners[type] || [];
372 _this.listeners[type].push(fn);
373 };
374 this.removeEventListener = function (type, fn) {
375 if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
376 };
377 this.dispatchEvent = function (evt) {
378 var list = _this.listeners[evt.type];
379 if (list) {
380 for (var i = 0; i < list.length; i++) {
381 list[i].call(_this, evt);
382 }
383 }
384 };
385 this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
386
387 var constructEvent = function (type, evt) {
388 var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
389 if (evt.result != null) e.target.result = evt.result;
390 return e;
391 };
392 var listener = function (evt) {
393 if (!loadStarted) {
394 loadStarted = true;
395 if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
396 }
397 var e;
398 if (evt.type === 'load') {
399 if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
400 e = constructEvent('load', evt);
401 if (_this.onload) _this.onload(e);
402 _this.dispatchEvent(e);
403 } else if (evt.type === 'progress') {
404 e = constructEvent('progress', evt);
405 if (_this.onprogress) _this.onprogress(e);
406 _this.dispatchEvent(e);
407 } else {
408 e = constructEvent('error', evt);
409 if (_this.onerror) _this.onerror(e);
410 _this.dispatchEvent(e);
411 }
412 };
413 this.readAsDataURL = function (file) {
414 FileAPI.readAsDataURL(file, listener);
415 };
416 this.readAsText = function (file) {
417 FileAPI.readAsText(file, listener);
418 };
419 };
420 }
421
422 /**!
423 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
424 * progress, resize, thumbnail, preview, validation and CORS
425 * @author Danial <danial.farid@gmail.com>
426 * @version 12.0.4
427 */
428
429 if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
430 window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {
431 return function (header, value) {
432 if (header === '__setXHR_') {
433 var val = value(this);
434 // fix for angular < 1.2.0
435 if (val instanceof Function) {
436 val(this);
437 }
438 } else {
439 orig.apply(this, arguments);
440 }
441 };
442 })(window.XMLHttpRequest.prototype.setRequestHeader);
443 }
444
445 var ngFileUpload = angular.module('ngFileUpload', []);
446
447 ngFileUpload.version = '12.0.4';
448
449 ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
450 var upload = this;
451 upload.promisesCount = 0;
452
453 this.isResumeSupported = function () {
454 return window.Blob && window.Blob.prototype.slice;
455 };
456
457 var resumeSupported = this.isResumeSupported();
458
459 function sendHttp(config) {
460 config.method = config.method || 'POST';
461 config.headers = config.headers || {};
462
463 var deferred = config._deferred = config._deferred || $q.defer();
464 var promise = deferred.promise;
465
466 function notifyProgress(e) {
467 if (deferred.notify) {
468 deferred.notify(e);
469 }
470 if (promise.progressFunc) {
471 $timeout(function () {
472 promise.progressFunc(e);
473 });
474 }
475 }
476
477 function getNotifyEvent(n) {
478 if (config._start != null && resumeSupported) {
479 return {
480 loaded: n.loaded + config._start,
481 total: (config._file && config._file.size) || n.total,
482 type: n.type, config: config,
483 lengthComputable: true, target: n.target
484 };
485 } else {
486 return n;
487 }
488 }
489
490 if (!config.disableProgress) {
491 config.headers.__setXHR_ = function () {
492 return function (xhr) {
493 if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;
494 config.__XHR = xhr;
495 if (config.xhrFn) config.xhrFn(xhr);
496 xhr.upload.addEventListener('progress', function (e) {
497 e.config = config;
498 notifyProgress(getNotifyEvent(e));
499 }, false);
500 //fix for firefox not firing upload progress end, also IE8-9
501 xhr.upload.addEventListener('load', function (e) {
502 if (e.lengthComputable) {
503 e.config = config;
504 notifyProgress(getNotifyEvent(e));
505 }
506 }, false);
507 };
508 };
509 }
510
511 function uploadWithAngular() {
512 $http(config).then(function (r) {
513 if (resumeSupported && config._chunkSize && !config._finished && config._file) {
514 notifyProgress({
515 loaded: config._end,
516 total: config._file && config._file.size,
517 config: config, type: 'progress'
518 }
519 );
520 upload.upload(config, true);
521 } else {
522 if (config._finished) delete config._finished;
523 deferred.resolve(r);
524 }
525 }, function (e) {
526 deferred.reject(e);
527 }, function (n) {
528 deferred.notify(n);
529 }
530 );
531 }
532
533 if (!resumeSupported) {
534 uploadWithAngular();
535 } else if (config._chunkSize && config._end && !config._finished) {
536 config._start = config._end;
537 config._end += config._chunkSize;
538 uploadWithAngular();
539 } else if (config.resumeSizeUrl) {
540 $http.get(config.resumeSizeUrl).then(function (resp) {
541 if (config.resumeSizeResponseReader) {
542 config._start = config.resumeSizeResponseReader(resp.data);
543 } else {
544 config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());
545 }
546 if (config._chunkSize) {
547 config._end = config._start + config._chunkSize;
548 }
549 uploadWithAngular();
550 }, function (e) {
551 throw e;
552 });
553 } else if (config.resumeSize) {
554 config.resumeSize().then(function (size) {
555 config._start = size;
556 uploadWithAngular();
557 }, function (e) {
558 throw e;
559 });
560 } else {
561 if (config._chunkSize) {
562 config._start = 0;
563 config._end = config._start + config._chunkSize;
564 }
565 uploadWithAngular();
566 }
567
568
569 promise.success = function (fn) {
570 promise.then(function (response) {
571 fn(response.data, response.status, response.headers, config);
572 });
573 return promise;
574 };
575
576 promise.error = function (fn) {
577 promise.then(null, function (response) {
578 fn(response.data, response.status, response.headers, config);
579 });
580 return promise;
581 };
582
583 promise.progress = function (fn) {
584 promise.progressFunc = fn;
585 promise.then(null, null, function (n) {
586 fn(n);
587 });
588 return promise;
589 };
590 promise.abort = promise.pause = function () {
591 if (config.__XHR) {
592 $timeout(function () {
593 config.__XHR.abort();
594 });
595 }
596 return promise;
597 };
598 promise.xhr = function (fn) {
599 config.xhrFn = (function (origXhrFn) {
600 return function () {
601 if (origXhrFn) origXhrFn.apply(promise, arguments);
602 fn.apply(promise, arguments);
603 };
604 })(config.xhrFn);
605 return promise;
606 };
607
608 upload.promisesCount++;
609 promise['finally'](function () {
610 upload.promisesCount--;
611 });
612 return promise;
613 }
614
615 this.isUploadInProgress = function () {
616 return upload.promisesCount > 0;
617 };
618
619 this.rename = function (file, name) {
620 file.ngfName = name;
621 return file;
622 };
623
624 this.jsonBlob = function (val) {
625 if (val != null && !angular.isString(val)) {
626 val = JSON.stringify(val);
627 }
628 var blob = new window.Blob([val], {type: 'application/json'});
629 blob._ngfBlob = true;
630 return blob;
631 };
632
633 this.json = function (val) {
634 return angular.toJson(val);
635 };
636
637 function copy(obj) {
638 var clone = {};
639 for (var key in obj) {
640 if (obj.hasOwnProperty(key)) {
641 clone[key] = obj[key];
642 }
643 }
644 return clone;
645 }
646
647 this.isFile = function (file) {
648 return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));
649 };
650
651 this.upload = function (config, internal) {
652 function toResumeFile(file, formData) {
653 if (file._ngfBlob) return file;
654 config._file = config._file || file;
655 if (config._start != null && resumeSupported) {
656 if (config._end && config._end >= file.size) {
657 config._finished = true;
658 config._end = file.size;
659 }
660 var slice = file.slice(config._start, config._end || file.size);
661 slice.name = file.name;
662 slice.ngfName = file.ngfName;
663 if (config._chunkSize) {
664 formData.append('_chunkSize', config._chunkSize);
665 formData.append('_currentChunkSize', config._end - config._start);
666 formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));
667 formData.append('_totalSize', config._file.size);
668 }
669 return slice;
670 }
671 return file;
672 }
673
674 function addFieldToFormData(formData, val, key) {
675 if (val !== undefined) {
676 if (angular.isDate(val)) {
677 val = val.toISOString();
678 }
679 if (angular.isString(val)) {
680 formData.append(key, val);
681 } else if (upload.isFile(val)) {
682 var file = toResumeFile(val, formData);
683 var split = key.split(',');
684 if (split[1]) {
685 file.ngfName = split[1].replace(/^\s+|\s+$/g, '');
686 key = split[0];
687 }
688 config._fileKey = config._fileKey || key;
689 formData.append(key, file, file.ngfName || file.name);
690 } else {
691 if (angular.isObject(val)) {
692 if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;
693
694 val.$$ngfCircularDetection = true;
695 try {
696 for (var k in val) {
697 if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {
698 var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
699 if (val.length && parseInt(k) > -1) {
700 objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
701 }
702 addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
703 }
704 }
705 } finally {
706 delete val.$$ngfCircularDetection;
707 }
708 } else {
709 formData.append(key, val);
710 }
711 }
712 }
713 }
714
715 function digestConfig() {
716 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
717 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
718
719 config.headers = config.headers || {};
720 config.headers['Content-Type'] = undefined;
721 config.transformRequest = config.transformRequest ?
722 (angular.isArray(config.transformRequest) ?
723 config.transformRequest : [config.transformRequest]) : [];
724 config.transformRequest.push(function (data) {
725 var formData = new window.FormData(), key;
726 data = data || config.fields || {};
727 if (config.file) {
728 data.file = config.file;
729 }
730 for (key in data) {
731 if (data.hasOwnProperty(key)) {
732 var val = data[key];
733 if (config.formDataAppender) {
734 config.formDataAppender(formData, key, val);
735 } else {
736 addFieldToFormData(formData, val, key);
737 }
738 }
739 }
740
741 return formData;
742 });
743 }
744
745 if (!internal) config = copy(config);
746 if (!config._isDigested) {
747 config._isDigested = true;
748 digestConfig();
749 }
750
751 return sendHttp(config);
752 };
753
754 this.http = function (config) {
755 config = copy(config);
756 config.transformRequest = config.transformRequest || function (data) {
757 if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {
758 return data;
759 }
760 return $http.defaults.transformRequest[0].apply(this, arguments);
761 };
762 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
763 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
764
765 return sendHttp(config);
766 };
767
768 this.translateScalars = function (str) {
769 if (angular.isString(str)) {
770 if (str.search(/kb/i) === str.length - 2) {
771 return parseFloat(str.substring(0, str.length - 2) * 1024);
772 } else if (str.search(/mb/i) === str.length - 2) {
773 return parseFloat(str.substring(0, str.length - 2) * 1048576);
774 } else if (str.search(/gb/i) === str.length - 2) {
775 return parseFloat(str.substring(0, str.length - 2) * 1073741824);
776 } else if (str.search(/b/i) === str.length - 1) {
777 return parseFloat(str.substring(0, str.length - 1));
778 } else if (str.search(/s/i) === str.length - 1) {
779 return parseFloat(str.substring(0, str.length - 1));
780 } else if (str.search(/m/i) === str.length - 1) {
781 return parseFloat(str.substring(0, str.length - 1) * 60);
782 } else if (str.search(/h/i) === str.length - 1) {
783 return parseFloat(str.substring(0, str.length - 1) * 3600);
784 }
785 }
786 return str;
787 };
788
789 this.urlToBlob = function(url) {
790 var defer = $q.defer();
791 $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {
792 var arrayBufferView = new Uint8Array(resp.data);
793 var type = resp.headers('content-type') || 'image/WebP';
794 var blob = new window.Blob([arrayBufferView], {type: type});
795 defer.resolve(blob);
796 //var split = type.split('[/;]');
797 //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
798 }, function (e) {
799 defer.reject(e);
800 });
801 return defer.promise;
802 };
803
804 this.setDefaults = function (defaults) {
805 this.defaults = defaults || {};
806 };
807
808 this.defaults = {};
809 this.version = ngFileUpload.version;
810 }
811
812 ]);
813
814 ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {
815 var upload = UploadExif;
816 upload.getAttrWithDefaults = function (attr, name) {
817 if (attr[name] != null) return attr[name];
818 var def = upload.defaults[name];
819 return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));
820 };
821
822 upload.attrGetter = function (name, attr, scope, params) {
823 var attrVal = this.getAttrWithDefaults(attr, name);
824 if (scope) {
825 try {
826 if (params) {
827 return $parse(attrVal)(scope, params);
828 } else {
829 return $parse(attrVal)(scope);
830 }
831 } catch (e) {
832 // hangle string value without single qoute
833 if (name.search(/min|max|pattern/i)) {
834 return attrVal;
835 } else {
836 throw e;
837 }
838 }
839 } else {
840 return attrVal;
841 }
842 };
843
844 upload.shouldUpdateOn = function (type, attr, scope) {
845 var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
846 if (modelOptions && modelOptions.updateOn) {
847 return modelOptions.updateOn.split(' ').indexOf(type) > -1;
848 }
849 return true;
850 };
851
852 upload.emptyPromise = function () {
853 var d = $q.defer();
854 var args = arguments;
855 $timeout(function () {
856 d.resolve.apply(d, args);
857 });
858 return d.promise;
859 };
860
861 upload.rejectPromise = function () {
862 var d = $q.defer();
863 var args = arguments;
864 $timeout(function () {
865 d.reject.apply(d, args);
866 });
867 return d.promise;
868 };
869
870 upload.happyPromise = function (promise, data) {
871 var d = $q.defer();
872 promise.then(function (result) {
873 d.resolve(result);
874 }, function (error) {
875 $timeout(function () {
876 throw error;
877 });
878 d.resolve(data);
879 });
880 return d.promise;
881 };
882
883 function applyExifRotations(files, attr, scope) {
884 var promises = [upload.emptyPromise()];
885 angular.forEach(files, function (f, i) {
886 if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {
887 promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {
888 files.splice(i, 1, fixedFile);
889 }));
890 }
891 });
892 return $q.all(promises);
893 }
894
895 function resize(files, attr, scope) {
896 var resizeVal = upload.attrGetter('ngfResize', attr, scope);
897 if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
898 if (resizeVal instanceof Function) {
899 var defer = $q.defer();
900 resizeVal(files).then(function (p) {
901 resizeWithParams(p, files, attr, scope).then(function (r) {
902 defer.resolve(r);
903 }, function (e) {
904 defer.reject(e);
905 });
906 }, function (e) {
907 defer.reject(e);
908 });
909 } else {
910 return resizeWithParams(resizeVal, files, attr, scope);
911 }
912 }
913
914 function resizeWithParams(param, files, attr, scope) {
915 var promises = [upload.emptyPromise()];
916
917 function handleFile(f, i) {
918 if (f.type.indexOf('image') === 0) {
919 if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
920 var promise = upload.resize(f, param.width, param.height, param.quality,
921 param.type, param.ratio, param.centerCrop, function (width, height) {
922 return upload.attrGetter('ngfResizeIf', attr, scope,
923 {$width: width, $height: height, $file: f});
924 }, param.restoreExif !== false);
925 promises.push(promise);
926 promise.then(function (resizedFile) {
927 files.splice(i, 1, resizedFile);
928 }, function (e) {
929 f.$error = 'resize';
930 f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
931 });
932 }
933 }
934
935 for (var i = 0; i < files.length; i++) {
936 handleFile(files[i], i);
937 }
938 return $q.all(promises);
939 }
940
941 upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
942 function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {
943 attr.$$ngfPrevValidFiles = files;
944 attr.$$ngfPrevInvalidFiles = invalidFiles;
945 var file = files && files.length ? files[0] : null;
946 var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;
947
948 if (ngModel) {
949 upload.applyModelValidation(ngModel, files);
950 ngModel.$setViewValue(isSingleModel ? file : files);
951 }
952
953 if (fileChange) {
954 $parse(fileChange)(scope, {
955 $files: files,
956 $file: file,
957 $newFiles: newFiles,
958 $duplicateFiles: dupFiles,
959 $invalidFiles: invalidFiles,
960 $invalidFile: invalidFile,
961 $event: evt
962 });
963 }
964
965 var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
966 if (invalidModel) {
967 $timeout(function () {
968 $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);
969 });
970 }
971 $timeout(function () {
972 // scope apply changes
973 });
974 }
975
976 var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,
977 invalids = [], valids = [];
978
979 function removeDuplicates() {
980 function equals(f1, f2) {
981 return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&
982 f1.type === f2.type;
983 }
984
985 function isInPrevFiles(f) {
986 var j;
987 for (j = 0; j < prevValidFiles.length; j++) {
988 if (equals(f, prevValidFiles[j])) {
989 return true;
990 }
991 }
992 for (j = 0; j < prevInvalidFiles.length; j++) {
993 if (equals(f, prevInvalidFiles[j])) {
994 return true;
995 }
996 }
997 return false;
998 }
999
1000 if (files) {
1001 allNewFiles = [];
1002 dupFiles = [];
1003 for (var i = 0; i < files.length; i++) {
1004 if (isInPrevFiles(files[i])) {
1005 dupFiles.push(files[i]);
1006 } else {
1007 allNewFiles.push(files[i]);
1008 }
1009 }
1010 }
1011 }
1012
1013 function toArray(v) {
1014 return angular.isArray(v) ? v : [v];
1015 }
1016
1017 function separateInvalids() {
1018 valids = [];
1019 invalids = [];
1020 angular.forEach(allNewFiles, function (file) {
1021 if (file.$error) {
1022 invalids.push(file);
1023 } else {
1024 valids.push(file);
1025 }
1026 });
1027 }
1028
1029 function resizeAndUpdate() {
1030 function updateModel() {
1031 $timeout(function () {
1032 update(keep ? prevValidFiles.concat(valids) : valids,
1033 keep ? prevInvalidFiles.concat(invalids) : invalids,
1034 files, dupFiles, isSingleModel);
1035 }, options && options.debounce ? options.debounce.change || options.debounce : 0);
1036 }
1037
1038 resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () {
1039 if (validateAfterResize) {
1040 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
1041 separateInvalids();
1042 updateModel();
1043 });
1044 } else {
1045 updateModel();
1046 }
1047 }, function (e) {
1048 throw 'Could not resize files ' + e;
1049 });
1050 }
1051
1052 prevValidFiles = attr.$$ngfPrevValidFiles || [];
1053 prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];
1054 if (ngModel && ngModel.$modelValue) {
1055 prevValidFiles = toArray(ngModel.$modelValue);
1056 }
1057
1058 var keep = upload.attrGetter('ngfKeep', attr, scope);
1059 allNewFiles = (files || []).slice(0);
1060 if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {
1061 removeDuplicates(attr, scope);
1062 }
1063
1064 var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);
1065
1066 if (keep && !allNewFiles.length) return;
1067
1068 upload.attrGetter('ngfBeforeModelChange', attr, scope, {
1069 $files: files,
1070 $file: files && files.length ? files[0] : null,
1071 $newFiles: allNewFiles,
1072 $duplicateFiles: dupFiles,
1073 $event: evt
1074 });
1075
1076 var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
1077
1078 var options = upload.attrGetter('ngModelOptions', attr, scope);
1079 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
1080 if (noDelay) {
1081 update(allNewFiles, [], files, dupFiles, isSingleModel);
1082 } else {
1083 if ((!options || !options.allowInvalid) && !validateAfterResize) {
1084 separateInvalids();
1085 } else {
1086 valids = allNewFiles;
1087 }
1088 if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {
1089 applyExifRotations(valids, attr, scope).then(function () {
1090 resizeAndUpdate();
1091 });
1092 } else {
1093 resizeAndUpdate();
1094 }
1095 }
1096 });
1097 };
1098
1099 return upload;
1100 }]);
1101
1102 ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {
1103 var generatedElems = [];
1104
1105 function isDelayedClickSupported(ua) {
1106 // fix for android native browser < 4.4 and safari windows
1107 var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/);
1108 if (m && m.length > 2) {
1109 var v = Upload.defaults.androidFixMinorVersion || 4;
1110 return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);
1111 }
1112
1113 // safari on windows
1114 return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);
1115 }
1116
1117 function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {
1118 /** @namespace attr.ngfSelect */
1119 /** @namespace attr.ngfChange */
1120 /** @namespace attr.ngModel */
1121 /** @namespace attr.ngModelOptions */
1122 /** @namespace attr.ngfMultiple */
1123 /** @namespace attr.ngfCapture */
1124 /** @namespace attr.ngfValidate */
1125 /** @namespace attr.ngfKeep */
1126 var attrGetter = function (name, scope) {
1127 return upload.attrGetter(name, attr, scope);
1128 };
1129
1130 function isInputTypeFile() {
1131 return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';
1132 }
1133
1134 function fileChangeAttr() {
1135 return attrGetter('ngfChange') || attrGetter('ngfSelect');
1136 }
1137
1138 function changeFn(evt) {
1139 if (upload.shouldUpdateOn('change', attr, scope)) {
1140 var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
1141 for (var i = 0; i < fileList.length; i++) {
1142 files.push(fileList[i]);
1143 }
1144 upload.updateModel(ngModel, attr, scope, fileChangeAttr(),
1145 files.length ? files : null, evt);
1146 }
1147 }
1148
1149 upload.registerModelChangeValidator(ngModel, attr, scope);
1150
1151 var unwatches = [];
1152 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
1153 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
1154 }));
1155 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
1156 fileElem.attr('capture', attrGetter('ngfCapture', scope));
1157 }));
1158 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
1159 fileElem.attr('accept', attrGetter('ngfAccept', scope));
1160 }));
1161 attr.$observe('accept', function () {
1162 fileElem.attr('accept', attrGetter('accept'));
1163 });
1164 unwatches.push(function () {
1165 if (attr.$$observers) delete attr.$$observers.accept;
1166 });
1167 function bindAttrToFileInput(fileElem) {
1168 if (elem !== fileElem) {
1169 for (var i = 0; i < elem[0].attributes.length; i++) {
1170 var attribute = elem[0].attributes[i];
1171 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
1172 if (attribute.value == null || attribute.value === '') {
1173 if (attribute.name === 'required') attribute.value = 'required';
1174 if (attribute.name === 'multiple') attribute.value = 'multiple';
1175 }
1176 fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
1177 }
1178 }
1179 }
1180 }
1181
1182 function createFileInput() {
1183 if (isInputTypeFile()) {
1184 return elem;
1185 }
1186
1187 var fileElem = angular.element('<input type="file">');
1188
1189 bindAttrToFileInput(fileElem);
1190
1191 var label = angular.element('<label>upload</label>');
1192 label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
1193 .css('width', '0px').css('height', '0px').css('border', 'none')
1194 .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
1195 generatedElems.push({el: elem, ref: label});
1196
1197 document.body.appendChild(label.append(fileElem)[0]);
1198
1199 return fileElem;
1200 }
1201
1202 var initialTouchStartY = 0;
1203
1204 function clickHandler(evt) {
1205 if (elem.attr('disabled')) return false;
1206 if (attrGetter('ngfSelectDisabled', scope)) return;
1207
1208 var r = handleTouch(evt);
1209 if (r != null) return r;
1210
1211 resetModel(evt);
1212
1213 // fix for md when the element is removed from the DOM and added back #460
1214 try {
1215 if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {
1216 generatedElems.push({el: elem, ref: fileElem.parent()});
1217 document.body.appendChild(fileElem.parent()[0]);
1218 fileElem.bind('change', changeFn);
1219 }
1220 } catch(e){/*ignore*/}
1221
1222 if (isDelayedClickSupported(navigator.userAgent)) {
1223 setTimeout(function () {
1224 fileElem[0].click();
1225 }, 0);
1226 } else {
1227 fileElem[0].click();
1228 }
1229
1230 return false;
1231 }
1232
1233 function handleTouch(evt) {
1234 var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
1235 if (evt.type === 'touchstart') {
1236 initialTouchStartY = touches ? touches[0].clientY : 0;
1237 return true; // don't block event default
1238 } else {
1239 evt.stopPropagation();
1240 evt.preventDefault();
1241
1242 // prevent scroll from triggering event
1243 if (evt.type === 'touchend') {
1244 var currentLocation = touches ? touches[0].clientY : 0;
1245 if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
1246 }
1247 }
1248 }
1249
1250 var fileElem = elem;
1251
1252 function resetModel(evt) {
1253 if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {
1254 fileElem.val(null);
1255 upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);
1256 }
1257 }
1258
1259 if (!isInputTypeFile()) {
1260 fileElem = createFileInput();
1261 }
1262 fileElem.bind('change', changeFn);
1263
1264 if (!isInputTypeFile()) {
1265 elem.bind('click touchstart touchend', clickHandler);
1266 } else {
1267 elem.bind('click', resetModel);
1268 }
1269
1270 function ie10SameFileSelectFix(evt) {
1271 if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {
1272 if (!fileElem[0].parentNode) {
1273 fileElem = null;
1274 return;
1275 }
1276 evt.preventDefault();
1277 evt.stopPropagation();
1278 fileElem.unbind('click');
1279 var clone = fileElem.clone();
1280 fileElem.replaceWith(clone);
1281 fileElem = clone;
1282 fileElem.attr('__ngf_ie10_Fix_', 'true');
1283 fileElem.bind('change', changeFn);
1284 fileElem.bind('click', ie10SameFileSelectFix);
1285 fileElem[0].click();
1286 return false;
1287 } else {
1288 fileElem.removeAttr('__ngf_ie10_Fix_');
1289 }
1290 }
1291
1292 if (navigator.appVersion.indexOf('MSIE 10') !== -1) {
1293 fileElem.bind('click', ie10SameFileSelectFix);
1294 }
1295
1296 if (ngModel) ngModel.$formatters.push(function (val) {
1297 if (val == null || val.length === 0) {
1298 if (fileElem.val()) {
1299 fileElem.val(null);
1300 }
1301 }
1302 return val;
1303 });
1304
1305 scope.$on('$destroy', function () {
1306 if (!isInputTypeFile()) fileElem.parent().remove();
1307 angular.forEach(unwatches, function (unwatch) {
1308 unwatch();
1309 });
1310 });
1311
1312 $timeout(function () {
1313 for (var i = 0; i < generatedElems.length; i++) {
1314 var g = generatedElems[i];
1315 if (!document.body.contains(g.el[0])) {
1316 generatedElems.splice(i, 1);
1317 g.ref.remove();
1318 }
1319 }
1320 });
1321
1322 if (window.FileAPI && window.FileAPI.ngfFixIE) {
1323 window.FileAPI.ngfFixIE(elem, fileElem, changeFn);
1324 }
1325 }
1326
1327 return {
1328 restrict: 'AEC',
1329 require: '?ngModel',
1330 link: function (scope, elem, attr, ngModel) {
1331 linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);
1332 }
1333 };
1334 }]);
1335
1336 (function () {
1337
1338 ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {
1339 var upload = UploadBase;
1340 upload.base64DataUrl = function (file) {
1341 if (angular.isArray(file)) {
1342 var d = $q.defer(), count = 0;
1343 angular.forEach(file, function (f) {
1344 upload.dataUrl(f, true)['finally'](function () {
1345 count++;
1346 if (count === file.length) {
1347 var urls = [];
1348 angular.forEach(file, function (ff) {
1349 urls.push(ff.$ngfDataUrl);
1350 });
1351 d.resolve(urls, file);
1352 }
1353 });
1354 });
1355 return d.promise;
1356 } else {
1357 return upload.dataUrl(file, true);
1358 }
1359 };
1360 upload.dataUrl = function (file, disallowObjectUrl) {
1361 if (!file) return upload.emptyPromise(file, file);
1362 if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {
1363 return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);
1364 }
1365 var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;
1366 if (p) return p;
1367
1368 var deferred = $q.defer();
1369 $timeout(function () {
1370 if (window.FileReader && file &&
1371 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&
1372 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {
1373 //prefer URL.createObjectURL for handling refrences to files of all sizes
1374 //since it doesn´t build a large string in memory
1375 var URL = window.URL || window.webkitURL;
1376 if (URL && URL.createObjectURL && !disallowObjectUrl) {
1377 var url;
1378 try {
1379 url = URL.createObjectURL(file);
1380 } catch (e) {
1381 $timeout(function () {
1382 file.$ngfBlobUrl = '';
1383 deferred.reject();
1384 });
1385 return;
1386 }
1387 $timeout(function () {
1388 file.$ngfBlobUrl = url;
1389 if (url) {
1390 deferred.resolve(url, file);
1391 upload.blobUrls = upload.blobUrls || [];
1392 upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;
1393 upload.blobUrls.push({url: url, size: file.size});
1394 upload.blobUrlsTotalSize += file.size || 0;
1395 var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;
1396 var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;
1397 while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {
1398 var obj = upload.blobUrls.splice(0, 1)[0];
1399 URL.revokeObjectURL(obj.url);
1400 upload.blobUrlsTotalSize -= obj.size;
1401 }
1402 }
1403 });
1404 } else {
1405 var fileReader = new FileReader();
1406 fileReader.onload = function (e) {
1407 $timeout(function () {
1408 file.$ngfDataUrl = e.target.result;
1409 deferred.resolve(e.target.result, file);
1410 $timeout(function () {
1411 delete file.$ngfDataUrl;
1412 }, 1000);
1413 });
1414 };
1415 fileReader.onerror = function () {
1416 $timeout(function () {
1417 file.$ngfDataUrl = '';
1418 deferred.reject();
1419 });
1420 };
1421 fileReader.readAsDataURL(file);
1422 }
1423 } else {
1424 $timeout(function () {
1425 file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';
1426 deferred.reject();
1427 });
1428 }
1429 });
1430
1431 if (disallowObjectUrl) {
1432 p = file.$$ngfDataUrlPromise = deferred.promise;
1433 } else {
1434 p = file.$$ngfBlobUrlPromise = deferred.promise;
1435 }
1436 p['finally'](function () {
1437 delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];
1438 });
1439 return p;
1440 };
1441 return upload;
1442 }]);
1443
1444 function getTagType(el) {
1445 if (el.tagName.toLowerCase() === 'img') return 'image';
1446 if (el.tagName.toLowerCase() === 'audio') return 'audio';
1447 if (el.tagName.toLowerCase() === 'video') return 'video';
1448 return /./;
1449 }
1450
1451 function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {
1452 function constructDataUrl(file) {
1453 var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);
1454 Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {
1455 $timeout(function () {
1456 var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;
1457 if (isBackground) {
1458 elem.css('background-image', 'url(\'' + (src || '') + '\')');
1459 } else {
1460 elem.attr('src', src);
1461 }
1462 if (src) {
1463 elem.removeClass('ng-hide');
1464 } else {
1465 elem.addClass('ng-hide');
1466 }
1467 });
1468 });
1469 }
1470
1471 $timeout(function () {
1472 var unwatch = scope.$watch(attr[directiveName], function (file) {
1473 var size = resizeParams;
1474 if (directiveName === 'ngfThumbnail') {
1475 if (!size) {
1476 size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
1477 }
1478 if (size.width === 0 && window.getComputedStyle) {
1479 var style = getComputedStyle(elem[0]);
1480 size = {
1481 width: parseInt(style.width.slice(0, -2)),
1482 height: parseInt(style.height.slice(0, -2))
1483 };
1484 }
1485 }
1486
1487 if (angular.isString(file)) {
1488 elem.removeClass('ng-hide');
1489 if (isBackground) {
1490 return elem.css('background-image', 'url(\'' + file + '\')');
1491 } else {
1492 return elem.attr('src', file);
1493 }
1494 }
1495 if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
1496 (!isBackground || file.type.indexOf('image') === 0)) {
1497 if (size && Upload.isResizeSupported()) {
1498 Upload.resize(file, size.width, size.height, size.quality).then(
1499 function (f) {
1500 constructDataUrl(f);
1501 }, function (e) {
1502 throw e;
1503 }
1504 );
1505 } else {
1506 constructDataUrl(file);
1507 }
1508 } else {
1509 elem.addClass('ng-hide');
1510 }
1511 });
1512
1513 scope.$on('$destroy', function () {
1514 unwatch();
1515 });
1516 });
1517 }
1518
1519
1520 /** @namespace attr.ngfSrc */
1521 /** @namespace attr.ngfNoObjectUrl */
1522 ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {
1523 return {
1524 restrict: 'AE',
1525 link: function (scope, elem, attr) {
1526 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',
1527 Upload.attrGetter('ngfResize', attr, scope), false);
1528 }
1529 };
1530 }]);
1531
1532 /** @namespace attr.ngfBackground */
1533 /** @namespace attr.ngfNoObjectUrl */
1534 ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {
1535 return {
1536 restrict: 'AE',
1537 link: function (scope, elem, attr) {
1538 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',
1539 Upload.attrGetter('ngfResize', attr, scope), true);
1540 }
1541 };
1542 }]);
1543
1544 /** @namespace attr.ngfThumbnail */
1545 /** @namespace attr.ngfAsBackground */
1546 /** @namespace attr.ngfSize */
1547 /** @namespace attr.ngfNoObjectUrl */
1548 ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {
1549 return {
1550 restrict: 'AE',
1551 link: function (scope, elem, attr) {
1552 var size = Upload.attrGetter('ngfSize', attr, scope);
1553 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,
1554 Upload.attrGetter('ngfAsBackground', attr, scope));
1555 }
1556 };
1557 }]);
1558
1559 ngFileUpload.config(['$compileProvider', function ($compileProvider) {
1560 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1561 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1562 }]);
1563
1564 ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
1565 return function (file, disallowObjectUrl, trustedUrl) {
1566 if (angular.isString(file)) {
1567 return $sce.trustAsResourceUrl(file);
1568 }
1569 var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);
1570 if (file && !src) {
1571 if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {
1572 file.$ngfDataUrlFilterInProgress = true;
1573 UploadDataUrl.dataUrl(file, disallowObjectUrl);
1574 }
1575 return '';
1576 }
1577 if (file) delete file.$ngfDataUrlFilterInProgress;
1578 return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';
1579 };
1580 }]);
1581
1582 })();
1583
1584 ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {
1585 var upload = UploadDataUrl;
1586
1587 function globStringToRegex(str) {
1588 var regexp = '', excludes = [];
1589 if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {
1590 regexp = str.substring(1, str.length - 1);
1591 } else {
1592 var split = str.split(',');
1593 if (split.length > 1) {
1594 for (var i = 0; i < split.length; i++) {
1595 var r = globStringToRegex(split[i]);
1596 if (r.regexp) {
1597 regexp += '(' + r.regexp + ')';
1598 if (i < split.length - 1) {
1599 regexp += '|';
1600 }
1601 } else {
1602 excludes = excludes.concat(r.excludes);
1603 }
1604 }
1605 } else {
1606 if (str.indexOf('!') === 0) {
1607 excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');
1608 } else {
1609 if (str.indexOf('.') === 0) {
1610 str = '*' + str;
1611 }
1612 regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$';
1613 regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
1614 }
1615 }
1616 }
1617 return {regexp: regexp, excludes: excludes};
1618 }
1619
1620 upload.validatePattern = function (file, val) {
1621 if (!val) {
1622 return true;
1623 }
1624 var pattern = globStringToRegex(val), valid = true;
1625 if (pattern.regexp && pattern.regexp.length) {
1626 var regexp = new RegExp(pattern.regexp, 'i');
1627 valid = (file.type != null && regexp.test(file.type)) ||
1628 (file.name != null && regexp.test(file.name));
1629 }
1630 var len = pattern.excludes.length;
1631 while (len--) {
1632 var exclude = new RegExp(pattern.excludes[len], 'i');
1633 valid = valid && (file.type == null || exclude.test(file.type)) &&
1634 (file.name == null || exclude.test(file.name));
1635 }
1636 return valid;
1637 };
1638
1639 upload.ratioToFloat = function (val) {
1640 var r = val.toString(), xIndex = r.search(/[x:]/i);
1641 if (xIndex > -1) {
1642 r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));
1643 } else {
1644 r = parseFloat(r);
1645 }
1646 return r;
1647 };
1648
1649 upload.registerModelChangeValidator = function (ngModel, attr, scope) {
1650 if (ngModel) {
1651 ngModel.$formatters.push(function (files) {
1652 if (ngModel.$dirty) {
1653 if (files && !angular.isArray(files)) {
1654 files = [files];
1655 }
1656 upload.validate(files, 0, ngModel, attr, scope).then(function () {
1657 upload.applyModelValidation(ngModel, files);
1658 });
1659 }
1660 });
1661 }
1662 };
1663
1664 function markModelAsDirty(ngModel, files) {
1665 if (files != null && !ngModel.$dirty) {
1666 if (ngModel.$setDirty) {
1667 ngModel.$setDirty();
1668 } else {
1669 ngModel.$dirty = true;
1670 }
1671 }
1672 }
1673
1674 upload.applyModelValidation = function (ngModel, files) {
1675 markModelAsDirty(ngModel, files);
1676 angular.forEach(ngModel.$ngfValidations, function (validation) {
1677 ngModel.$setValidity(validation.name, validation.valid);
1678 });
1679 };
1680
1681 upload.getValidationAttr = function (attr, scope, name, validationName, file) {
1682 var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);
1683 var val = upload.attrGetter(dName, attr, scope, {$file: file});
1684 if (val == null) {
1685 val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});
1686 if (val) {
1687 var split = (validationName || name).split('.');
1688 val = val[split[0]];
1689 if (split.length > 1) {
1690 val = val && val[split[1]];
1691 }
1692 }
1693 }
1694 return val;
1695 };
1696
1697 upload.validate = function (files, prevLength, ngModel, attr, scope) {
1698 ngModel = ngModel || {};
1699 ngModel.$ngfValidations = ngModel.$ngfValidations || [];
1700
1701 angular.forEach(ngModel.$ngfValidations, function (v) {
1702 v.valid = true;
1703 });
1704
1705 var attrGetter = function (name, params) {
1706 return upload.attrGetter(name, attr, scope, params);
1707 };
1708
1709 if (files == null || files.length === 0) {
1710 return upload.emptyPromise(ngModel);
1711 }
1712
1713 files = files.length === undefined ? [files] : files.slice(0);
1714
1715 function validateSync(name, validationName, fn) {
1716 if (files) {
1717 var i = files.length, valid = null;
1718 while (i--) {
1719 var file = files[i];
1720 if (file) {
1721 var val = upload.getValidationAttr(attr, scope, name, validationName, file);
1722 if (val != null) {
1723 if (!fn(file, val, i)) {
1724 file.$error = name;
1725 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1726 file.$errorParam = val;
1727 files.splice(i, 1);
1728 valid = false;
1729 }
1730 }
1731 }
1732 }
1733 if (valid !== null) {
1734 ngModel.$ngfValidations.push({name: name, valid: valid});
1735 }
1736 }
1737 }
1738
1739 validateSync('maxFiles', null, function (file, val, i) {
1740 return prevLength + i < val;
1741 });
1742 validateSync('pattern', null, upload.validatePattern);
1743 validateSync('minSize', 'size.min', function (file, val) {
1744 return file.size + 0.1 >= upload.translateScalars(val);
1745 });
1746 validateSync('maxSize', 'size.max', function (file, val) {
1747 return file.size - 0.1 <= upload.translateScalars(val);
1748 });
1749 var totalSize = 0;
1750 validateSync('maxTotalSize', null, function (file, val) {
1751 totalSize += file.size;
1752 if (totalSize > upload.translateScalars(val)) {
1753 files.splice(0, files.length);
1754 return false;
1755 }
1756 return true;
1757 });
1758
1759 validateSync('validateFn', null, function (file, r) {
1760 return r === true || r === null || r === '';
1761 });
1762
1763 if (!files.length) {
1764 return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
1765 }
1766
1767 function validateAsync(name, validationName, type, asyncFn, fn) {
1768 function resolveResult(defer, file, val) {
1769 if (val != null) {
1770 asyncFn(file, val).then(function (d) {
1771 if (!fn(d, val)) {
1772 file.$error = name;
1773 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1774 file.$errorParam = val;
1775 defer.reject();
1776 } else {
1777 defer.resolve();
1778 }
1779 }, function () {
1780 if (attrGetter('ngfValidateForce', {$file: file})) {
1781 file.$error = name;
1782 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1783 file.$errorParam = val;
1784 defer.reject();
1785 } else {
1786 defer.resolve();
1787 }
1788 });
1789 } else {
1790 defer.resolve();
1791 }
1792 }
1793
1794 var promises = [upload.emptyPromise()];
1795 if (files) {
1796 files = files.length === undefined ? [files] : files;
1797 angular.forEach(files, function (file) {
1798 var defer = $q.defer();
1799 promises.push(defer.promise);
1800 if (type && (file.type == null || file.type.search(type) !== 0)) {
1801 defer.resolve();
1802 return;
1803 }
1804 if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {
1805 upload.imageDimensions(file).then(function (d) {
1806 resolveResult(defer, file,
1807 attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));
1808 }, function () {
1809 defer.reject();
1810 });
1811 } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {
1812 upload.mediaDuration(file).then(function (d) {
1813 resolveResult(defer, file,
1814 attrGetter('ngfDuration', {$file: file, $duration: d}));
1815 }, function () {
1816 defer.reject();
1817 });
1818 } else {
1819 resolveResult(defer, file,
1820 upload.getValidationAttr(attr, scope, name, validationName, file));
1821 }
1822 });
1823 return $q.all(promises).then(function () {
1824 ngModel.$ngfValidations.push({name: name, valid: true});
1825 }, function () {
1826 ngModel.$ngfValidations.push({name: name, valid: false});
1827 });
1828 }
1829 }
1830
1831 var deffer = $q.defer();
1832 var promises = [];
1833
1834 promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/,
1835 this.imageDimensions, function (d, val) {
1836 return d.height <= val;
1837 })));
1838 promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/,
1839 this.imageDimensions, function (d, val) {
1840 return d.height >= val;
1841 })));
1842 promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/,
1843 this.imageDimensions, function (d, val) {
1844 return d.width <= val;
1845 })));
1846 promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/,
1847 this.imageDimensions, function (d, val) {
1848 return d.width >= val;
1849 })));
1850 promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/,
1851 function (file, val) {
1852 return upload.emptyPromise(val);
1853 }, function (r) {
1854 return r;
1855 })));
1856 promises.push(upload.happyPromise(validateAsync('ratio', null, /image/,
1857 this.imageDimensions, function (d, val) {
1858 var split = val.toString().split(','), valid = false;
1859 for (var i = 0; i < split.length; i++) {
1860 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
1861 valid = true;
1862 }
1863 }
1864 return valid;
1865 })));
1866 promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/,
1867 this.imageDimensions, function (d, val) {
1868 return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
1869 })));
1870 promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/,
1871 this.imageDimensions, function (d, val) {
1872 return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
1873 })));
1874 promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/,
1875 this.mediaDuration, function (d, val) {
1876 return d <= upload.translateScalars(val);
1877 })));
1878 promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/,
1879 this.mediaDuration, function (d, val) {
1880 return d >= upload.translateScalars(val);
1881 })));
1882 promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/,
1883 function (file, val) {
1884 return upload.emptyPromise(val);
1885 }, function (r) {
1886 return r;
1887 })));
1888
1889 promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null,
1890 function (file, val) {
1891 return val;
1892 }, function (r) {
1893 return r === true || r === null || r === '';
1894 })));
1895
1896 return $q.all(promises).then(function () {
1897 deffer.resolve(ngModel, ngModel.$ngfValidations);
1898 });
1899 };
1900
1901 upload.imageDimensions = function (file) {
1902 if (file.$ngfWidth && file.$ngfHeight) {
1903 var d = $q.defer();
1904 $timeout(function () {
1905 d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});
1906 });
1907 return d.promise;
1908 }
1909 if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;
1910
1911 var deferred = $q.defer();
1912 $timeout(function () {
1913 if (file.type.indexOf('image') !== 0) {
1914 deferred.reject('not image');
1915 return;
1916 }
1917 upload.dataUrl(file).then(function (dataUrl) {
1918 var img = angular.element('<img>').attr('src', dataUrl)
1919 .css('visibility', 'hidden').css('position', 'fixed')
1920 .css('max-width', 'none !important').css('max-height', 'none !important');
1921
1922 function success() {
1923 var width = img[0].clientWidth;
1924 var height = img[0].clientHeight;
1925 img.remove();
1926 file.$ngfWidth = width;
1927 file.$ngfHeight = height;
1928 deferred.resolve({width: width, height: height});
1929 }
1930
1931 function error() {
1932 img.remove();
1933 deferred.reject('load error');
1934 }
1935
1936 img.on('load', success);
1937 img.on('error', error);
1938 var count = 0;
1939
1940 function checkLoadError() {
1941 $timeout(function () {
1942 if (img[0].parentNode) {
1943 if (img[0].clientWidth) {
1944 success();
1945 } else if (count > 10) {
1946 error();
1947 } else {
1948 checkLoadError();
1949 }
1950 }
1951 }, 1000);
1952 }
1953
1954 checkLoadError();
1955
1956 angular.element(document.getElementsByTagName('body')[0]).append(img);
1957 }, function () {
1958 deferred.reject('load error');
1959 });
1960 });
1961
1962 file.$ngfDimensionPromise = deferred.promise;
1963 file.$ngfDimensionPromise['finally'](function () {
1964 delete file.$ngfDimensionPromise;
1965 });
1966 return file.$ngfDimensionPromise;
1967 };
1968
1969 upload.mediaDuration = function (file) {
1970 if (file.$ngfDuration) {
1971 var d = $q.defer();
1972 $timeout(function () {
1973 d.resolve(file.$ngfDuration);
1974 });
1975 return d.promise;
1976 }
1977 if (file.$ngfDurationPromise) return file.$ngfDurationPromise;
1978
1979 var deferred = $q.defer();
1980 $timeout(function () {
1981 if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {
1982 deferred.reject('not media');
1983 return;
1984 }
1985 upload.dataUrl(file).then(function (dataUrl) {
1986 var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')
1987 .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');
1988
1989 function success() {
1990 var duration = el[0].duration;
1991 file.$ngfDuration = duration;
1992 el.remove();
1993 deferred.resolve(duration);
1994 }
1995
1996 function error() {
1997 el.remove();
1998 deferred.reject('load error');
1999 }
2000
2001 el.on('loadedmetadata', success);
2002 el.on('error', error);
2003 var count = 0;
2004
2005 function checkLoadError() {
2006 $timeout(function () {
2007 if (el[0].parentNode) {
2008 if (el[0].duration) {
2009 success();
2010 } else if (count > 10) {
2011 error();
2012 } else {
2013 checkLoadError();
2014 }
2015 }
2016 }, 1000);
2017 }
2018
2019 checkLoadError();
2020
2021 angular.element(document.body).append(el);
2022 }, function () {
2023 deferred.reject('load error');
2024 });
2025 });
2026
2027 file.$ngfDurationPromise = deferred.promise;
2028 file.$ngfDurationPromise['finally'](function () {
2029 delete file.$ngfDurationPromise;
2030 });
2031 return file.$ngfDurationPromise;
2032 };
2033 return upload;
2034 }
2035 ]);
2036
2037 ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {
2038 var upload = UploadValidate;
2039
2040 /**
2041 * Conserve aspect ratio of the original region. Useful when shrinking/enlarging
2042 * images to fit into a certain area.
2043 * Source: http://stackoverflow.com/a/14731922
2044 *
2045 * @param {Number} srcWidth Source area width
2046 * @param {Number} srcHeight Source area height
2047 * @param {Number} maxWidth Nestable area maximum available width
2048 * @param {Number} maxHeight Nestable area maximum available height
2049 * @return {Object} { width, height }
2050 */
2051 var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {
2052 var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :
2053 Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
2054 return {
2055 width: srcWidth * ratio, height: srcHeight * ratio,
2056 marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight
2057 };
2058 };
2059
2060 // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89
2061 var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {
2062 var deferred = $q.defer();
2063 var canvasElement = document.createElement('canvas');
2064 var imageElement = document.createElement('img');
2065
2066 imageElement.onload = function () {
2067 if (resizeIf != null && resizeIf(imageElement.width, imageElement.height) === false) {
2068 deferred.reject('resizeIf');
2069 return;
2070 }
2071 try {
2072 if (ratio) {
2073 var ratioFloat = upload.ratioToFloat(ratio);
2074 var imgRatio = imageElement.width / imageElement.height;
2075 if (imgRatio < ratioFloat) {
2076 width = imageElement.width;
2077 height = width / ratioFloat;
2078 } else {
2079 height = imageElement.height;
2080 width = height * ratioFloat;
2081 }
2082 }
2083 if (!width) {
2084 width = imageElement.width;
2085 }
2086 if (!height) {
2087 height = imageElement.height;
2088 }
2089 var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
2090 canvasElement.width = Math.min(dimensions.width, width);
2091 canvasElement.height = Math.min(dimensions.height, height);
2092 var context = canvasElement.getContext('2d');
2093 context.drawImage(imageElement,
2094 Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),
2095 dimensions.width, dimensions.height);
2096 deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));
2097 } catch (e) {
2098 deferred.reject(e);
2099 }
2100 };
2101 imageElement.onerror = function () {
2102 deferred.reject();
2103 };
2104 imageElement.src = imagen;
2105 return deferred.promise;
2106 };
2107
2108 upload.dataUrltoBlob = function (dataurl, name, origSize) {
2109 var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
2110 bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
2111 while (n--) {
2112 u8arr[n] = bstr.charCodeAt(n);
2113 }
2114 var blob = new window.Blob([u8arr], {type: mime});
2115 blob.name = name;
2116 blob.$ngfOrigSize = origSize;
2117 return blob;
2118 };
2119
2120 upload.isResizeSupported = function () {
2121 var elem = document.createElement('canvas');
2122 return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;
2123 };
2124
2125 if (upload.isResizeSupported()) {
2126 // add name getter to the blob constructor prototype
2127 Object.defineProperty(window.Blob.prototype, 'name', {
2128 get: function () {
2129 return this.$ngfName;
2130 },
2131 set: function (v) {
2132 this.$ngfName = v;
2133 },
2134 configurable: true
2135 });
2136 }
2137
2138 upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) {
2139 if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
2140
2141 var deferred = $q.defer();
2142 upload.dataUrl(file, true).then(function (url) {
2143 resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf)
2144 .then(function (dataUrl) {
2145 if (file.type === 'image/jpeg' && restoreExif) {
2146 try {
2147 dataUrl = upload.restoreExif(url, dataUrl);
2148 } catch (e) {
2149 setTimeout(function () {throw e;}, 1);
2150 }
2151 }
2152 try {
2153 var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);
2154 deferred.resolve(blob);
2155 } catch (e) {
2156 deferred.reject(e);
2157 }
2158 }, function (r) {
2159 if (r === 'resizeIf') {
2160 deferred.resolve(file);
2161 }
2162 deferred.reject(r);
2163 });
2164 }, function (e) {
2165 deferred.reject(e);
2166 });
2167 return deferred.promise;
2168 };
2169
2170 return upload;
2171 }]);
2172
2173 (function () {
2174 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
2175 function ($parse, $timeout, $location, Upload, $http, $q) {
2176 return {
2177 restrict: 'AEC',
2178 require: '?ngModel',
2179 link: function (scope, elem, attr, ngModel) {
2180 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
2181 }
2182 };
2183 }]);
2184
2185 ngFileUpload.directive('ngfNoFileDrop', function () {
2186 return function (scope, elem) {
2187 if (dropAvailable()) elem.css('display', 'none');
2188 };
2189 });
2190
2191 ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {
2192 return function (scope, elem, attr) {
2193 if (dropAvailable()) {
2194 var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));
2195 $timeout(function () {
2196 model(scope);
2197 if (model.assign) {
2198 model.assign(scope, true);
2199 }
2200 });
2201 }
2202 };
2203 }]);
2204
2205 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
2206 var available = dropAvailable();
2207
2208 var attrGetter = function (name, scope, params) {
2209 return upload.attrGetter(name, attr, scope, params);
2210 };
2211
2212 if (attrGetter('dropAvailable')) {
2213 $timeout(function () {
2214 if (scope[attrGetter('dropAvailable')]) {
2215 scope[attrGetter('dropAvailable')].value = available;
2216 } else {
2217 scope[attrGetter('dropAvailable')] = available;
2218 }
2219 });
2220 }
2221 if (!available) {
2222 if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {
2223 elem.css('display', 'none');
2224 }
2225 return;
2226 }
2227
2228 function isDisabled() {
2229 return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);
2230 }
2231
2232 if (attrGetter('ngfSelect') == null) {
2233 upload.registerModelChangeValidator(ngModel, attr, scope);
2234 }
2235
2236 var leaveTimeout = null;
2237 var stopPropagation = $parse(attrGetter('ngfStopPropagation'));
2238 var dragOverDelay = 1;
2239 var actualDragOverClass;
2240
2241 elem[0].addEventListener('dragover', function (evt) {
2242 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
2243 evt.preventDefault();
2244 if (stopPropagation(scope)) evt.stopPropagation();
2245 // handling dragover events from the Chrome download bar
2246 if (navigator.userAgent.indexOf('Chrome') > -1) {
2247 var b = evt.dataTransfer.effectAllowed;
2248 evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';
2249 }
2250 $timeout.cancel(leaveTimeout);
2251 if (!actualDragOverClass) {
2252 actualDragOverClass = 'C';
2253 calculateDragOverClass(scope, attr, evt, function (clazz) {
2254 actualDragOverClass = clazz;
2255 elem.addClass(actualDragOverClass);
2256 attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});
2257 });
2258 }
2259 }, false);
2260 elem[0].addEventListener('dragenter', function (evt) {
2261 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
2262 evt.preventDefault();
2263 if (stopPropagation(scope)) evt.stopPropagation();
2264 }, false);
2265 elem[0].addEventListener('dragleave', function (evt) {
2266 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
2267 evt.preventDefault();
2268 if (stopPropagation(scope)) evt.stopPropagation();
2269 leaveTimeout = $timeout(function () {
2270 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
2271 actualDragOverClass = null;
2272 attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});
2273 }, dragOverDelay || 100);
2274 }, false);
2275 elem[0].addEventListener('drop', function (evt) {
2276 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
2277 evt.preventDefault();
2278 if (stopPropagation(scope)) evt.stopPropagation();
2279 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
2280 actualDragOverClass = null;
2281 var items = evt.dataTransfer.items;
2282 var html;
2283 try {
2284 html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
2285 } catch (e) {/* Fix IE11 that throw error calling getData */
2286 }
2287
2288 extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
2289 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
2290 if (files.length) {
2291 updateModel(files, evt);
2292 } else {
2293 extractFilesFromHtml('dropUrl', html).then(function (files) {
2294 updateModel(files, evt);
2295 });
2296 }
2297 });
2298 }, false);
2299 elem[0].addEventListener('paste', function (evt) {
2300 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
2301 attrGetter('ngfEnableFirefoxPaste', scope)) {
2302 evt.preventDefault();
2303 }
2304 if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
2305 var files = [];
2306 var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
2307 if (clipboard && clipboard.items) {
2308 for (var k = 0; k < clipboard.items.length; k++) {
2309 if (clipboard.items[k].type.indexOf('image') !== -1) {
2310 files.push(clipboard.items[k].getAsFile());
2311 }
2312 }
2313 }
2314 if (files.length) {
2315 updateModel(files, evt);
2316 } else {
2317 extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
2318 updateModel(files, evt);
2319 });
2320 }
2321 }, false);
2322
2323 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
2324 attrGetter('ngfEnableFirefoxPaste', scope)) {
2325 elem.attr('contenteditable', true);
2326 elem.on('keypress', function (e) {
2327 if (!e.metaKey && !e.ctrlKey) {
2328 e.preventDefault();
2329 }
2330 });
2331 }
2332
2333 function updateModel(files, evt) {
2334 upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
2335 }
2336
2337 function extractFilesFromHtml(updateOn, html) {
2338 if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
2339 var urls = [];
2340 html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
2341 urls.push(src);
2342 });
2343 var promises = [], files = [];
2344 if (urls.length) {
2345 angular.forEach(urls, function (url) {
2346 promises.push(upload.urlToBlob(url).then(function (blob) {
2347 files.push(blob);
2348 }));
2349 });
2350 var defer = $q.defer();
2351 $q.all(promises).then(function () {
2352 defer.resolve(files);
2353 }, function (e) {
2354 defer.reject(e);
2355 });
2356 return defer.promise;
2357 }
2358 return upload.emptyPromise();
2359 }
2360
2361 function calculateDragOverClass(scope, attr, evt, callback) {
2362 var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';
2363 if (angular.isString(obj)) {
2364 dClass = obj;
2365 } else if (obj) {
2366 if (obj.delay) dragOverDelay = obj.delay;
2367 if (obj.accept || obj.reject) {
2368 var items = evt.dataTransfer.items;
2369 if (items == null || !items.length) {
2370 dClass = obj.accept;
2371 } else {
2372 var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
2373 var len = items.length;
2374 while (len--) {
2375 if (!upload.validatePattern(items[len], pattern)) {
2376 dClass = obj.reject;
2377 break;
2378 } else {
2379 dClass = obj.accept;
2380 }
2381 }
2382 }
2383 }
2384 }
2385 callback(dClass);
2386 }
2387
2388 function extractFiles(items, fileList, allowDir, multiple) {
2389 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
2390 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
2391 var includeDir = attrGetter('ngfIncludeDir', scope);
2392 var files = [], totalSize = 0;
2393
2394 function traverseFileTree(entry, path) {
2395 var defer = $q.defer();
2396 if (entry != null) {
2397 if (entry.isDirectory) {
2398 var promises = [upload.emptyPromise()];
2399 if (includeDir) {
2400 var file = {type: 'directory'};
2401 file.name = file.path = (path || '') + entry.name + entry.name;
2402 files.push(file);
2403 }
2404 var dirReader = entry.createReader();
2405 var entries = [];
2406 var readEntries = function () {
2407 dirReader.readEntries(function (results) {
2408 try {
2409 if (!results.length) {
2410 angular.forEach(entries.slice(0), function (e) {
2411 if (files.length <= maxFiles && totalSize <= maxTotalSize) {
2412 promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));
2413 }
2414 });
2415 $q.all(promises).then(function () {
2416 defer.resolve();
2417 }, function (e) {
2418 defer.reject(e);
2419 });
2420 } else {
2421 entries = entries.concat(Array.prototype.slice.call(results || [], 0));
2422 readEntries();
2423 }
2424 } catch (e) {
2425 defer.reject(e);
2426 }
2427 }, function (e) {
2428 defer.reject(e);
2429 });
2430 };
2431 readEntries();
2432 } else {
2433 entry.file(function (file) {
2434 try {
2435 file.path = (path ? path : '') + file.name;
2436 if (includeDir) {
2437 file = upload.rename(file, file.path);
2438 }
2439 files.push(file);
2440 totalSize += file.size;
2441 defer.resolve();
2442 } catch (e) {
2443 defer.reject(e);
2444 }
2445 }, function (e) {
2446 defer.reject(e);
2447 });
2448 }
2449 }
2450 return defer.promise;
2451 }
2452
2453 var promises = [upload.emptyPromise()];
2454
2455 if (items && items.length > 0 && $location.protocol() !== 'file') {
2456 for (var i = 0; i < items.length; i++) {
2457 if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
2458 var entry = items[i].webkitGetAsEntry();
2459 if (entry.isDirectory && !allowDir) {
2460 continue;
2461 }
2462 if (entry != null) {
2463 promises.push(traverseFileTree(entry));
2464 }
2465 } else {
2466 var f = items[i].getAsFile();
2467 if (f != null) {
2468 files.push(f);
2469 totalSize += f.size;
2470 }
2471 }
2472 if (files.length > maxFiles || totalSize > maxTotalSize ||
2473 (!multiple && files.length > 0)) break;
2474 }
2475 } else {
2476 if (fileList != null) {
2477 for (var j = 0; j < fileList.length; j++) {
2478 var file = fileList.item(j);
2479 if (file.type || file.size > 0) {
2480 files.push(file);
2481 totalSize += file.size;
2482 }
2483 if (files.length > maxFiles || totalSize > maxTotalSize ||
2484 (!multiple && files.length > 0)) break;
2485 }
2486 }
2487 }
2488
2489 var defer = $q.defer();
2490 $q.all(promises).then(function () {
2491 if (!multiple && !includeDir && files.length) {
2492 var i = 0;
2493 while (files[i] && files[i].type === 'directory') i++;
2494 defer.resolve([files[i]]);
2495 } else {
2496 defer.resolve(files);
2497 }
2498 }, function (e) {
2499 defer.reject(e);
2500 });
2501
2502 return defer.promise;
2503 }
2504 }
2505
2506 function dropAvailable() {
2507 var div = document.createElement('div');
2508 return ('draggable' in div) && ('ondrop' in div) && !/Edge\/12./i.test(navigator.userAgent);
2509 }
2510
2511 })();
2512
2513 // customized version of https://github.com/exif-js/exif-js
2514 ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {
2515 var upload = UploadResize;
2516
2517 upload.isExifSupported = function () {
2518 return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();
2519 };
2520
2521 function applyTransform(ctx, orientation, width, height) {
2522 switch (orientation) {
2523 case 2:
2524 return ctx.transform(-1, 0, 0, 1, width, 0);
2525 case 3:
2526 return ctx.transform(-1, 0, 0, -1, width, height);
2527 case 4:
2528 return ctx.transform(1, 0, 0, -1, 0, height);
2529 case 5:
2530 return ctx.transform(0, 1, 1, 0, 0, 0);
2531 case 6:
2532 return ctx.transform(0, 1, -1, 0, height, 0);
2533 case 7:
2534 return ctx.transform(0, -1, -1, 0, height, width);
2535 case 8:
2536 return ctx.transform(0, -1, 1, 0, 0, width);
2537 }
2538 }
2539
2540 upload.readOrientation = function (file) {
2541 var defer = $q.defer();
2542 var reader = new FileReader();
2543 var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;
2544 reader.readAsArrayBuffer(slicedFile);
2545 reader.onerror = function (e) {
2546 return defer.reject(e);
2547 };
2548 reader.onload = function (e) {
2549 var result = {orientation: 1};
2550 var view = new DataView(this.result);
2551 if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);
2552
2553 var length = view.byteLength,
2554 offset = 2;
2555 while (offset < length) {
2556 var marker = view.getUint16(offset, false);
2557 offset += 2;
2558 if (marker === 0xFFE1) {
2559 if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);
2560
2561 var little = view.getUint16(offset += 6, false) === 0x4949;
2562 offset += view.getUint32(offset + 4, little);
2563 var tags = view.getUint16(offset, little);
2564 offset += 2;
2565 for (var i = 0; i < tags; i++)
2566 if (view.getUint16(offset + (i * 12), little) === 0x0112) {
2567 var orientation = view.getUint16(offset + (i * 12) + 8, little);
2568 if (orientation >= 2 && orientation <= 8) {
2569 view.setUint16(offset + (i * 12) + 8, 1, little);
2570 result.fixedArrayBuffer = e.target.result;
2571 }
2572 result.orientation = orientation;
2573 return defer.resolve(result);
2574 }
2575 } else if ((marker & 0xFF00) !== 0xFF00) break;
2576 else offset += view.getUint16(offset, false);
2577 }
2578 return defer.resolve(result);
2579 };
2580 return defer.promise;
2581 };
2582
2583 function arrayBufferToBase64(buffer) {
2584 var binary = '';
2585 var bytes = new Uint8Array(buffer);
2586 var len = bytes.byteLength;
2587 for (var i = 0; i < len; i++) {
2588 binary += String.fromCharCode(bytes[i]);
2589 }
2590 return window.btoa(binary);
2591 }
2592
2593 upload.applyExifRotation = function (file) {
2594 if (file.type.indexOf('image/jpeg') !== 0) {
2595 return upload.emptyPromise(file);
2596 }
2597
2598 var deferred = $q.defer();
2599 upload.readOrientation(file).then(function (result) {
2600 if (result.orientation < 2 || result.orientation > 8) {
2601 return deferred.resolve(file);
2602 }
2603 upload.dataUrl(file, true).then(function (url) {
2604 var canvas = document.createElement('canvas');
2605 var img = document.createElement('img');
2606
2607 img.onload = function () {
2608 try {
2609 canvas.width = result.orientation > 4 ? img.height : img.width;
2610 canvas.height = result.orientation > 4 ? img.width : img.height;
2611 var ctx = canvas.getContext('2d');
2612 applyTransform(ctx, result.orientation, img.width, img.height);
2613 ctx.drawImage(img, 0, 0);
2614 var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);
2615 dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);
2616 var blob = upload.dataUrltoBlob(dataUrl, file.name);
2617 deferred.resolve(blob);
2618 } catch (e) {
2619 return deferred.reject(e);
2620 }
2621 };
2622 img.onerror = function () {
2623 deferred.reject();
2624 };
2625 img.src = url;
2626 }, function (e) {
2627 deferred.reject(e);
2628 });
2629 }, function (e) {
2630 deferred.reject(e);
2631 });
2632 return deferred.promise;
2633 };
2634
2635 upload.restoreExif = function (orig, resized) {
2636 var ExifRestorer = {};
2637
2638 ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +
2639 'QRSTUVWXYZabcdef' +
2640 'ghijklmnopqrstuv' +
2641 'wxyz0123456789+/' +
2642 '=';
2643
2644 ExifRestorer.encode64 = function (input) {
2645 var output = '',
2646 chr1, chr2, chr3 = '',
2647 enc1, enc2, enc3, enc4 = '',
2648 i = 0;
2649
2650 do {
2651 chr1 = input[i++];
2652 chr2 = input[i++];
2653 chr3 = input[i++];
2654
2655 enc1 = chr1 >> 2;
2656 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2657 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
2658 enc4 = chr3 & 63;
2659
2660 if (isNaN(chr2)) {
2661 enc3 = enc4 = 64;
2662 } else if (isNaN(chr3)) {
2663 enc4 = 64;
2664 }
2665
2666 output = output +
2667 this.KEY_STR.charAt(enc1) +
2668 this.KEY_STR.charAt(enc2) +
2669 this.KEY_STR.charAt(enc3) +
2670 this.KEY_STR.charAt(enc4);
2671 chr1 = chr2 = chr3 = '';
2672 enc1 = enc2 = enc3 = enc4 = '';
2673 } while (i < input.length);
2674
2675 return output;
2676 };
2677
2678 ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {
2679 if (origFileBase64.match('data:image/jpeg;base64,')) {
2680 origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');
2681 }
2682
2683 var rawImage = this.decode64(origFileBase64);
2684 var segments = this.slice2Segments(rawImage);
2685
2686 var image = this.exifManipulation(resizedFileBase64, segments);
2687
2688 return 'data:image/jpeg;base64,' + this.encode64(image);
2689 };
2690
2691
2692 ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {
2693 var exifArray = this.getExifArray(segments),
2694 newImageArray = this.insertExif(resizedFileBase64, exifArray);
2695 return new Uint8Array(newImageArray);
2696 };
2697
2698
2699 ExifRestorer.getExifArray = function (segments) {
2700 var seg;
2701 for (var x = 0; x < segments.length; x++) {
2702 seg = segments[x];
2703 if (seg[0] === 255 & seg[1] === 225) //(ff e1)
2704 {
2705 return seg;
2706 }
2707 }
2708 return [];
2709 };
2710
2711
2712 ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {
2713 var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),
2714 buf = this.decode64(imageData),
2715 separatePoint = buf.indexOf(255, 3),
2716 mae = buf.slice(0, separatePoint),
2717 ato = buf.slice(separatePoint),
2718 array = mae;
2719
2720 array = array.concat(exifArray);
2721 array = array.concat(ato);
2722 return array;
2723 };
2724
2725
2726 ExifRestorer.slice2Segments = function (rawImageArray) {
2727 var head = 0,
2728 segments = [];
2729
2730 while (1) {
2731 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {
2732 break;
2733 }
2734 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {
2735 head += 2;
2736 }
2737 else {
2738 var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],
2739 endPoint = head + length + 2,
2740 seg = rawImageArray.slice(head, endPoint);
2741 segments.push(seg);
2742 head = endPoint;
2743 }
2744 if (head > rawImageArray.length) {
2745 break;
2746 }
2747 }
2748
2749 return segments;
2750 };
2751
2752
2753 ExifRestorer.decode64 = function (input) {
2754 var chr1, chr2, chr3 = '',
2755 enc1, enc2, enc3, enc4 = '',
2756 i = 0,
2757 buf = [];
2758
2759 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
2760 var base64test = /[^A-Za-z0-9\+\/\=]/g;
2761 if (base64test.exec(input)) {
2762 console.log('There were invalid base64 characters in the input text.\n' +
2763 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' +
2764 'Expect errors in decoding.');
2765 }
2766 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
2767
2768 do {
2769 enc1 = this.KEY_STR.indexOf(input.charAt(i++));
2770 enc2 = this.KEY_STR.indexOf(input.charAt(i++));
2771 enc3 = this.KEY_STR.indexOf(input.charAt(i++));
2772 enc4 = this.KEY_STR.indexOf(input.charAt(i++));
2773
2774 chr1 = (enc1 << 2) | (enc2 >> 4);
2775 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
2776 chr3 = ((enc3 & 3) << 6) | enc4;
2777
2778 buf.push(chr1);
2779
2780 if (enc3 !== 64) {
2781 buf.push(chr2);
2782 }
2783 if (enc4 !== 64) {
2784 buf.push(chr3);
2785 }
2786
2787 chr1 = chr2 = chr3 = '';
2788 enc1 = enc2 = enc3 = enc4 = '';
2789
2790 } while (i < input.length);
2791
2792 return buf;
2793 };
2794
2795 return ExifRestorer.restore(orig, resized); //<= EXIF
2796 };
2797
2798 return upload;
2799 }]);
2800
+0
-421
demo/src/main/webapp/js/ng-file-upload-shim.js less more
0 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
2 * progress, resize, thumbnail, preview, validation and CORS
3 * FileAPI Flash shim for old browsers not supporting FormData
4 * @author Danial <danial.farid@gmail.com>
5 * @version 12.0.4
6 */
7
8 (function () {
9 /** @namespace FileAPI.noContentTimeout */
10
11 function patchXHR(fnName, newFn) {
12 window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
13 }
14
15 function redefineProp(xhr, prop, fn) {
16 try {
17 Object.defineProperty(xhr, prop, {get: fn});
18 } catch (e) {/*ignore*/
19 }
20 }
21
22 if (!window.FileAPI) {
23 window.FileAPI = {};
24 }
25
26 if (!window.XMLHttpRequest) {
27 throw 'AJAX is not supported. XMLHttpRequest is not defined.';
28 }
29
30 FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
31 if (FileAPI.shouldLoad) {
32 var initializeUploadListener = function (xhr) {
33 if (!xhr.__listeners) {
34 if (!xhr.upload) xhr.upload = {};
35 xhr.__listeners = [];
36 var origAddEventListener = xhr.upload.addEventListener;
37 xhr.upload.addEventListener = function (t, fn) {
38 xhr.__listeners[t] = fn;
39 if (origAddEventListener) origAddEventListener.apply(this, arguments);
40 };
41 }
42 };
43
44 patchXHR('open', function (orig) {
45 return function (m, url, b) {
46 initializeUploadListener(this);
47 this.__url = url;
48 try {
49 orig.apply(this, [m, url, b]);
50 } catch (e) {
51 if (e.message.indexOf('Access is denied') > -1) {
52 this.__origError = e;
53 orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
54 }
55 }
56 };
57 });
58
59 patchXHR('getResponseHeader', function (orig) {
60 return function (h) {
61 return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
62 };
63 });
64
65 patchXHR('getAllResponseHeaders', function (orig) {
66 return function () {
67 return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
68 };
69 });
70
71 patchXHR('abort', function (orig) {
72 return function () {
73 return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
74 };
75 });
76
77 patchXHR('setRequestHeader', function (orig) {
78 return function (header, value) {
79 if (header === '__setXHR_') {
80 initializeUploadListener(this);
81 var val = value(this);
82 // fix for angular < 1.2.0
83 if (val instanceof Function) {
84 val(this);
85 }
86 } else {
87 this.__requestHeaders = this.__requestHeaders || {};
88 this.__requestHeaders[header] = value;
89 orig.apply(this, arguments);
90 }
91 };
92 });
93
94 patchXHR('send', function (orig) {
95 return function () {
96 var xhr = this;
97 if (arguments[0] && arguments[0].__isFileAPIShim) {
98 var formData = arguments[0];
99 var config = {
100 url: xhr.__url,
101 jsonp: false, //removes the callback form param
102 cache: true, //removes the ?fileapiXXX in the url
103 complete: function (err, fileApiXHR) {
104 if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
105 // this error seems to be fine the file is being uploaded properly.
106 err = null;
107 }
108 xhr.__completed = true;
109 if (!err && xhr.__listeners.load)
110 xhr.__listeners.load({
111 type: 'load',
112 loaded: xhr.__loaded,
113 total: xhr.__total,
114 target: xhr,
115 lengthComputable: true
116 });
117 if (!err && xhr.__listeners.loadend)
118 xhr.__listeners.loadend({
119 type: 'loadend',
120 loaded: xhr.__loaded,
121 total: xhr.__total,
122 target: xhr,
123 lengthComputable: true
124 });
125 if (err === 'abort' && xhr.__listeners.abort)
126 xhr.__listeners.abort({
127 type: 'abort',
128 loaded: xhr.__loaded,
129 total: xhr.__total,
130 target: xhr,
131 lengthComputable: true
132 });
133 if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
134 return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
135 });
136 if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
137 return fileApiXHR.statusText;
138 });
139 redefineProp(xhr, 'readyState', function () {
140 return 4;
141 });
142 if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
143 return fileApiXHR.response;
144 });
145 var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
146 redefineProp(xhr, 'responseText', function () {
147 return resp;
148 });
149 redefineProp(xhr, 'response', function () {
150 return resp;
151 });
152 if (err) redefineProp(xhr, 'err', function () {
153 return err;
154 });
155 xhr.__fileApiXHR = fileApiXHR;
156 if (xhr.onreadystatechange) xhr.onreadystatechange();
157 if (xhr.onload) xhr.onload();
158 },
159 progress: function (e) {
160 e.target = xhr;
161 if (xhr.__listeners.progress) xhr.__listeners.progress(e);
162 xhr.__total = e.total;
163 xhr.__loaded = e.loaded;
164 if (e.total === e.loaded) {
165 // fix flash issue that doesn't call complete if there is no response text from the server
166 var _this = this;
167 setTimeout(function () {
168 if (!xhr.__completed) {
169 xhr.getAllResponseHeaders = function () {
170 };
171 _this.complete(null, {status: 204, statusText: 'No Content'});
172 }
173 }, FileAPI.noContentTimeout || 10000);
174 }
175 },
176 headers: xhr.__requestHeaders
177 };
178 config.data = {};
179 config.files = {};
180 for (var i = 0; i < formData.data.length; i++) {
181 var item = formData.data[i];
182 if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
183 config.files[item.key] = item.val;
184 } else {
185 config.data[item.key] = item.val;
186 }
187 }
188
189 setTimeout(function () {
190 if (!FileAPI.hasFlash) {
191 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
192 }
193 xhr.__fileApiXHR = FileAPI.upload(config);
194 }, 1);
195 } else {
196 if (this.__origError) {
197 throw this.__origError;
198 }
199 orig.apply(xhr, arguments);
200 }
201 };
202 });
203 window.XMLHttpRequest.__isFileAPIShim = true;
204 window.FormData = FormData = function () {
205 return {
206 append: function (key, val, name) {
207 if (val.__isFileAPIBlobShim) {
208 val = val.data[0];
209 }
210 this.data.push({
211 key: key,
212 val: val,
213 name: name
214 });
215 },
216 data: [],
217 __isFileAPIShim: true
218 };
219 };
220
221 window.Blob = Blob = function (b) {
222 return {
223 data: b,
224 __isFileAPIBlobShim: true
225 };
226 };
227 }
228
229 })();
230
231 (function () {
232 /** @namespace FileAPI.forceLoad */
233 /** @namespace window.FileAPI.jsUrl */
234 /** @namespace window.FileAPI.jsPath */
235
236 function isInputTypeFile(elem) {
237 return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
238 }
239
240 function hasFlash() {
241 try {
242 var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
243 if (fo) return true;
244 } catch (e) {
245 if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
246 }
247 return false;
248 }
249
250 function getOffset(obj) {
251 var left = 0, top = 0;
252
253 if (window.jQuery) {
254 return jQuery(obj).offset();
255 }
256
257 if (obj.offsetParent) {
258 do {
259 left += (obj.offsetLeft - obj.scrollLeft);
260 top += (obj.offsetTop - obj.scrollTop);
261 obj = obj.offsetParent;
262 } while (obj);
263 }
264 return {
265 left: left,
266 top: top
267 };
268 }
269
270 if (FileAPI.shouldLoad) {
271 FileAPI.hasFlash = hasFlash();
272
273 //load FileAPI
274 if (FileAPI.forceLoad) {
275 FileAPI.html5 = false;
276 }
277
278 if (!FileAPI.upload) {
279 var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
280 if (window.FileAPI.jsUrl) {
281 jsUrl = window.FileAPI.jsUrl;
282 } else if (window.FileAPI.jsPath) {
283 basePath = window.FileAPI.jsPath;
284 } else {
285 for (i = 0; i < allScripts.length; i++) {
286 src = allScripts[i].src;
287 index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
288 if (index > -1) {
289 basePath = src.substring(0, index + 1);
290 break;
291 }
292 }
293 }
294
295 if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
296 script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
297 document.getElementsByTagName('head')[0].appendChild(script);
298 }
299
300 FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
301 if (!hasFlash()) {
302 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
303 }
304 var fixInputStyle = function () {
305 var label = fileElem.parent();
306 if (elem.attr('disabled')) {
307 if (label) label.removeClass('js-fileapi-wrapper');
308 } else {
309 if (!fileElem.attr('__ngf_flash_')) {
310 fileElem.unbind('change');
311 fileElem.unbind('click');
312 fileElem.bind('change', function (evt) {
313 fileApiChangeFn.apply(this, [evt]);
314 changeFn.apply(this, [evt]);
315 });
316 fileElem.attr('__ngf_flash_', 'true');
317 }
318 label.addClass('js-fileapi-wrapper');
319 if (!isInputTypeFile(elem)) {
320 label.css('position', 'absolute')
321 .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
322 .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
323 .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
324 .css('overflow', 'hidden').css('z-index', '900000')
325 .css('visibility', 'visible');
326 fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
327 .css('position', 'absolute').css('top', '0px').css('left', '0px');
328 }
329 }
330 };
331
332 elem.bind('mouseenter', fixInputStyle);
333
334 var fileApiChangeFn = function (evt) {
335 var files = FileAPI.getFiles(evt);
336 //just a double check for #233
337 for (var i = 0; i < files.length; i++) {
338 if (files[i].size === undefined) files[i].size = 0;
339 if (files[i].name === undefined) files[i].name = 'file';
340 if (files[i].type === undefined) files[i].type = 'undefined';
341 }
342 if (!evt.target) {
343 evt.target = {};
344 }
345 evt.target.files = files;
346 // if evt.target.files is not writable use helper field
347 if (evt.target.files !== files) {
348 evt.__files_ = files;
349 }
350 (evt.__files_ || evt.target.files).item = function (i) {
351 return (evt.__files_ || evt.target.files)[i] || null;
352 };
353 };
354 };
355
356 FileAPI.disableFileInput = function (elem, disable) {
357 if (disable) {
358 elem.removeClass('js-fileapi-wrapper');
359 } else {
360 elem.addClass('js-fileapi-wrapper');
361 }
362 };
363 }
364 })();
365
366 if (!window.FileReader) {
367 window.FileReader = function () {
368 var _this = this, loadStarted = false;
369 this.listeners = {};
370 this.addEventListener = function (type, fn) {
371 _this.listeners[type] = _this.listeners[type] || [];
372 _this.listeners[type].push(fn);
373 };
374 this.removeEventListener = function (type, fn) {
375 if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
376 };
377 this.dispatchEvent = function (evt) {
378 var list = _this.listeners[evt.type];
379 if (list) {
380 for (var i = 0; i < list.length; i++) {
381 list[i].call(_this, evt);
382 }
383 }
384 };
385 this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
386
387 var constructEvent = function (type, evt) {
388 var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
389 if (evt.result != null) e.target.result = evt.result;
390 return e;
391 };
392 var listener = function (evt) {
393 if (!loadStarted) {
394 loadStarted = true;
395 if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
396 }
397 var e;
398 if (evt.type === 'load') {
399 if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
400 e = constructEvent('load', evt);
401 if (_this.onload) _this.onload(e);
402 _this.dispatchEvent(e);
403 } else if (evt.type === 'progress') {
404 e = constructEvent('progress', evt);
405 if (_this.onprogress) _this.onprogress(e);
406 _this.dispatchEvent(e);
407 } else {
408 e = constructEvent('error', evt);
409 if (_this.onerror) _this.onerror(e);
410 _this.dispatchEvent(e);
411 }
412 };
413 this.readAsDataURL = function (file) {
414 FileAPI.readAsDataURL(file, listener);
415 };
416 this.readAsText = function (file) {
417 FileAPI.readAsText(file, listener);
418 };
419 };
420 }
+0
-2379
demo/src/main/webapp/js/ng-file-upload.js less more
0 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
2 * progress, resize, thumbnail, preview, validation and CORS
3 * @author Danial <danial.farid@gmail.com>
4 * @version 12.0.4
5 */
6
7 if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
8 window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {
9 return function (header, value) {
10 if (header === '__setXHR_') {
11 var val = value(this);
12 // fix for angular < 1.2.0
13 if (val instanceof Function) {
14 val(this);
15 }
16 } else {
17 orig.apply(this, arguments);
18 }
19 };
20 })(window.XMLHttpRequest.prototype.setRequestHeader);
21 }
22
23 var ngFileUpload = angular.module('ngFileUpload', []);
24
25 ngFileUpload.version = '12.0.4';
26
27 ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
28 var upload = this;
29 upload.promisesCount = 0;
30
31 this.isResumeSupported = function () {
32 return window.Blob && window.Blob.prototype.slice;
33 };
34
35 var resumeSupported = this.isResumeSupported();
36
37 function sendHttp(config) {
38 config.method = config.method || 'POST';
39 config.headers = config.headers || {};
40
41 var deferred = config._deferred = config._deferred || $q.defer();
42 var promise = deferred.promise;
43
44 function notifyProgress(e) {
45 if (deferred.notify) {
46 deferred.notify(e);
47 }
48 if (promise.progressFunc) {
49 $timeout(function () {
50 promise.progressFunc(e);
51 });
52 }
53 }
54
55 function getNotifyEvent(n) {
56 if (config._start != null && resumeSupported) {
57 return {
58 loaded: n.loaded + config._start,
59 total: (config._file && config._file.size) || n.total,
60 type: n.type, config: config,
61 lengthComputable: true, target: n.target
62 };
63 } else {
64 return n;
65 }
66 }
67
68 if (!config.disableProgress) {
69 config.headers.__setXHR_ = function () {
70 return function (xhr) {
71 if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;
72 config.__XHR = xhr;
73 if (config.xhrFn) config.xhrFn(xhr);
74 xhr.upload.addEventListener('progress', function (e) {
75 e.config = config;
76 notifyProgress(getNotifyEvent(e));
77 }, false);
78 //fix for firefox not firing upload progress end, also IE8-9
79 xhr.upload.addEventListener('load', function (e) {
80 if (e.lengthComputable) {
81 e.config = config;
82 notifyProgress(getNotifyEvent(e));
83 }
84 }, false);
85 };
86 };
87 }
88
89 function uploadWithAngular() {
90 $http(config).then(function (r) {
91 if (resumeSupported && config._chunkSize && !config._finished && config._file) {
92 notifyProgress({
93 loaded: config._end,
94 total: config._file && config._file.size,
95 config: config, type: 'progress'
96 }
97 );
98 upload.upload(config, true);
99 } else {
100 if (config._finished) delete config._finished;
101 deferred.resolve(r);
102 }
103 }, function (e) {
104 deferred.reject(e);
105 }, function (n) {
106 deferred.notify(n);
107 }
108 );
109 }
110
111 if (!resumeSupported) {
112 uploadWithAngular();
113 } else if (config._chunkSize && config._end && !config._finished) {
114 config._start = config._end;
115 config._end += config._chunkSize;
116 uploadWithAngular();
117 } else if (config.resumeSizeUrl) {
118 $http.get(config.resumeSizeUrl).then(function (resp) {
119 if (config.resumeSizeResponseReader) {
120 config._start = config.resumeSizeResponseReader(resp.data);
121 } else {
122 config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());
123 }
124 if (config._chunkSize) {
125 config._end = config._start + config._chunkSize;
126 }
127 uploadWithAngular();
128 }, function (e) {
129 throw e;
130 });
131 } else if (config.resumeSize) {
132 config.resumeSize().then(function (size) {
133 config._start = size;
134 uploadWithAngular();
135 }, function (e) {
136 throw e;
137 });
138 } else {
139 if (config._chunkSize) {
140 config._start = 0;
141 config._end = config._start + config._chunkSize;
142 }
143 uploadWithAngular();
144 }
145
146
147 promise.success = function (fn) {
148 promise.then(function (response) {
149 fn(response.data, response.status, response.headers, config);
150 });
151 return promise;
152 };
153
154 promise.error = function (fn) {
155 promise.then(null, function (response) {
156 fn(response.data, response.status, response.headers, config);
157 });
158 return promise;
159 };
160
161 promise.progress = function (fn) {
162 promise.progressFunc = fn;
163 promise.then(null, null, function (n) {
164 fn(n);
165 });
166 return promise;
167 };
168 promise.abort = promise.pause = function () {
169 if (config.__XHR) {
170 $timeout(function () {
171 config.__XHR.abort();
172 });
173 }
174 return promise;
175 };
176 promise.xhr = function (fn) {
177 config.xhrFn = (function (origXhrFn) {
178 return function () {
179 if (origXhrFn) origXhrFn.apply(promise, arguments);
180 fn.apply(promise, arguments);
181 };
182 })(config.xhrFn);
183 return promise;
184 };
185
186 upload.promisesCount++;
187 promise['finally'](function () {
188 upload.promisesCount--;
189 });
190 return promise;
191 }
192
193 this.isUploadInProgress = function () {
194 return upload.promisesCount > 0;
195 };
196
197 this.rename = function (file, name) {
198 file.ngfName = name;
199 return file;
200 };
201
202 this.jsonBlob = function (val) {
203 if (val != null && !angular.isString(val)) {
204 val = JSON.stringify(val);
205 }
206 var blob = new window.Blob([val], {type: 'application/json'});
207 blob._ngfBlob = true;
208 return blob;
209 };
210
211 this.json = function (val) {
212 return angular.toJson(val);
213 };
214
215 function copy(obj) {
216 var clone = {};
217 for (var key in obj) {
218 if (obj.hasOwnProperty(key)) {
219 clone[key] = obj[key];
220 }
221 }
222 return clone;
223 }
224
225 this.isFile = function (file) {
226 return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));
227 };
228
229 this.upload = function (config, internal) {
230 function toResumeFile(file, formData) {
231 if (file._ngfBlob) return file;
232 config._file = config._file || file;
233 if (config._start != null && resumeSupported) {
234 if (config._end && config._end >= file.size) {
235 config._finished = true;
236 config._end = file.size;
237 }
238 var slice = file.slice(config._start, config._end || file.size);
239 slice.name = file.name;
240 slice.ngfName = file.ngfName;
241 if (config._chunkSize) {
242 formData.append('_chunkSize', config._chunkSize);
243 formData.append('_currentChunkSize', config._end - config._start);
244 formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));
245 formData.append('_totalSize', config._file.size);
246 }
247 return slice;
248 }
249 return file;
250 }
251
252 function addFieldToFormData(formData, val, key) {
253 if (val !== undefined) {
254 if (angular.isDate(val)) {
255 val = val.toISOString();
256 }
257 if (angular.isString(val)) {
258 formData.append(key, val);
259 } else if (upload.isFile(val)) {
260 var file = toResumeFile(val, formData);
261 var split = key.split(',');
262 if (split[1]) {
263 file.ngfName = split[1].replace(/^\s+|\s+$/g, '');
264 key = split[0];
265 }
266 config._fileKey = config._fileKey || key;
267 formData.append(key, file, file.ngfName || file.name);
268 } else {
269 if (angular.isObject(val)) {
270 if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;
271
272 val.$$ngfCircularDetection = true;
273 try {
274 for (var k in val) {
275 if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {
276 var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
277 if (val.length && parseInt(k) > -1) {
278 objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
279 }
280 addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
281 }
282 }
283 } finally {
284 delete val.$$ngfCircularDetection;
285 }
286 } else {
287 formData.append(key, val);
288 }
289 }
290 }
291 }
292
293 function digestConfig() {
294 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
295 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
296
297 config.headers = config.headers || {};
298 config.headers['Content-Type'] = undefined;
299 config.transformRequest = config.transformRequest ?
300 (angular.isArray(config.transformRequest) ?
301 config.transformRequest : [config.transformRequest]) : [];
302 config.transformRequest.push(function (data) {
303 var formData = new window.FormData(), key;
304 data = data || config.fields || {};
305 if (config.file) {
306 data.file = config.file;
307 }
308 for (key in data) {
309 if (data.hasOwnProperty(key)) {
310 var val = data[key];
311 if (config.formDataAppender) {
312 config.formDataAppender(formData, key, val);
313 } else {
314 addFieldToFormData(formData, val, key);
315 }
316 }
317 }
318
319 return formData;
320 });
321 }
322
323 if (!internal) config = copy(config);
324 if (!config._isDigested) {
325 config._isDigested = true;
326 digestConfig();
327 }
328
329 return sendHttp(config);
330 };
331
332 this.http = function (config) {
333 config = copy(config);
334 config.transformRequest = config.transformRequest || function (data) {
335 if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {
336 return data;
337 }
338 return $http.defaults.transformRequest[0].apply(this, arguments);
339 };
340 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
341 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
342
343 return sendHttp(config);
344 };
345
346 this.translateScalars = function (str) {
347 if (angular.isString(str)) {
348 if (str.search(/kb/i) === str.length - 2) {
349 return parseFloat(str.substring(0, str.length - 2) * 1024);
350 } else if (str.search(/mb/i) === str.length - 2) {
351 return parseFloat(str.substring(0, str.length - 2) * 1048576);
352 } else if (str.search(/gb/i) === str.length - 2) {
353 return parseFloat(str.substring(0, str.length - 2) * 1073741824);
354 } else if (str.search(/b/i) === str.length - 1) {
355 return parseFloat(str.substring(0, str.length - 1));
356 } else if (str.search(/s/i) === str.length - 1) {
357 return parseFloat(str.substring(0, str.length - 1));
358 } else if (str.search(/m/i) === str.length - 1) {
359 return parseFloat(str.substring(0, str.length - 1) * 60);
360 } else if (str.search(/h/i) === str.length - 1) {
361 return parseFloat(str.substring(0, str.length - 1) * 3600);
362 }
363 }
364 return str;
365 };
366
367 this.urlToBlob = function(url) {
368 var defer = $q.defer();
369 $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {
370 var arrayBufferView = new Uint8Array(resp.data);
371 var type = resp.headers('content-type') || 'image/WebP';
372 var blob = new window.Blob([arrayBufferView], {type: type});
373 defer.resolve(blob);
374 //var split = type.split('[/;]');
375 //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
376 }, function (e) {
377 defer.reject(e);
378 });
379 return defer.promise;
380 };
381
382 this.setDefaults = function (defaults) {
383 this.defaults = defaults || {};
384 };
385
386 this.defaults = {};
387 this.version = ngFileUpload.version;
388 }
389
390 ]);
391
392 ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {
393 var upload = UploadExif;
394 upload.getAttrWithDefaults = function (attr, name) {
395 if (attr[name] != null) return attr[name];
396 var def = upload.defaults[name];
397 return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));
398 };
399
400 upload.attrGetter = function (name, attr, scope, params) {
401 var attrVal = this.getAttrWithDefaults(attr, name);
402 if (scope) {
403 try {
404 if (params) {
405 return $parse(attrVal)(scope, params);
406 } else {
407 return $parse(attrVal)(scope);
408 }
409 } catch (e) {
410 // hangle string value without single qoute
411 if (name.search(/min|max|pattern/i)) {
412 return attrVal;
413 } else {
414 throw e;
415 }
416 }
417 } else {
418 return attrVal;
419 }
420 };
421
422 upload.shouldUpdateOn = function (type, attr, scope) {
423 var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
424 if (modelOptions && modelOptions.updateOn) {
425 return modelOptions.updateOn.split(' ').indexOf(type) > -1;
426 }
427 return true;
428 };
429
430 upload.emptyPromise = function () {
431 var d = $q.defer();
432 var args = arguments;
433 $timeout(function () {
434 d.resolve.apply(d, args);
435 });
436 return d.promise;
437 };
438
439 upload.rejectPromise = function () {
440 var d = $q.defer();
441 var args = arguments;
442 $timeout(function () {
443 d.reject.apply(d, args);
444 });
445 return d.promise;
446 };
447
448 upload.happyPromise = function (promise, data) {
449 var d = $q.defer();
450 promise.then(function (result) {
451 d.resolve(result);
452 }, function (error) {
453 $timeout(function () {
454 throw error;
455 });
456 d.resolve(data);
457 });
458 return d.promise;
459 };
460
461 function applyExifRotations(files, attr, scope) {
462 var promises = [upload.emptyPromise()];
463 angular.forEach(files, function (f, i) {
464 if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {
465 promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {
466 files.splice(i, 1, fixedFile);
467 }));
468 }
469 });
470 return $q.all(promises);
471 }
472
473 function resize(files, attr, scope) {
474 var resizeVal = upload.attrGetter('ngfResize', attr, scope);
475 if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
476 if (resizeVal instanceof Function) {
477 var defer = $q.defer();
478 resizeVal(files).then(function (p) {
479 resizeWithParams(p, files, attr, scope).then(function (r) {
480 defer.resolve(r);
481 }, function (e) {
482 defer.reject(e);
483 });
484 }, function (e) {
485 defer.reject(e);
486 });
487 } else {
488 return resizeWithParams(resizeVal, files, attr, scope);
489 }
490 }
491
492 function resizeWithParams(param, files, attr, scope) {
493 var promises = [upload.emptyPromise()];
494
495 function handleFile(f, i) {
496 if (f.type.indexOf('image') === 0) {
497 if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
498 var promise = upload.resize(f, param.width, param.height, param.quality,
499 param.type, param.ratio, param.centerCrop, function (width, height) {
500 return upload.attrGetter('ngfResizeIf', attr, scope,
501 {$width: width, $height: height, $file: f});
502 }, param.restoreExif !== false);
503 promises.push(promise);
504 promise.then(function (resizedFile) {
505 files.splice(i, 1, resizedFile);
506 }, function (e) {
507 f.$error = 'resize';
508 f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
509 });
510 }
511 }
512
513 for (var i = 0; i < files.length; i++) {
514 handleFile(files[i], i);
515 }
516 return $q.all(promises);
517 }
518
519 upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
520 function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {
521 attr.$$ngfPrevValidFiles = files;
522 attr.$$ngfPrevInvalidFiles = invalidFiles;
523 var file = files && files.length ? files[0] : null;
524 var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;
525
526 if (ngModel) {
527 upload.applyModelValidation(ngModel, files);
528 ngModel.$setViewValue(isSingleModel ? file : files);
529 }
530
531 if (fileChange) {
532 $parse(fileChange)(scope, {
533 $files: files,
534 $file: file,
535 $newFiles: newFiles,
536 $duplicateFiles: dupFiles,
537 $invalidFiles: invalidFiles,
538 $invalidFile: invalidFile,
539 $event: evt
540 });
541 }
542
543 var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
544 if (invalidModel) {
545 $timeout(function () {
546 $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);
547 });
548 }
549 $timeout(function () {
550 // scope apply changes
551 });
552 }
553
554 var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,
555 invalids = [], valids = [];
556
557 function removeDuplicates() {
558 function equals(f1, f2) {
559 return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&
560 f1.type === f2.type;
561 }
562
563 function isInPrevFiles(f) {
564 var j;
565 for (j = 0; j < prevValidFiles.length; j++) {
566 if (equals(f, prevValidFiles[j])) {
567 return true;
568 }
569 }
570 for (j = 0; j < prevInvalidFiles.length; j++) {
571 if (equals(f, prevInvalidFiles[j])) {
572 return true;
573 }
574 }
575 return false;
576 }
577
578 if (files) {
579 allNewFiles = [];
580 dupFiles = [];
581 for (var i = 0; i < files.length; i++) {
582 if (isInPrevFiles(files[i])) {
583 dupFiles.push(files[i]);
584 } else {
585 allNewFiles.push(files[i]);
586 }
587 }
588 }
589 }
590
591 function toArray(v) {
592 return angular.isArray(v) ? v : [v];
593 }
594
595 function separateInvalids() {
596 valids = [];
597 invalids = [];
598 angular.forEach(allNewFiles, function (file) {
599 if (file.$error) {
600 invalids.push(file);
601 } else {
602 valids.push(file);
603 }
604 });
605 }
606
607 function resizeAndUpdate() {
608 function updateModel() {
609 $timeout(function () {
610 update(keep ? prevValidFiles.concat(valids) : valids,
611 keep ? prevInvalidFiles.concat(invalids) : invalids,
612 files, dupFiles, isSingleModel);
613 }, options && options.debounce ? options.debounce.change || options.debounce : 0);
614 }
615
616 resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () {
617 if (validateAfterResize) {
618 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
619 separateInvalids();
620 updateModel();
621 });
622 } else {
623 updateModel();
624 }
625 }, function (e) {
626 throw 'Could not resize files ' + e;
627 });
628 }
629
630 prevValidFiles = attr.$$ngfPrevValidFiles || [];
631 prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];
632 if (ngModel && ngModel.$modelValue) {
633 prevValidFiles = toArray(ngModel.$modelValue);
634 }
635
636 var keep = upload.attrGetter('ngfKeep', attr, scope);
637 allNewFiles = (files || []).slice(0);
638 if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {
639 removeDuplicates(attr, scope);
640 }
641
642 var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);
643
644 if (keep && !allNewFiles.length) return;
645
646 upload.attrGetter('ngfBeforeModelChange', attr, scope, {
647 $files: files,
648 $file: files && files.length ? files[0] : null,
649 $newFiles: allNewFiles,
650 $duplicateFiles: dupFiles,
651 $event: evt
652 });
653
654 var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
655
656 var options = upload.attrGetter('ngModelOptions', attr, scope);
657 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
658 if (noDelay) {
659 update(allNewFiles, [], files, dupFiles, isSingleModel);
660 } else {
661 if ((!options || !options.allowInvalid) && !validateAfterResize) {
662 separateInvalids();
663 } else {
664 valids = allNewFiles;
665 }
666 if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {
667 applyExifRotations(valids, attr, scope).then(function () {
668 resizeAndUpdate();
669 });
670 } else {
671 resizeAndUpdate();
672 }
673 }
674 });
675 };
676
677 return upload;
678 }]);
679
680 ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {
681 var generatedElems = [];
682
683 function isDelayedClickSupported(ua) {
684 // fix for android native browser < 4.4 and safari windows
685 var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/);
686 if (m && m.length > 2) {
687 var v = Upload.defaults.androidFixMinorVersion || 4;
688 return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);
689 }
690
691 // safari on windows
692 return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);
693 }
694
695 function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {
696 /** @namespace attr.ngfSelect */
697 /** @namespace attr.ngfChange */
698 /** @namespace attr.ngModel */
699 /** @namespace attr.ngModelOptions */
700 /** @namespace attr.ngfMultiple */
701 /** @namespace attr.ngfCapture */
702 /** @namespace attr.ngfValidate */
703 /** @namespace attr.ngfKeep */
704 var attrGetter = function (name, scope) {
705 return upload.attrGetter(name, attr, scope);
706 };
707
708 function isInputTypeFile() {
709 return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';
710 }
711
712 function fileChangeAttr() {
713 return attrGetter('ngfChange') || attrGetter('ngfSelect');
714 }
715
716 function changeFn(evt) {
717 if (upload.shouldUpdateOn('change', attr, scope)) {
718 var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
719 for (var i = 0; i < fileList.length; i++) {
720 files.push(fileList[i]);
721 }
722 upload.updateModel(ngModel, attr, scope, fileChangeAttr(),
723 files.length ? files : null, evt);
724 }
725 }
726
727 upload.registerModelChangeValidator(ngModel, attr, scope);
728
729 var unwatches = [];
730 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
731 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
732 }));
733 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
734 fileElem.attr('capture', attrGetter('ngfCapture', scope));
735 }));
736 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
737 fileElem.attr('accept', attrGetter('ngfAccept', scope));
738 }));
739 attr.$observe('accept', function () {
740 fileElem.attr('accept', attrGetter('accept'));
741 });
742 unwatches.push(function () {
743 if (attr.$$observers) delete attr.$$observers.accept;
744 });
745 function bindAttrToFileInput(fileElem) {
746 if (elem !== fileElem) {
747 for (var i = 0; i < elem[0].attributes.length; i++) {
748 var attribute = elem[0].attributes[i];
749 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
750 if (attribute.value == null || attribute.value === '') {
751 if (attribute.name === 'required') attribute.value = 'required';
752 if (attribute.name === 'multiple') attribute.value = 'multiple';
753 }
754 fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
755 }
756 }
757 }
758 }
759
760 function createFileInput() {
761 if (isInputTypeFile()) {
762 return elem;
763 }
764
765 var fileElem = angular.element('<input type="file">');
766
767 bindAttrToFileInput(fileElem);
768
769 var label = angular.element('<label>upload</label>');
770 label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
771 .css('width', '0px').css('height', '0px').css('border', 'none')
772 .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
773 generatedElems.push({el: elem, ref: label});
774
775 document.body.appendChild(label.append(fileElem)[0]);
776
777 return fileElem;
778 }
779
780 var initialTouchStartY = 0;
781
782 function clickHandler(evt) {
783 if (elem.attr('disabled')) return false;
784 if (attrGetter('ngfSelectDisabled', scope)) return;
785
786 var r = handleTouch(evt);
787 if (r != null) return r;
788
789 resetModel(evt);
790
791 // fix for md when the element is removed from the DOM and added back #460
792 try {
793 if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {
794 generatedElems.push({el: elem, ref: fileElem.parent()});
795 document.body.appendChild(fileElem.parent()[0]);
796 fileElem.bind('change', changeFn);
797 }
798 } catch(e){/*ignore*/}
799
800 if (isDelayedClickSupported(navigator.userAgent)) {
801 setTimeout(function () {
802 fileElem[0].click();
803 }, 0);
804 } else {
805 fileElem[0].click();
806 }
807
808 return false;
809 }
810
811 function handleTouch(evt) {
812 var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
813 if (evt.type === 'touchstart') {
814 initialTouchStartY = touches ? touches[0].clientY : 0;
815 return true; // don't block event default
816 } else {
817 evt.stopPropagation();
818 evt.preventDefault();
819
820 // prevent scroll from triggering event
821 if (evt.type === 'touchend') {
822 var currentLocation = touches ? touches[0].clientY : 0;
823 if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
824 }
825 }
826 }
827
828 var fileElem = elem;
829
830 function resetModel(evt) {
831 if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {
832 fileElem.val(null);
833 upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);
834 }
835 }
836
837 if (!isInputTypeFile()) {
838 fileElem = createFileInput();
839 }
840 fileElem.bind('change', changeFn);
841
842 if (!isInputTypeFile()) {
843 elem.bind('click touchstart touchend', clickHandler);
844 } else {
845 elem.bind('click', resetModel);
846 }
847
848 function ie10SameFileSelectFix(evt) {
849 if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {
850 if (!fileElem[0].parentNode) {
851 fileElem = null;
852 return;
853 }
854 evt.preventDefault();
855 evt.stopPropagation();
856 fileElem.unbind('click');
857 var clone = fileElem.clone();
858 fileElem.replaceWith(clone);
859 fileElem = clone;
860 fileElem.attr('__ngf_ie10_Fix_', 'true');
861 fileElem.bind('change', changeFn);
862 fileElem.bind('click', ie10SameFileSelectFix);
863 fileElem[0].click();
864 return false;
865 } else {
866 fileElem.removeAttr('__ngf_ie10_Fix_');
867 }
868 }
869
870 if (navigator.appVersion.indexOf('MSIE 10') !== -1) {
871 fileElem.bind('click', ie10SameFileSelectFix);
872 }
873
874 if (ngModel) ngModel.$formatters.push(function (val) {
875 if (val == null || val.length === 0) {
876 if (fileElem.val()) {
877 fileElem.val(null);
878 }
879 }
880 return val;
881 });
882
883 scope.$on('$destroy', function () {
884 if (!isInputTypeFile()) fileElem.parent().remove();
885 angular.forEach(unwatches, function (unwatch) {
886 unwatch();
887 });
888 });
889
890 $timeout(function () {
891 for (var i = 0; i < generatedElems.length; i++) {
892 var g = generatedElems[i];
893 if (!document.body.contains(g.el[0])) {
894 generatedElems.splice(i, 1);
895 g.ref.remove();
896 }
897 }
898 });
899
900 if (window.FileAPI && window.FileAPI.ngfFixIE) {
901 window.FileAPI.ngfFixIE(elem, fileElem, changeFn);
902 }
903 }
904
905 return {
906 restrict: 'AEC',
907 require: '?ngModel',
908 link: function (scope, elem, attr, ngModel) {
909 linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);
910 }
911 };
912 }]);
913
914 (function () {
915
916 ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {
917 var upload = UploadBase;
918 upload.base64DataUrl = function (file) {
919 if (angular.isArray(file)) {
920 var d = $q.defer(), count = 0;
921 angular.forEach(file, function (f) {
922 upload.dataUrl(f, true)['finally'](function () {
923 count++;
924 if (count === file.length) {
925 var urls = [];
926 angular.forEach(file, function (ff) {
927 urls.push(ff.$ngfDataUrl);
928 });
929 d.resolve(urls, file);
930 }
931 });
932 });
933 return d.promise;
934 } else {
935 return upload.dataUrl(file, true);
936 }
937 };
938 upload.dataUrl = function (file, disallowObjectUrl) {
939 if (!file) return upload.emptyPromise(file, file);
940 if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {
941 return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);
942 }
943 var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;
944 if (p) return p;
945
946 var deferred = $q.defer();
947 $timeout(function () {
948 if (window.FileReader && file &&
949 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&
950 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {
951 //prefer URL.createObjectURL for handling refrences to files of all sizes
952 //since it doesn´t build a large string in memory
953 var URL = window.URL || window.webkitURL;
954 if (URL && URL.createObjectURL && !disallowObjectUrl) {
955 var url;
956 try {
957 url = URL.createObjectURL(file);
958 } catch (e) {
959 $timeout(function () {
960 file.$ngfBlobUrl = '';
961 deferred.reject();
962 });
963 return;
964 }
965 $timeout(function () {
966 file.$ngfBlobUrl = url;
967 if (url) {
968 deferred.resolve(url, file);
969 upload.blobUrls = upload.blobUrls || [];
970 upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;
971 upload.blobUrls.push({url: url, size: file.size});
972 upload.blobUrlsTotalSize += file.size || 0;
973 var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;
974 var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;
975 while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {
976 var obj = upload.blobUrls.splice(0, 1)[0];
977 URL.revokeObjectURL(obj.url);
978 upload.blobUrlsTotalSize -= obj.size;
979 }
980 }
981 });
982 } else {
983 var fileReader = new FileReader();
984 fileReader.onload = function (e) {
985 $timeout(function () {
986 file.$ngfDataUrl = e.target.result;
987 deferred.resolve(e.target.result, file);
988 $timeout(function () {
989 delete file.$ngfDataUrl;
990 }, 1000);
991 });
992 };
993 fileReader.onerror = function () {
994 $timeout(function () {
995 file.$ngfDataUrl = '';
996 deferred.reject();
997 });
998 };
999 fileReader.readAsDataURL(file);
1000 }
1001 } else {
1002 $timeout(function () {
1003 file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';
1004 deferred.reject();
1005 });
1006 }
1007 });
1008
1009 if (disallowObjectUrl) {
1010 p = file.$$ngfDataUrlPromise = deferred.promise;
1011 } else {
1012 p = file.$$ngfBlobUrlPromise = deferred.promise;
1013 }
1014 p['finally'](function () {
1015 delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];
1016 });
1017 return p;
1018 };
1019 return upload;
1020 }]);
1021
1022 function getTagType(el) {
1023 if (el.tagName.toLowerCase() === 'img') return 'image';
1024 if (el.tagName.toLowerCase() === 'audio') return 'audio';
1025 if (el.tagName.toLowerCase() === 'video') return 'video';
1026 return /./;
1027 }
1028
1029 function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {
1030 function constructDataUrl(file) {
1031 var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);
1032 Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {
1033 $timeout(function () {
1034 var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;
1035 if (isBackground) {
1036 elem.css('background-image', 'url(\'' + (src || '') + '\')');
1037 } else {
1038 elem.attr('src', src);
1039 }
1040 if (src) {
1041 elem.removeClass('ng-hide');
1042 } else {
1043 elem.addClass('ng-hide');
1044 }
1045 });
1046 });
1047 }
1048
1049 $timeout(function () {
1050 var unwatch = scope.$watch(attr[directiveName], function (file) {
1051 var size = resizeParams;
1052 if (directiveName === 'ngfThumbnail') {
1053 if (!size) {
1054 size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
1055 }
1056 if (size.width === 0 && window.getComputedStyle) {
1057 var style = getComputedStyle(elem[0]);
1058 size = {
1059 width: parseInt(style.width.slice(0, -2)),
1060 height: parseInt(style.height.slice(0, -2))
1061 };
1062 }
1063 }
1064
1065 if (angular.isString(file)) {
1066 elem.removeClass('ng-hide');
1067 if (isBackground) {
1068 return elem.css('background-image', 'url(\'' + file + '\')');
1069 } else {
1070 return elem.attr('src', file);
1071 }
1072 }
1073 if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
1074 (!isBackground || file.type.indexOf('image') === 0)) {
1075 if (size && Upload.isResizeSupported()) {
1076 Upload.resize(file, size.width, size.height, size.quality).then(
1077 function (f) {
1078 constructDataUrl(f);
1079 }, function (e) {
1080 throw e;
1081 }
1082 );
1083 } else {
1084 constructDataUrl(file);
1085 }
1086 } else {
1087 elem.addClass('ng-hide');
1088 }
1089 });
1090
1091 scope.$on('$destroy', function () {
1092 unwatch();
1093 });
1094 });
1095 }
1096
1097
1098 /** @namespace attr.ngfSrc */
1099 /** @namespace attr.ngfNoObjectUrl */
1100 ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {
1101 return {
1102 restrict: 'AE',
1103 link: function (scope, elem, attr) {
1104 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',
1105 Upload.attrGetter('ngfResize', attr, scope), false);
1106 }
1107 };
1108 }]);
1109
1110 /** @namespace attr.ngfBackground */
1111 /** @namespace attr.ngfNoObjectUrl */
1112 ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {
1113 return {
1114 restrict: 'AE',
1115 link: function (scope, elem, attr) {
1116 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',
1117 Upload.attrGetter('ngfResize', attr, scope), true);
1118 }
1119 };
1120 }]);
1121
1122 /** @namespace attr.ngfThumbnail */
1123 /** @namespace attr.ngfAsBackground */
1124 /** @namespace attr.ngfSize */
1125 /** @namespace attr.ngfNoObjectUrl */
1126 ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {
1127 return {
1128 restrict: 'AE',
1129 link: function (scope, elem, attr) {
1130 var size = Upload.attrGetter('ngfSize', attr, scope);
1131 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,
1132 Upload.attrGetter('ngfAsBackground', attr, scope));
1133 }
1134 };
1135 }]);
1136
1137 ngFileUpload.config(['$compileProvider', function ($compileProvider) {
1138 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1139 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1140 }]);
1141
1142 ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
1143 return function (file, disallowObjectUrl, trustedUrl) {
1144 if (angular.isString(file)) {
1145 return $sce.trustAsResourceUrl(file);
1146 }
1147 var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);
1148 if (file && !src) {
1149 if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {
1150 file.$ngfDataUrlFilterInProgress = true;
1151 UploadDataUrl.dataUrl(file, disallowObjectUrl);
1152 }
1153 return '';
1154 }
1155 if (file) delete file.$ngfDataUrlFilterInProgress;
1156 return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';
1157 };
1158 }]);
1159
1160 })();
1161
1162 ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {
1163 var upload = UploadDataUrl;
1164
1165 function globStringToRegex(str) {
1166 var regexp = '', excludes = [];
1167 if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {
1168 regexp = str.substring(1, str.length - 1);
1169 } else {
1170 var split = str.split(',');
1171 if (split.length > 1) {
1172 for (var i = 0; i < split.length; i++) {
1173 var r = globStringToRegex(split[i]);
1174 if (r.regexp) {
1175 regexp += '(' + r.regexp + ')';
1176 if (i < split.length - 1) {
1177 regexp += '|';
1178 }
1179 } else {
1180 excludes = excludes.concat(r.excludes);
1181 }
1182 }
1183 } else {
1184 if (str.indexOf('!') === 0) {
1185 excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');
1186 } else {
1187 if (str.indexOf('.') === 0) {
1188 str = '*' + str;
1189 }
1190 regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$';
1191 regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
1192 }
1193 }
1194 }
1195 return {regexp: regexp, excludes: excludes};
1196 }
1197
1198 upload.validatePattern = function (file, val) {
1199 if (!val) {
1200 return true;
1201 }
1202 var pattern = globStringToRegex(val), valid = true;
1203 if (pattern.regexp && pattern.regexp.length) {
1204 var regexp = new RegExp(pattern.regexp, 'i');
1205 valid = (file.type != null && regexp.test(file.type)) ||
1206 (file.name != null && regexp.test(file.name));
1207 }
1208 var len = pattern.excludes.length;
1209 while (len--) {
1210 var exclude = new RegExp(pattern.excludes[len], 'i');
1211 valid = valid && (file.type == null || exclude.test(file.type)) &&
1212 (file.name == null || exclude.test(file.name));
1213 }
1214 return valid;
1215 };
1216
1217 upload.ratioToFloat = function (val) {
1218 var r = val.toString(), xIndex = r.search(/[x:]/i);
1219 if (xIndex > -1) {
1220 r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));
1221 } else {
1222 r = parseFloat(r);
1223 }
1224 return r;
1225 };
1226
1227 upload.registerModelChangeValidator = function (ngModel, attr, scope) {
1228 if (ngModel) {
1229 ngModel.$formatters.push(function (files) {
1230 if (ngModel.$dirty) {
1231 if (files && !angular.isArray(files)) {
1232 files = [files];
1233 }
1234 upload.validate(files, 0, ngModel, attr, scope).then(function () {
1235 upload.applyModelValidation(ngModel, files);
1236 });
1237 }
1238 });
1239 }
1240 };
1241
1242 function markModelAsDirty(ngModel, files) {
1243 if (files != null && !ngModel.$dirty) {
1244 if (ngModel.$setDirty) {
1245 ngModel.$setDirty();
1246 } else {
1247 ngModel.$dirty = true;
1248 }
1249 }
1250 }
1251
1252 upload.applyModelValidation = function (ngModel, files) {
1253 markModelAsDirty(ngModel, files);
1254 angular.forEach(ngModel.$ngfValidations, function (validation) {
1255 ngModel.$setValidity(validation.name, validation.valid);
1256 });
1257 };
1258
1259 upload.getValidationAttr = function (attr, scope, name, validationName, file) {
1260 var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);
1261 var val = upload.attrGetter(dName, attr, scope, {$file: file});
1262 if (val == null) {
1263 val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});
1264 if (val) {
1265 var split = (validationName || name).split('.');
1266 val = val[split[0]];
1267 if (split.length > 1) {
1268 val = val && val[split[1]];
1269 }
1270 }
1271 }
1272 return val;
1273 };
1274
1275 upload.validate = function (files, prevLength, ngModel, attr, scope) {
1276 ngModel = ngModel || {};
1277 ngModel.$ngfValidations = ngModel.$ngfValidations || [];
1278
1279 angular.forEach(ngModel.$ngfValidations, function (v) {
1280 v.valid = true;
1281 });
1282
1283 var attrGetter = function (name, params) {
1284 return upload.attrGetter(name, attr, scope, params);
1285 };
1286
1287 if (files == null || files.length === 0) {
1288 return upload.emptyPromise(ngModel);
1289 }
1290
1291 files = files.length === undefined ? [files] : files.slice(0);
1292
1293 function validateSync(name, validationName, fn) {
1294 if (files) {
1295 var i = files.length, valid = null;
1296 while (i--) {
1297 var file = files[i];
1298 if (file) {
1299 var val = upload.getValidationAttr(attr, scope, name, validationName, file);
1300 if (val != null) {
1301 if (!fn(file, val, i)) {
1302 file.$error = name;
1303 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1304 file.$errorParam = val;
1305 files.splice(i, 1);
1306 valid = false;
1307 }
1308 }
1309 }
1310 }
1311 if (valid !== null) {
1312 ngModel.$ngfValidations.push({name: name, valid: valid});
1313 }
1314 }
1315 }
1316
1317 validateSync('maxFiles', null, function (file, val, i) {
1318 return prevLength + i < val;
1319 });
1320 validateSync('pattern', null, upload.validatePattern);
1321 validateSync('minSize', 'size.min', function (file, val) {
1322 return file.size + 0.1 >= upload.translateScalars(val);
1323 });
1324 validateSync('maxSize', 'size.max', function (file, val) {
1325 return file.size - 0.1 <= upload.translateScalars(val);
1326 });
1327 var totalSize = 0;
1328 validateSync('maxTotalSize', null, function (file, val) {
1329 totalSize += file.size;
1330 if (totalSize > upload.translateScalars(val)) {
1331 files.splice(0, files.length);
1332 return false;
1333 }
1334 return true;
1335 });
1336
1337 validateSync('validateFn', null, function (file, r) {
1338 return r === true || r === null || r === '';
1339 });
1340
1341 if (!files.length) {
1342 return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
1343 }
1344
1345 function validateAsync(name, validationName, type, asyncFn, fn) {
1346 function resolveResult(defer, file, val) {
1347 if (val != null) {
1348 asyncFn(file, val).then(function (d) {
1349 if (!fn(d, val)) {
1350 file.$error = name;
1351 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1352 file.$errorParam = val;
1353 defer.reject();
1354 } else {
1355 defer.resolve();
1356 }
1357 }, function () {
1358 if (attrGetter('ngfValidateForce', {$file: file})) {
1359 file.$error = name;
1360 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1361 file.$errorParam = val;
1362 defer.reject();
1363 } else {
1364 defer.resolve();
1365 }
1366 });
1367 } else {
1368 defer.resolve();
1369 }
1370 }
1371
1372 var promises = [upload.emptyPromise()];
1373 if (files) {
1374 files = files.length === undefined ? [files] : files;
1375 angular.forEach(files, function (file) {
1376 var defer = $q.defer();
1377 promises.push(defer.promise);
1378 if (type && (file.type == null || file.type.search(type) !== 0)) {
1379 defer.resolve();
1380 return;
1381 }
1382 if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {
1383 upload.imageDimensions(file).then(function (d) {
1384 resolveResult(defer, file,
1385 attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));
1386 }, function () {
1387 defer.reject();
1388 });
1389 } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {
1390 upload.mediaDuration(file).then(function (d) {
1391 resolveResult(defer, file,
1392 attrGetter('ngfDuration', {$file: file, $duration: d}));
1393 }, function () {
1394 defer.reject();
1395 });
1396 } else {
1397 resolveResult(defer, file,
1398 upload.getValidationAttr(attr, scope, name, validationName, file));
1399 }
1400 });
1401 return $q.all(promises).then(function () {
1402 ngModel.$ngfValidations.push({name: name, valid: true});
1403 }, function () {
1404 ngModel.$ngfValidations.push({name: name, valid: false});
1405 });
1406 }
1407 }
1408
1409 var deffer = $q.defer();
1410 var promises = [];
1411
1412 promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/,
1413 this.imageDimensions, function (d, val) {
1414 return d.height <= val;
1415 })));
1416 promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/,
1417 this.imageDimensions, function (d, val) {
1418 return d.height >= val;
1419 })));
1420 promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/,
1421 this.imageDimensions, function (d, val) {
1422 return d.width <= val;
1423 })));
1424 promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/,
1425 this.imageDimensions, function (d, val) {
1426 return d.width >= val;
1427 })));
1428 promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/,
1429 function (file, val) {
1430 return upload.emptyPromise(val);
1431 }, function (r) {
1432 return r;
1433 })));
1434 promises.push(upload.happyPromise(validateAsync('ratio', null, /image/,
1435 this.imageDimensions, function (d, val) {
1436 var split = val.toString().split(','), valid = false;
1437 for (var i = 0; i < split.length; i++) {
1438 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
1439 valid = true;
1440 }
1441 }
1442 return valid;
1443 })));
1444 promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/,
1445 this.imageDimensions, function (d, val) {
1446 return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
1447 })));
1448 promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/,
1449 this.imageDimensions, function (d, val) {
1450 return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
1451 })));
1452 promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/,
1453 this.mediaDuration, function (d, val) {
1454 return d <= upload.translateScalars(val);
1455 })));
1456 promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/,
1457 this.mediaDuration, function (d, val) {
1458 return d >= upload.translateScalars(val);
1459 })));
1460 promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/,
1461 function (file, val) {
1462 return upload.emptyPromise(val);
1463 }, function (r) {
1464 return r;
1465 })));
1466
1467 promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null,
1468 function (file, val) {
1469 return val;
1470 }, function (r) {
1471 return r === true || r === null || r === '';
1472 })));
1473
1474 return $q.all(promises).then(function () {
1475 deffer.resolve(ngModel, ngModel.$ngfValidations);
1476 });
1477 };
1478
1479 upload.imageDimensions = function (file) {
1480 if (file.$ngfWidth && file.$ngfHeight) {
1481 var d = $q.defer();
1482 $timeout(function () {
1483 d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});
1484 });
1485 return d.promise;
1486 }
1487 if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;
1488
1489 var deferred = $q.defer();
1490 $timeout(function () {
1491 if (file.type.indexOf('image') !== 0) {
1492 deferred.reject('not image');
1493 return;
1494 }
1495 upload.dataUrl(file).then(function (dataUrl) {
1496 var img = angular.element('<img>').attr('src', dataUrl)
1497 .css('visibility', 'hidden').css('position', 'fixed')
1498 .css('max-width', 'none !important').css('max-height', 'none !important');
1499
1500 function success() {
1501 var width = img[0].clientWidth;
1502 var height = img[0].clientHeight;
1503 img.remove();
1504 file.$ngfWidth = width;
1505 file.$ngfHeight = height;
1506 deferred.resolve({width: width, height: height});
1507 }
1508
1509 function error() {
1510 img.remove();
1511 deferred.reject('load error');
1512 }
1513
1514 img.on('load', success);
1515 img.on('error', error);
1516 var count = 0;
1517
1518 function checkLoadError() {
1519 $timeout(function () {
1520 if (img[0].parentNode) {
1521 if (img[0].clientWidth) {
1522 success();
1523 } else if (count > 10) {
1524 error();
1525 } else {
1526 checkLoadError();
1527 }
1528 }
1529 }, 1000);
1530 }
1531
1532 checkLoadError();
1533
1534 angular.element(document.getElementsByTagName('body')[0]).append(img);
1535 }, function () {
1536 deferred.reject('load error');
1537 });
1538 });
1539
1540 file.$ngfDimensionPromise = deferred.promise;
1541 file.$ngfDimensionPromise['finally'](function () {
1542 delete file.$ngfDimensionPromise;
1543 });
1544 return file.$ngfDimensionPromise;
1545 };
1546
1547 upload.mediaDuration = function (file) {
1548 if (file.$ngfDuration) {
1549 var d = $q.defer();
1550 $timeout(function () {
1551 d.resolve(file.$ngfDuration);
1552 });
1553 return d.promise;
1554 }
1555 if (file.$ngfDurationPromise) return file.$ngfDurationPromise;
1556
1557 var deferred = $q.defer();
1558 $timeout(function () {
1559 if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {
1560 deferred.reject('not media');
1561 return;
1562 }
1563 upload.dataUrl(file).then(function (dataUrl) {
1564 var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')
1565 .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');
1566
1567 function success() {
1568 var duration = el[0].duration;
1569 file.$ngfDuration = duration;
1570 el.remove();
1571 deferred.resolve(duration);
1572 }
1573
1574 function error() {
1575 el.remove();
1576 deferred.reject('load error');
1577 }
1578
1579 el.on('loadedmetadata', success);
1580 el.on('error', error);
1581 var count = 0;
1582
1583 function checkLoadError() {
1584 $timeout(function () {
1585 if (el[0].parentNode) {
1586 if (el[0].duration) {
1587 success();
1588 } else if (count > 10) {
1589 error();
1590 } else {
1591 checkLoadError();
1592 }
1593 }
1594 }, 1000);
1595 }
1596
1597 checkLoadError();
1598
1599 angular.element(document.body).append(el);
1600 }, function () {
1601 deferred.reject('load error');
1602 });
1603 });
1604
1605 file.$ngfDurationPromise = deferred.promise;
1606 file.$ngfDurationPromise['finally'](function () {
1607 delete file.$ngfDurationPromise;
1608 });
1609 return file.$ngfDurationPromise;
1610 };
1611 return upload;
1612 }
1613 ]);
1614
1615 ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {
1616 var upload = UploadValidate;
1617
1618 /**
1619 * Conserve aspect ratio of the original region. Useful when shrinking/enlarging
1620 * images to fit into a certain area.
1621 * Source: http://stackoverflow.com/a/14731922
1622 *
1623 * @param {Number} srcWidth Source area width
1624 * @param {Number} srcHeight Source area height
1625 * @param {Number} maxWidth Nestable area maximum available width
1626 * @param {Number} maxHeight Nestable area maximum available height
1627 * @return {Object} { width, height }
1628 */
1629 var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {
1630 var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :
1631 Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
1632 return {
1633 width: srcWidth * ratio, height: srcHeight * ratio,
1634 marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight
1635 };
1636 };
1637
1638 // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89
1639 var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {
1640 var deferred = $q.defer();
1641 var canvasElement = document.createElement('canvas');
1642 var imageElement = document.createElement('img');
1643
1644 imageElement.onload = function () {
1645 if (resizeIf != null && resizeIf(imageElement.width, imageElement.height) === false) {
1646 deferred.reject('resizeIf');
1647 return;
1648 }
1649 try {
1650 if (ratio) {
1651 var ratioFloat = upload.ratioToFloat(ratio);
1652 var imgRatio = imageElement.width / imageElement.height;
1653 if (imgRatio < ratioFloat) {
1654 width = imageElement.width;
1655 height = width / ratioFloat;
1656 } else {
1657 height = imageElement.height;
1658 width = height * ratioFloat;
1659 }
1660 }
1661 if (!width) {
1662 width = imageElement.width;
1663 }
1664 if (!height) {
1665 height = imageElement.height;
1666 }
1667 var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
1668 canvasElement.width = Math.min(dimensions.width, width);
1669 canvasElement.height = Math.min(dimensions.height, height);
1670 var context = canvasElement.getContext('2d');
1671 context.drawImage(imageElement,
1672 Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),
1673 dimensions.width, dimensions.height);
1674 deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));
1675 } catch (e) {
1676 deferred.reject(e);
1677 }
1678 };
1679 imageElement.onerror = function () {
1680 deferred.reject();
1681 };
1682 imageElement.src = imagen;
1683 return deferred.promise;
1684 };
1685
1686 upload.dataUrltoBlob = function (dataurl, name, origSize) {
1687 var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
1688 bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
1689 while (n--) {
1690 u8arr[n] = bstr.charCodeAt(n);
1691 }
1692 var blob = new window.Blob([u8arr], {type: mime});
1693 blob.name = name;
1694 blob.$ngfOrigSize = origSize;
1695 return blob;
1696 };
1697
1698 upload.isResizeSupported = function () {
1699 var elem = document.createElement('canvas');
1700 return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;
1701 };
1702
1703 if (upload.isResizeSupported()) {
1704 // add name getter to the blob constructor prototype
1705 Object.defineProperty(window.Blob.prototype, 'name', {
1706 get: function () {
1707 return this.$ngfName;
1708 },
1709 set: function (v) {
1710 this.$ngfName = v;
1711 },
1712 configurable: true
1713 });
1714 }
1715
1716 upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) {
1717 if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
1718
1719 var deferred = $q.defer();
1720 upload.dataUrl(file, true).then(function (url) {
1721 resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf)
1722 .then(function (dataUrl) {
1723 if (file.type === 'image/jpeg' && restoreExif) {
1724 try {
1725 dataUrl = upload.restoreExif(url, dataUrl);
1726 } catch (e) {
1727 setTimeout(function () {throw e;}, 1);
1728 }
1729 }
1730 try {
1731 var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);
1732 deferred.resolve(blob);
1733 } catch (e) {
1734 deferred.reject(e);
1735 }
1736 }, function (r) {
1737 if (r === 'resizeIf') {
1738 deferred.resolve(file);
1739 }
1740 deferred.reject(r);
1741 });
1742 }, function (e) {
1743 deferred.reject(e);
1744 });
1745 return deferred.promise;
1746 };
1747
1748 return upload;
1749 }]);
1750
1751 (function () {
1752 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
1753 function ($parse, $timeout, $location, Upload, $http, $q) {
1754 return {
1755 restrict: 'AEC',
1756 require: '?ngModel',
1757 link: function (scope, elem, attr, ngModel) {
1758 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
1759 }
1760 };
1761 }]);
1762
1763 ngFileUpload.directive('ngfNoFileDrop', function () {
1764 return function (scope, elem) {
1765 if (dropAvailable()) elem.css('display', 'none');
1766 };
1767 });
1768
1769 ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {
1770 return function (scope, elem, attr) {
1771 if (dropAvailable()) {
1772 var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));
1773 $timeout(function () {
1774 model(scope);
1775 if (model.assign) {
1776 model.assign(scope, true);
1777 }
1778 });
1779 }
1780 };
1781 }]);
1782
1783 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
1784 var available = dropAvailable();
1785
1786 var attrGetter = function (name, scope, params) {
1787 return upload.attrGetter(name, attr, scope, params);
1788 };
1789
1790 if (attrGetter('dropAvailable')) {
1791 $timeout(function () {
1792 if (scope[attrGetter('dropAvailable')]) {
1793 scope[attrGetter('dropAvailable')].value = available;
1794 } else {
1795 scope[attrGetter('dropAvailable')] = available;
1796 }
1797 });
1798 }
1799 if (!available) {
1800 if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {
1801 elem.css('display', 'none');
1802 }
1803 return;
1804 }
1805
1806 function isDisabled() {
1807 return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);
1808 }
1809
1810 if (attrGetter('ngfSelect') == null) {
1811 upload.registerModelChangeValidator(ngModel, attr, scope);
1812 }
1813
1814 var leaveTimeout = null;
1815 var stopPropagation = $parse(attrGetter('ngfStopPropagation'));
1816 var dragOverDelay = 1;
1817 var actualDragOverClass;
1818
1819 elem[0].addEventListener('dragover', function (evt) {
1820 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
1821 evt.preventDefault();
1822 if (stopPropagation(scope)) evt.stopPropagation();
1823 // handling dragover events from the Chrome download bar
1824 if (navigator.userAgent.indexOf('Chrome') > -1) {
1825 var b = evt.dataTransfer.effectAllowed;
1826 evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';
1827 }
1828 $timeout.cancel(leaveTimeout);
1829 if (!actualDragOverClass) {
1830 actualDragOverClass = 'C';
1831 calculateDragOverClass(scope, attr, evt, function (clazz) {
1832 actualDragOverClass = clazz;
1833 elem.addClass(actualDragOverClass);
1834 attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});
1835 });
1836 }
1837 }, false);
1838 elem[0].addEventListener('dragenter', function (evt) {
1839 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
1840 evt.preventDefault();
1841 if (stopPropagation(scope)) evt.stopPropagation();
1842 }, false);
1843 elem[0].addEventListener('dragleave', function (evt) {
1844 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
1845 evt.preventDefault();
1846 if (stopPropagation(scope)) evt.stopPropagation();
1847 leaveTimeout = $timeout(function () {
1848 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
1849 actualDragOverClass = null;
1850 attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});
1851 }, dragOverDelay || 100);
1852 }, false);
1853 elem[0].addEventListener('drop', function (evt) {
1854 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
1855 evt.preventDefault();
1856 if (stopPropagation(scope)) evt.stopPropagation();
1857 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
1858 actualDragOverClass = null;
1859 var items = evt.dataTransfer.items;
1860 var html;
1861 try {
1862 html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
1863 } catch (e) {/* Fix IE11 that throw error calling getData */
1864 }
1865
1866 extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
1867 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
1868 if (files.length) {
1869 updateModel(files, evt);
1870 } else {
1871 extractFilesFromHtml('dropUrl', html).then(function (files) {
1872 updateModel(files, evt);
1873 });
1874 }
1875 });
1876 }, false);
1877 elem[0].addEventListener('paste', function (evt) {
1878 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
1879 attrGetter('ngfEnableFirefoxPaste', scope)) {
1880 evt.preventDefault();
1881 }
1882 if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
1883 var files = [];
1884 var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
1885 if (clipboard && clipboard.items) {
1886 for (var k = 0; k < clipboard.items.length; k++) {
1887 if (clipboard.items[k].type.indexOf('image') !== -1) {
1888 files.push(clipboard.items[k].getAsFile());
1889 }
1890 }
1891 }
1892 if (files.length) {
1893 updateModel(files, evt);
1894 } else {
1895 extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
1896 updateModel(files, evt);
1897 });
1898 }
1899 }, false);
1900
1901 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
1902 attrGetter('ngfEnableFirefoxPaste', scope)) {
1903 elem.attr('contenteditable', true);
1904 elem.on('keypress', function (e) {
1905 if (!e.metaKey && !e.ctrlKey) {
1906 e.preventDefault();
1907 }
1908 });
1909 }
1910
1911 function updateModel(files, evt) {
1912 upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
1913 }
1914
1915 function extractFilesFromHtml(updateOn, html) {
1916 if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
1917 var urls = [];
1918 html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
1919 urls.push(src);
1920 });
1921 var promises = [], files = [];
1922 if (urls.length) {
1923 angular.forEach(urls, function (url) {
1924 promises.push(upload.urlToBlob(url).then(function (blob) {
1925 files.push(blob);
1926 }));
1927 });
1928 var defer = $q.defer();
1929 $q.all(promises).then(function () {
1930 defer.resolve(files);
1931 }, function (e) {
1932 defer.reject(e);
1933 });
1934 return defer.promise;
1935 }
1936 return upload.emptyPromise();
1937 }
1938
1939 function calculateDragOverClass(scope, attr, evt, callback) {
1940 var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';
1941 if (angular.isString(obj)) {
1942 dClass = obj;
1943 } else if (obj) {
1944 if (obj.delay) dragOverDelay = obj.delay;
1945 if (obj.accept || obj.reject) {
1946 var items = evt.dataTransfer.items;
1947 if (items == null || !items.length) {
1948 dClass = obj.accept;
1949 } else {
1950 var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
1951 var len = items.length;
1952 while (len--) {
1953 if (!upload.validatePattern(items[len], pattern)) {
1954 dClass = obj.reject;
1955 break;
1956 } else {
1957 dClass = obj.accept;
1958 }
1959 }
1960 }
1961 }
1962 }
1963 callback(dClass);
1964 }
1965
1966 function extractFiles(items, fileList, allowDir, multiple) {
1967 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
1968 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
1969 var includeDir = attrGetter('ngfIncludeDir', scope);
1970 var files = [], totalSize = 0;
1971
1972 function traverseFileTree(entry, path) {
1973 var defer = $q.defer();
1974 if (entry != null) {
1975 if (entry.isDirectory) {
1976 var promises = [upload.emptyPromise()];
1977 if (includeDir) {
1978 var file = {type: 'directory'};
1979 file.name = file.path = (path || '') + entry.name + entry.name;
1980 files.push(file);
1981 }
1982 var dirReader = entry.createReader();
1983 var entries = [];
1984 var readEntries = function () {
1985 dirReader.readEntries(function (results) {
1986 try {
1987 if (!results.length) {
1988 angular.forEach(entries.slice(0), function (e) {
1989 if (files.length <= maxFiles && totalSize <= maxTotalSize) {
1990 promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));
1991 }
1992 });
1993 $q.all(promises).then(function () {
1994 defer.resolve();
1995 }, function (e) {
1996 defer.reject(e);
1997 });
1998 } else {
1999 entries = entries.concat(Array.prototype.slice.call(results || [], 0));
2000 readEntries();
2001 }
2002 } catch (e) {
2003 defer.reject(e);
2004 }
2005 }, function (e) {
2006 defer.reject(e);
2007 });
2008 };
2009 readEntries();
2010 } else {
2011 entry.file(function (file) {
2012 try {
2013 file.path = (path ? path : '') + file.name;
2014 if (includeDir) {
2015 file = upload.rename(file, file.path);
2016 }
2017 files.push(file);
2018 totalSize += file.size;
2019 defer.resolve();
2020 } catch (e) {
2021 defer.reject(e);
2022 }
2023 }, function (e) {
2024 defer.reject(e);
2025 });
2026 }
2027 }
2028 return defer.promise;
2029 }
2030
2031 var promises = [upload.emptyPromise()];
2032
2033 if (items && items.length > 0 && $location.protocol() !== 'file') {
2034 for (var i = 0; i < items.length; i++) {
2035 if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
2036 var entry = items[i].webkitGetAsEntry();
2037 if (entry.isDirectory && !allowDir) {
2038 continue;
2039 }
2040 if (entry != null) {
2041 promises.push(traverseFileTree(entry));
2042 }
2043 } else {
2044 var f = items[i].getAsFile();
2045 if (f != null) {
2046 files.push(f);
2047 totalSize += f.size;
2048 }
2049 }
2050 if (files.length > maxFiles || totalSize > maxTotalSize ||
2051 (!multiple && files.length > 0)) break;
2052 }
2053 } else {
2054 if (fileList != null) {
2055 for (var j = 0; j < fileList.length; j++) {
2056 var file = fileList.item(j);
2057 if (file.type || file.size > 0) {
2058 files.push(file);
2059 totalSize += file.size;
2060 }
2061 if (files.length > maxFiles || totalSize > maxTotalSize ||
2062 (!multiple && files.length > 0)) break;
2063 }
2064 }
2065 }
2066
2067 var defer = $q.defer();
2068 $q.all(promises).then(function () {
2069 if (!multiple && !includeDir && files.length) {
2070 var i = 0;
2071 while (files[i] && files[i].type === 'directory') i++;
2072 defer.resolve([files[i]]);
2073 } else {
2074 defer.resolve(files);
2075 }
2076 }, function (e) {
2077 defer.reject(e);
2078 });
2079
2080 return defer.promise;
2081 }
2082 }
2083
2084 function dropAvailable() {
2085 var div = document.createElement('div');
2086 return ('draggable' in div) && ('ondrop' in div) && !/Edge\/12./i.test(navigator.userAgent);
2087 }
2088
2089 })();
2090
2091 // customized version of https://github.com/exif-js/exif-js
2092 ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {
2093 var upload = UploadResize;
2094
2095 upload.isExifSupported = function () {
2096 return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();
2097 };
2098
2099 function applyTransform(ctx, orientation, width, height) {
2100 switch (orientation) {
2101 case 2:
2102 return ctx.transform(-1, 0, 0, 1, width, 0);
2103 case 3:
2104 return ctx.transform(-1, 0, 0, -1, width, height);
2105 case 4:
2106 return ctx.transform(1, 0, 0, -1, 0, height);
2107 case 5:
2108 return ctx.transform(0, 1, 1, 0, 0, 0);
2109 case 6:
2110 return ctx.transform(0, 1, -1, 0, height, 0);
2111 case 7:
2112 return ctx.transform(0, -1, -1, 0, height, width);
2113 case 8:
2114 return ctx.transform(0, -1, 1, 0, 0, width);
2115 }
2116 }
2117
2118 upload.readOrientation = function (file) {
2119 var defer = $q.defer();
2120 var reader = new FileReader();
2121 var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;
2122 reader.readAsArrayBuffer(slicedFile);
2123 reader.onerror = function (e) {
2124 return defer.reject(e);
2125 };
2126 reader.onload = function (e) {
2127 var result = {orientation: 1};
2128 var view = new DataView(this.result);
2129 if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);
2130
2131 var length = view.byteLength,
2132 offset = 2;
2133 while (offset < length) {
2134 var marker = view.getUint16(offset, false);
2135 offset += 2;
2136 if (marker === 0xFFE1) {
2137 if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);
2138
2139 var little = view.getUint16(offset += 6, false) === 0x4949;
2140 offset += view.getUint32(offset + 4, little);
2141 var tags = view.getUint16(offset, little);
2142 offset += 2;
2143 for (var i = 0; i < tags; i++)
2144 if (view.getUint16(offset + (i * 12), little) === 0x0112) {
2145 var orientation = view.getUint16(offset + (i * 12) + 8, little);
2146 if (orientation >= 2 && orientation <= 8) {
2147 view.setUint16(offset + (i * 12) + 8, 1, little);
2148 result.fixedArrayBuffer = e.target.result;
2149 }
2150 result.orientation = orientation;
2151 return defer.resolve(result);
2152 }
2153 } else if ((marker & 0xFF00) !== 0xFF00) break;
2154 else offset += view.getUint16(offset, false);
2155 }
2156 return defer.resolve(result);
2157 };
2158 return defer.promise;
2159 };
2160
2161 function arrayBufferToBase64(buffer) {
2162 var binary = '';
2163 var bytes = new Uint8Array(buffer);
2164 var len = bytes.byteLength;
2165 for (var i = 0; i < len; i++) {
2166 binary += String.fromCharCode(bytes[i]);
2167 }
2168 return window.btoa(binary);
2169 }
2170
2171 upload.applyExifRotation = function (file) {
2172 if (file.type.indexOf('image/jpeg') !== 0) {
2173 return upload.emptyPromise(file);
2174 }
2175
2176 var deferred = $q.defer();
2177 upload.readOrientation(file).then(function (result) {
2178 if (result.orientation < 2 || result.orientation > 8) {
2179 return deferred.resolve(file);
2180 }
2181 upload.dataUrl(file, true).then(function (url) {
2182 var canvas = document.createElement('canvas');
2183 var img = document.createElement('img');
2184
2185 img.onload = function () {
2186 try {
2187 canvas.width = result.orientation > 4 ? img.height : img.width;
2188 canvas.height = result.orientation > 4 ? img.width : img.height;
2189 var ctx = canvas.getContext('2d');
2190 applyTransform(ctx, result.orientation, img.width, img.height);
2191 ctx.drawImage(img, 0, 0);
2192 var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);
2193 dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);
2194 var blob = upload.dataUrltoBlob(dataUrl, file.name);
2195 deferred.resolve(blob);
2196 } catch (e) {
2197 return deferred.reject(e);
2198 }
2199 };
2200 img.onerror = function () {
2201 deferred.reject();
2202 };
2203 img.src = url;
2204 }, function (e) {
2205 deferred.reject(e);
2206 });
2207 }, function (e) {
2208 deferred.reject(e);
2209 });
2210 return deferred.promise;
2211 };
2212
2213 upload.restoreExif = function (orig, resized) {
2214 var ExifRestorer = {};
2215
2216 ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +
2217 'QRSTUVWXYZabcdef' +
2218 'ghijklmnopqrstuv' +
2219 'wxyz0123456789+/' +
2220 '=';
2221
2222 ExifRestorer.encode64 = function (input) {
2223 var output = '',
2224 chr1, chr2, chr3 = '',
2225 enc1, enc2, enc3, enc4 = '',
2226 i = 0;
2227
2228 do {
2229 chr1 = input[i++];
2230 chr2 = input[i++];
2231 chr3 = input[i++];
2232
2233 enc1 = chr1 >> 2;
2234 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2235 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
2236 enc4 = chr3 & 63;
2237
2238 if (isNaN(chr2)) {
2239 enc3 = enc4 = 64;
2240 } else if (isNaN(chr3)) {
2241 enc4 = 64;
2242 }
2243
2244 output = output +
2245 this.KEY_STR.charAt(enc1) +
2246 this.KEY_STR.charAt(enc2) +
2247 this.KEY_STR.charAt(enc3) +
2248 this.KEY_STR.charAt(enc4);
2249 chr1 = chr2 = chr3 = '';
2250 enc1 = enc2 = enc3 = enc4 = '';
2251 } while (i < input.length);
2252
2253 return output;
2254 };
2255
2256 ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {
2257 if (origFileBase64.match('data:image/jpeg;base64,')) {
2258 origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');
2259 }
2260
2261 var rawImage = this.decode64(origFileBase64);
2262 var segments = this.slice2Segments(rawImage);
2263
2264 var image = this.exifManipulation(resizedFileBase64, segments);
2265
2266 return 'data:image/jpeg;base64,' + this.encode64(image);
2267 };
2268
2269
2270 ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {
2271 var exifArray = this.getExifArray(segments),
2272 newImageArray = this.insertExif(resizedFileBase64, exifArray);
2273 return new Uint8Array(newImageArray);
2274 };
2275
2276
2277 ExifRestorer.getExifArray = function (segments) {
2278 var seg;
2279 for (var x = 0; x < segments.length; x++) {
2280 seg = segments[x];
2281 if (seg[0] === 255 & seg[1] === 225) //(ff e1)
2282 {
2283 return seg;
2284 }
2285 }
2286 return [];
2287 };
2288
2289
2290 ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {
2291 var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),
2292 buf = this.decode64(imageData),
2293 separatePoint = buf.indexOf(255, 3),
2294 mae = buf.slice(0, separatePoint),
2295 ato = buf.slice(separatePoint),
2296 array = mae;
2297
2298 array = array.concat(exifArray);
2299 array = array.concat(ato);
2300 return array;
2301 };
2302
2303
2304 ExifRestorer.slice2Segments = function (rawImageArray) {
2305 var head = 0,
2306 segments = [];
2307
2308 while (1) {
2309 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {
2310 break;
2311 }
2312 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {
2313 head += 2;
2314 }
2315 else {
2316 var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],
2317 endPoint = head + length + 2,
2318 seg = rawImageArray.slice(head, endPoint);
2319 segments.push(seg);
2320 head = endPoint;
2321 }
2322 if (head > rawImageArray.length) {
2323 break;
2324 }
2325 }
2326
2327 return segments;
2328 };
2329
2330
2331 ExifRestorer.decode64 = function (input) {
2332 var chr1, chr2, chr3 = '',
2333 enc1, enc2, enc3, enc4 = '',
2334 i = 0,
2335 buf = [];
2336
2337 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
2338 var base64test = /[^A-Za-z0-9\+\/\=]/g;
2339 if (base64test.exec(input)) {
2340 console.log('There were invalid base64 characters in the input text.\n' +
2341 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' +
2342 'Expect errors in decoding.');
2343 }
2344 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
2345
2346 do {
2347 enc1 = this.KEY_STR.indexOf(input.charAt(i++));
2348 enc2 = this.KEY_STR.indexOf(input.charAt(i++));
2349 enc3 = this.KEY_STR.indexOf(input.charAt(i++));
2350 enc4 = this.KEY_STR.indexOf(input.charAt(i++));
2351
2352 chr1 = (enc1 << 2) | (enc2 >> 4);
2353 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
2354 chr3 = ((enc3 & 3) << 6) | enc4;
2355
2356 buf.push(chr1);
2357
2358 if (enc3 !== 64) {
2359 buf.push(chr2);
2360 }
2361 if (enc4 !== 64) {
2362 buf.push(chr3);
2363 }
2364
2365 chr1 = chr2 = chr3 = '';
2366 enc1 = enc2 = enc3 = enc4 = '';
2367
2368 } while (i < input.length);
2369
2370 return buf;
2371 };
2372
2373 return ExifRestorer.restore(orig, resized); //<= EXIF
2374 };
2375
2376 return upload;
2377 }]);
2378
+0
-18
demo/src/main/webapp/js/ng-img-crop.css less more
0 /* line 1, ../../source/scss/ng-img-crop.scss */
1 img-crop {
2 width: 100%;
3 height: 100%;
4 display: block;
5 position: relative;
6 overflow: hidden;
7 }
8 /* line 7, ../../source/scss/ng-img-crop.scss */
9 img-crop canvas {
10 display: block;
11 position: absolute;
12 top: 50%;
13 left: 50%;
14 outline: none;
15 -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
16 /* mobile webkit */
17 }
+0
-1879
demo/src/main/webapp/js/ng-img-crop.js less more
0 /*!
1 * ngImgCrop v0.3.2
2 * https://github.com/alexk111/ngImgCrop
3 *
4 * Copyright (c) 2014 Alex Kaul
5 * License: MIT
6 *
7 * Generated at Wednesday, December 3rd, 2014, 3:54:12 PM
8 */
9 (function() {
10 'use strict';
11
12 var crop = angular.module('ngImgCrop', []);
13
14 crop.factory('cropAreaCircle', ['cropArea', function(CropArea) {
15 var CropAreaCircle = function() {
16 CropArea.apply(this, arguments);
17
18 this._boxResizeBaseSize = 20;
19 this._boxResizeNormalRatio = 0.9;
20 this._boxResizeHoverRatio = 1.2;
21 this._iconMoveNormalRatio = 0.9;
22 this._iconMoveHoverRatio = 1.2;
23
24 this._boxResizeNormalSize = this._boxResizeBaseSize*this._boxResizeNormalRatio;
25 this._boxResizeHoverSize = this._boxResizeBaseSize*this._boxResizeHoverRatio;
26
27 this._posDragStartX=0;
28 this._posDragStartY=0;
29 this._posResizeStartX=0;
30 this._posResizeStartY=0;
31 this._posResizeStartSize=0;
32
33 this._boxResizeIsHover = false;
34 this._areaIsHover = false;
35 this._boxResizeIsDragging = false;
36 this._areaIsDragging = false;
37 };
38
39 CropAreaCircle.prototype = new CropArea();
40
41 CropAreaCircle.prototype._calcCirclePerimeterCoords=function(angleDegrees) {
42 var hSize=this._size/2;
43 var angleRadians=angleDegrees * (Math.PI / 180),
44 circlePerimeterX=this._x + hSize * Math.cos(angleRadians),
45 circlePerimeterY=this._y + hSize * Math.sin(angleRadians);
46 return [circlePerimeterX, circlePerimeterY];
47 };
48
49 CropAreaCircle.prototype._calcResizeIconCenterCoords=function() {
50 return this._calcCirclePerimeterCoords(-45);
51 };
52
53 CropAreaCircle.prototype._isCoordWithinArea=function(coord) {
54 return Math.sqrt((coord[0]-this._x)*(coord[0]-this._x) + (coord[1]-this._y)*(coord[1]-this._y)) < this._size/2;
55 };
56 CropAreaCircle.prototype._isCoordWithinBoxResize=function(coord) {
57 var resizeIconCenterCoords=this._calcResizeIconCenterCoords();
58 var hSize=this._boxResizeHoverSize/2;
59 return(coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize &&
60 coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize);
61 };
62
63 CropAreaCircle.prototype._drawArea=function(ctx,centerCoords,size){
64 ctx.arc(centerCoords[0],centerCoords[1],size/2,0,2*Math.PI);
65 };
66
67 CropAreaCircle.prototype.draw=function() {
68 CropArea.prototype.draw.apply(this, arguments);
69
70 // draw move icon
71 this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
72
73 // draw resize cubes
74 this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover?this._boxResizeHoverRatio:this._boxResizeNormalRatio);
75 };
76
77 CropAreaCircle.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
78 var cursor='default';
79 var res=false;
80
81 this._boxResizeIsHover = false;
82 this._areaIsHover = false;
83
84 if (this._areaIsDragging) {
85 this._x = mouseCurX - this._posDragStartX;
86 this._y = mouseCurY - this._posDragStartY;
87 this._areaIsHover = true;
88 cursor='move';
89 res=true;
90 this._events.trigger('area-move');
91 } else if (this._boxResizeIsDragging) {
92 cursor = 'nesw-resize';
93 var iFR, iFX, iFY;
94 iFX = mouseCurX - this._posResizeStartX;
95 iFY = this._posResizeStartY - mouseCurY;
96 if(iFX>iFY) {
97 iFR = this._posResizeStartSize + iFY*2;
98 } else {
99 iFR = this._posResizeStartSize + iFX*2;
100 }
101
102 this._size = Math.max(this._minSize, iFR);
103 this._boxResizeIsHover = true;
104 res=true;
105 this._events.trigger('area-resize');
106 } else if (this._isCoordWithinBoxResize([mouseCurX,mouseCurY])) {
107 cursor = 'nesw-resize';
108 this._areaIsHover = false;
109 this._boxResizeIsHover = true;
110 res=true;
111 } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
112 cursor = 'move';
113 this._areaIsHover = true;
114 res=true;
115 }
116
117 this._dontDragOutside();
118 angular.element(this._ctx.canvas).css({'cursor': cursor});
119
120 return res;
121 };
122
123 CropAreaCircle.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
124 if (this._isCoordWithinBoxResize([mouseDownX,mouseDownY])) {
125 this._areaIsDragging = false;
126 this._areaIsHover = false;
127 this._boxResizeIsDragging = true;
128 this._boxResizeIsHover = true;
129 this._posResizeStartX=mouseDownX;
130 this._posResizeStartY=mouseDownY;
131 this._posResizeStartSize = this._size;
132 this._events.trigger('area-resize-start');
133 } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
134 this._areaIsDragging = true;
135 this._areaIsHover = true;
136 this._boxResizeIsDragging = false;
137 this._boxResizeIsHover = false;
138 this._posDragStartX = mouseDownX - this._x;
139 this._posDragStartY = mouseDownY - this._y;
140 this._events.trigger('area-move-start');
141 }
142 };
143
144 CropAreaCircle.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
145 if(this._areaIsDragging) {
146 this._areaIsDragging = false;
147 this._events.trigger('area-move-end');
148 }
149 if(this._boxResizeIsDragging) {
150 this._boxResizeIsDragging = false;
151 this._events.trigger('area-resize-end');
152 }
153 this._areaIsHover = false;
154 this._boxResizeIsHover = false;
155
156 this._posDragStartX = 0;
157 this._posDragStartY = 0;
158 };
159
160 return CropAreaCircle;
161 }]);
162
163
164
165 crop.factory('cropAreaSquare', ['cropArea', function(CropArea) {
166 var CropAreaSquare = function() {
167 CropArea.apply(this, arguments);
168
169 this._resizeCtrlBaseRadius = 10;
170 this._resizeCtrlNormalRatio = 0.75;
171 this._resizeCtrlHoverRatio = 1;
172 this._iconMoveNormalRatio = 0.9;
173 this._iconMoveHoverRatio = 1.2;
174
175 this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius*this._resizeCtrlNormalRatio;
176 this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius*this._resizeCtrlHoverRatio;
177
178 this._posDragStartX=0;
179 this._posDragStartY=0;
180 this._posResizeStartX=0;
181 this._posResizeStartY=0;
182 this._posResizeStartSize=0;
183
184 this._resizeCtrlIsHover = -1;
185 this._areaIsHover = false;
186 this._resizeCtrlIsDragging = -1;
187 this._areaIsDragging = false;
188 };
189
190 CropAreaSquare.prototype = new CropArea();
191
192 CropAreaSquare.prototype._calcSquareCorners=function() {
193 var hSize=this._size/2;
194 return [
195 [this._x-hSize, this._y-hSize],
196 [this._x+hSize, this._y-hSize],
197 [this._x-hSize, this._y+hSize],
198 [this._x+hSize, this._y+hSize]
199 ];
200 };
201
202 CropAreaSquare.prototype._calcSquareDimensions=function() {
203 var hSize=this._size/2;
204 return {
205 left: this._x-hSize,
206 top: this._y-hSize,
207 right: this._x+hSize,
208 bottom: this._y+hSize
209 };
210 };
211
212 CropAreaSquare.prototype._isCoordWithinArea=function(coord) {
213 var squareDimensions=this._calcSquareDimensions();
214 return (coord[0]>=squareDimensions.left&&coord[0]<=squareDimensions.right&&coord[1]>=squareDimensions.top&&coord[1]<=squareDimensions.bottom);
215 };
216
217 CropAreaSquare.prototype._isCoordWithinResizeCtrl=function(coord) {
218 var resizeIconsCenterCoords=this._calcSquareCorners();
219 var res=-1;
220 for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
221 var resizeIconCenterCoords=resizeIconsCenterCoords[i];
222 if(coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&
223 coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {
224 res=i;
225 break;
226 }
227 }
228 return res;
229 };
230
231 CropAreaSquare.prototype._drawArea=function(ctx,centerCoords,size){
232 var hSize=size/2;
233 ctx.rect(centerCoords[0]-hSize,centerCoords[1]-hSize,size,size);
234 };
235
236 CropAreaSquare.prototype.draw=function() {
237 CropArea.prototype.draw.apply(this, arguments);
238
239 // draw move icon
240 this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
241
242 // draw resize cubes
243 var resizeIconsCenterCoords=this._calcSquareCorners();
244 for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
245 var resizeIconCenterCoords=resizeIconsCenterCoords[i];
246 this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover===i?this._resizeCtrlHoverRatio:this._resizeCtrlNormalRatio);
247 }
248 };
249
250 CropAreaSquare.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
251 var cursor='default';
252 var res=false;
253
254 this._resizeCtrlIsHover = -1;
255 this._areaIsHover = false;
256
257 if (this._areaIsDragging) {
258 this._x = mouseCurX - this._posDragStartX;
259 this._y = mouseCurY - this._posDragStartY;
260 this._areaIsHover = true;
261 cursor='move';
262 res=true;
263 this._events.trigger('area-move');
264 } else if (this._resizeCtrlIsDragging>-1) {
265 var xMulti, yMulti;
266 switch(this._resizeCtrlIsDragging) {
267 case 0: // Top Left
268 xMulti=-1;
269 yMulti=-1;
270 cursor = 'nwse-resize';
271 break;
272 case 1: // Top Right
273 xMulti=1;
274 yMulti=-1;
275 cursor = 'nesw-resize';
276 break;
277 case 2: // Bottom Left
278 xMulti=-1;
279 yMulti=1;
280 cursor = 'nesw-resize';
281 break;
282 case 3: // Bottom Right
283 xMulti=1;
284 yMulti=1;
285 cursor = 'nwse-resize';
286 break;
287 }
288 var iFX = (mouseCurX - this._posResizeStartX)*xMulti;
289 var iFY = (mouseCurY - this._posResizeStartY)*yMulti;
290 var iFR;
291 if(iFX>iFY) {
292 iFR = this._posResizeStartSize + iFY;
293 } else {
294 iFR = this._posResizeStartSize + iFX;
295 }
296 var wasSize=this._size;
297 this._size = Math.max(this._minSize, iFR);
298 var posModifier=(this._size-wasSize)/2;
299 this._x+=posModifier*xMulti;
300 this._y+=posModifier*yMulti;
301 this._resizeCtrlIsHover = this._resizeCtrlIsDragging;
302 res=true;
303 this._events.trigger('area-resize');
304 } else {
305 var hoveredResizeBox=this._isCoordWithinResizeCtrl([mouseCurX,mouseCurY]);
306 if (hoveredResizeBox>-1) {
307 switch(hoveredResizeBox) {
308 case 0:
309 cursor = 'nwse-resize';
310 break;
311 case 1:
312 cursor = 'nesw-resize';
313 break;
314 case 2:
315 cursor = 'nesw-resize';
316 break;
317 case 3:
318 cursor = 'nwse-resize';
319 break;
320 }
321 this._areaIsHover = false;
322 this._resizeCtrlIsHover = hoveredResizeBox;
323 res=true;
324 } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
325 cursor = 'move';
326 this._areaIsHover = true;
327 res=true;
328 }
329 }
330
331 this._dontDragOutside();
332 angular.element(this._ctx.canvas).css({'cursor': cursor});
333
334 return res;
335 };
336
337 CropAreaSquare.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
338 var isWithinResizeCtrl=this._isCoordWithinResizeCtrl([mouseDownX,mouseDownY]);
339 if (isWithinResizeCtrl>-1) {
340 this._areaIsDragging = false;
341 this._areaIsHover = false;
342 this._resizeCtrlIsDragging = isWithinResizeCtrl;
343 this._resizeCtrlIsHover = isWithinResizeCtrl;
344 this._posResizeStartX=mouseDownX;
345 this._posResizeStartY=mouseDownY;
346 this._posResizeStartSize = this._size;
347 this._events.trigger('area-resize-start');
348 } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
349 this._areaIsDragging = true;
350 this._areaIsHover = true;
351 this._resizeCtrlIsDragging = -1;
352 this._resizeCtrlIsHover = -1;
353 this._posDragStartX = mouseDownX - this._x;
354 this._posDragStartY = mouseDownY - this._y;
355 this._events.trigger('area-move-start');
356 }
357 };
358
359 CropAreaSquare.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
360 if(this._areaIsDragging) {
361 this._areaIsDragging = false;
362 this._events.trigger('area-move-end');
363 }
364 if(this._resizeCtrlIsDragging>-1) {
365 this._resizeCtrlIsDragging = -1;
366 this._events.trigger('area-resize-end');
367 }
368 this._areaIsHover = false;
369 this._resizeCtrlIsHover = -1;
370
371 this._posDragStartX = 0;
372 this._posDragStartY = 0;
373 };
374
375 return CropAreaSquare;
376 }]);
377
378 crop.factory('cropArea', ['cropCanvas', function(CropCanvas) {
379 var CropArea = function(ctx, events) {
380 this._ctx=ctx;
381 this._events=events;
382
383 this._minSize=80;
384
385 this._cropCanvas=new CropCanvas(ctx);
386
387 this._image=new Image();
388 this._x = 0;
389 this._y = 0;
390 this._size = 200;
391 };
392
393 /* GETTERS/SETTERS */
394
395 CropArea.prototype.getImage = function () {
396 return this._image;
397 };
398 CropArea.prototype.setImage = function (image) {
399 this._image = image;
400 };
401
402 CropArea.prototype.getX = function () {
403 return this._x;
404 };
405 CropArea.prototype.setX = function (x) {
406 this._x = x;
407 this._dontDragOutside();
408 };
409
410 CropArea.prototype.getY = function () {
411 return this._y;
412 };
413 CropArea.prototype.setY = function (y) {
414 this._y = y;
415 this._dontDragOutside();
416 };
417
418 CropArea.prototype.getSize = function () {
419 return this._size;
420 };
421 CropArea.prototype.setSize = function (size) {
422 this._size = Math.max(this._minSize, size);
423 this._dontDragOutside();
424 };
425
426 CropArea.prototype.getMinSize = function () {
427 return this._minSize;
428 };
429 CropArea.prototype.setMinSize = function (size) {
430 this._minSize = size;
431 this._size = Math.max(this._minSize, this._size);
432 this._dontDragOutside();
433 };
434
435 /* FUNCTIONS */
436 CropArea.prototype._dontDragOutside=function() {
437 var h=this._ctx.canvas.height,
438 w=this._ctx.canvas.width;
439 if(this._size>w) { this._size=w; }
440 if(this._size>h) { this._size=h; }
441 if(this._x<this._size/2) { this._x=this._size/2; }
442 if(this._x>w-this._size/2) { this._x=w-this._size/2; }
443 if(this._y<this._size/2) { this._y=this._size/2; }
444 if(this._y>h-this._size/2) { this._y=h-this._size/2; }
445 };
446
447 CropArea.prototype._drawArea=function() {};
448
449 CropArea.prototype.draw=function() {
450 // draw crop area
451 this._cropCanvas.drawCropArea(this._image,[this._x,this._y],this._size,this._drawArea);
452 };
453
454 CropArea.prototype.processMouseMove=function() {};
455
456 CropArea.prototype.processMouseDown=function() {};
457
458 CropArea.prototype.processMouseUp=function() {};
459
460 return CropArea;
461 }]);
462
463 crop.factory('cropCanvas', [function() {
464 // Shape = Array of [x,y]; [0, 0] - center
465 var shapeArrowNW=[[-0.5,-2],[-3,-4.5],[-0.5,-7],[-7,-7],[-7,-0.5],[-4.5,-3],[-2,-0.5]];
466 var shapeArrowNE=[[0.5,-2],[3,-4.5],[0.5,-7],[7,-7],[7,-0.5],[4.5,-3],[2,-0.5]];
467 var shapeArrowSW=[[-0.5,2],[-3,4.5],[-0.5,7],[-7,7],[-7,0.5],[-4.5,3],[-2,0.5]];
468 var shapeArrowSE=[[0.5,2],[3,4.5],[0.5,7],[7,7],[7,0.5],[4.5,3],[2,0.5]];
469 var shapeArrowN=[[-1.5,-2.5],[-1.5,-6],[-5,-6],[0,-11],[5,-6],[1.5,-6],[1.5,-2.5]];
470 var shapeArrowW=[[-2.5,-1.5],[-6,-1.5],[-6,-5],[-11,0],[-6,5],[-6,1.5],[-2.5,1.5]];
471 var shapeArrowS=[[-1.5,2.5],[-1.5,6],[-5,6],[0,11],[5,6],[1.5,6],[1.5,2.5]];
472 var shapeArrowE=[[2.5,-1.5],[6,-1.5],[6,-5],[11,0],[6,5],[6,1.5],[2.5,1.5]];
473
474 // Colors
475 var colors={
476 areaOutline: '#fff',
477 resizeBoxStroke: '#fff',
478 resizeBoxFill: '#444',
479 resizeBoxArrowFill: '#fff',
480 resizeCircleStroke: '#fff',
481 resizeCircleFill: '#444',
482 moveIconFill: '#fff'
483 };
484
485 return function(ctx){
486
487 /* Base functions */
488
489 // Calculate Point
490 var calcPoint=function(point,offset,scale) {
491 return [scale*point[0]+offset[0], scale*point[1]+offset[1]];
492 };
493
494 // Draw Filled Polygon
495 var drawFilledPolygon=function(shape,fillStyle,centerCoords,scale) {
496 ctx.save();
497 ctx.fillStyle = fillStyle;
498 ctx.beginPath();
499 var pc, pc0=calcPoint(shape[0],centerCoords,scale);
500 ctx.moveTo(pc0[0],pc0[1]);
501
502 for(var p in shape) {
503 if (p > 0) {
504 pc=calcPoint(shape[p],centerCoords,scale);
505 ctx.lineTo(pc[0],pc[1]);
506 }
507 }
508
509 ctx.lineTo(pc0[0],pc0[1]);
510 ctx.fill();
511 ctx.closePath();
512 ctx.restore();
513 };
514
515 /* Icons */
516
517 this.drawIconMove=function(centerCoords, scale) {
518 drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale);
519 drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale);
520 drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale);
521 drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale);
522 };
523
524 this.drawIconResizeCircle=function(centerCoords, circleRadius, scale) {
525 var scaledCircleRadius=circleRadius*scale;
526 ctx.save();
527 ctx.strokeStyle = colors.resizeCircleStroke;
528 ctx.lineWidth = 2;
529 ctx.fillStyle = colors.resizeCircleFill;
530 ctx.beginPath();
531 ctx.arc(centerCoords[0],centerCoords[1],scaledCircleRadius,0,2*Math.PI);
532 ctx.fill();
533 ctx.stroke();
534 ctx.closePath();
535 ctx.restore();
536 };
537
538 this.drawIconResizeBoxBase=function(centerCoords, boxSize, scale) {
539 var scaledBoxSize=boxSize*scale;
540 ctx.save();
541 ctx.strokeStyle = colors.resizeBoxStroke;
542 ctx.lineWidth = 2;
543 ctx.fillStyle = colors.resizeBoxFill;
544 ctx.fillRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
545 ctx.strokeRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
546 ctx.restore();
547 };
548 this.drawIconResizeBoxNESW=function(centerCoords, boxSize, scale) {
549 this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
550 drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale);
551 drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale);
552 };
553 this.drawIconResizeBoxNWSE=function(centerCoords, boxSize, scale) {
554 this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
555 drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale);
556 drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale);
557 };
558
559 /* Crop Area */
560
561 this.drawCropArea=function(image, centerCoords, size, fnDrawClipPath) {
562 var xRatio=image.width/ctx.canvas.width,
563 yRatio=image.height/ctx.canvas.height,
564 xLeft=centerCoords[0]-size/2,
565 yTop=centerCoords[1]-size/2;
566
567 ctx.save();
568 ctx.strokeStyle = colors.areaOutline;
569 ctx.lineWidth = 2;
570 ctx.beginPath();
571 fnDrawClipPath(ctx, centerCoords, size);
572 ctx.stroke();
573 ctx.clip();
574
575 // draw part of original image
576 if (size > 0) {
577 ctx.drawImage(image, xLeft*xRatio, yTop*yRatio, size*xRatio, size*yRatio, xLeft, yTop, size, size);
578 }
579
580 ctx.beginPath();
581 fnDrawClipPath(ctx, centerCoords, size);
582 ctx.stroke();
583 ctx.clip();
584
585 ctx.restore();
586 };
587
588 };
589 }]);
590
591 /**
592 * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js)
593 */
594
595 crop.service('cropEXIF', [function() {
596 var debug = false;
597
598 var ExifTags = this.Tags = {
599
600 // version tags
601 0x9000 : "ExifVersion", // EXIF version
602 0xA000 : "FlashpixVersion", // Flashpix format version
603
604 // colorspace tags
605 0xA001 : "ColorSpace", // Color space information tag
606
607 // image configuration
608 0xA002 : "PixelXDimension", // Valid width of meaningful image
609 0xA003 : "PixelYDimension", // Valid height of meaningful image
610 0x9101 : "ComponentsConfiguration", // Information about channels
611 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel
612
613 // user information
614 0x927C : "MakerNote", // Any desired information written by the manufacturer
615 0x9286 : "UserComment", // Comments by user
616
617 // related file
618 0xA004 : "RelatedSoundFile", // Name of related sound file
619
620 // date and time
621 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated
622 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally
623 0x9290 : "SubsecTime", // Fractions of seconds for DateTime
624 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
625 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
626
627 // picture-taking conditions
628 0x829A : "ExposureTime", // Exposure time (in seconds)
629 0x829D : "FNumber", // F number
630 0x8822 : "ExposureProgram", // Exposure program
631 0x8824 : "SpectralSensitivity", // Spectral sensitivity
632 0x8827 : "ISOSpeedRatings", // ISO speed rating
633 0x8828 : "OECF", // Optoelectric conversion factor
634 0x9201 : "ShutterSpeedValue", // Shutter speed
635 0x9202 : "ApertureValue", // Lens aperture
636 0x9203 : "BrightnessValue", // Value of brightness
637 0x9204 : "ExposureBias", // Exposure bias
638 0x9205 : "MaxApertureValue", // Smallest F number of lens
639 0x9206 : "SubjectDistance", // Distance to subject in meters
640 0x9207 : "MeteringMode", // Metering mode
641 0x9208 : "LightSource", // Kind of light source
642 0x9209 : "Flash", // Flash status
643 0x9214 : "SubjectArea", // Location and area of main subject
644 0x920A : "FocalLength", // Focal length of the lens in mm
645 0xA20B : "FlashEnergy", // Strobe energy in BCPS
646 0xA20C : "SpatialFrequencyResponse", //
647 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
648 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
649 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
650 0xA214 : "SubjectLocation", // Location of subject in image
651 0xA215 : "ExposureIndex", // Exposure index selected on camera
652 0xA217 : "SensingMethod", // Image sensor type
653 0xA300 : "FileSource", // Image source (3 == DSC)
654 0xA301 : "SceneType", // Scene type (1 == directly photographed)
655 0xA302 : "CFAPattern", // Color filter array geometric pattern
656 0xA401 : "CustomRendered", // Special processing
657 0xA402 : "ExposureMode", // Exposure mode
658 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual
659 0xA404 : "DigitalZoomRation", // Digital zoom ratio
660 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
661 0xA406 : "SceneCaptureType", // Type of scene
662 0xA407 : "GainControl", // Degree of overall image gain adjustment
663 0xA408 : "Contrast", // Direction of contrast processing applied by camera
664 0xA409 : "Saturation", // Direction of saturation processing applied by camera
665 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera
666 0xA40B : "DeviceSettingDescription", //
667 0xA40C : "SubjectDistanceRange", // Distance to subject
668
669 // other tags
670 0xA005 : "InteroperabilityIFDPointer",
671 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image
672 };
673
674 var TiffTags = this.TiffTags = {
675 0x0100 : "ImageWidth",
676 0x0101 : "ImageHeight",
677 0x8769 : "ExifIFDPointer",
678 0x8825 : "GPSInfoIFDPointer",
679 0xA005 : "InteroperabilityIFDPointer",
680 0x0102 : "BitsPerSample",
681 0x0103 : "Compression",
682 0x0106 : "PhotometricInterpretation",
683 0x0112 : "Orientation",
684 0x0115 : "SamplesPerPixel",
685 0x011C : "PlanarConfiguration",
686 0x0212 : "YCbCrSubSampling",
687 0x0213 : "YCbCrPositioning",
688 0x011A : "XResolution",
689 0x011B : "YResolution",
690 0x0128 : "ResolutionUnit",
691 0x0111 : "StripOffsets",
692 0x0116 : "RowsPerStrip",
693 0x0117 : "StripByteCounts",
694 0x0201 : "JPEGInterchangeFormat",
695 0x0202 : "JPEGInterchangeFormatLength",
696 0x012D : "TransferFunction",
697 0x013E : "WhitePoint",
698 0x013F : "PrimaryChromaticities",
699 0x0211 : "YCbCrCoefficients",
700 0x0214 : "ReferenceBlackWhite",
701 0x0132 : "DateTime",
702 0x010E : "ImageDescription",
703 0x010F : "Make",
704 0x0110 : "Model",
705 0x0131 : "Software",
706 0x013B : "Artist",
707 0x8298 : "Copyright"
708 };
709
710 var GPSTags = this.GPSTags = {
711 0x0000 : "GPSVersionID",
712 0x0001 : "GPSLatitudeRef",
713 0x0002 : "GPSLatitude",
714 0x0003 : "GPSLongitudeRef",
715 0x0004 : "GPSLongitude",
716 0x0005 : "GPSAltitudeRef",
717 0x0006 : "GPSAltitude",
718 0x0007 : "GPSTimeStamp",
719 0x0008 : "GPSSatellites",
720 0x0009 : "GPSStatus",
721 0x000A : "GPSMeasureMode",
722 0x000B : "GPSDOP",
723 0x000C : "GPSSpeedRef",
724 0x000D : "GPSSpeed",
725 0x000E : "GPSTrackRef",
726 0x000F : "GPSTrack",
727 0x0010 : "GPSImgDirectionRef",
728 0x0011 : "GPSImgDirection",
729 0x0012 : "GPSMapDatum",
730 0x0013 : "GPSDestLatitudeRef",
731 0x0014 : "GPSDestLatitude",
732 0x0015 : "GPSDestLongitudeRef",
733 0x0016 : "GPSDestLongitude",
734 0x0017 : "GPSDestBearingRef",
735 0x0018 : "GPSDestBearing",
736 0x0019 : "GPSDestDistanceRef",
737 0x001A : "GPSDestDistance",
738 0x001B : "GPSProcessingMethod",
739 0x001C : "GPSAreaInformation",
740 0x001D : "GPSDateStamp",
741 0x001E : "GPSDifferential"
742 };
743
744 var StringValues = this.StringValues = {
745 ExposureProgram : {
746 0 : "Not defined",
747 1 : "Manual",
748 2 : "Normal program",
749 3 : "Aperture priority",
750 4 : "Shutter priority",
751 5 : "Creative program",
752 6 : "Action program",
753 7 : "Portrait mode",
754 8 : "Landscape mode"
755 },
756 MeteringMode : {
757 0 : "Unknown",
758 1 : "Average",
759 2 : "CenterWeightedAverage",
760 3 : "Spot",
761 4 : "MultiSpot",
762 5 : "Pattern",
763 6 : "Partial",
764 255 : "Other"
765 },
766 LightSource : {
767 0 : "Unknown",
768 1 : "Daylight",
769 2 : "Fluorescent",
770 3 : "Tungsten (incandescent light)",
771 4 : "Flash",
772 9 : "Fine weather",
773 10 : "Cloudy weather",
774 11 : "Shade",
775 12 : "Daylight fluorescent (D 5700 - 7100K)",
776 13 : "Day white fluorescent (N 4600 - 5400K)",
777 14 : "Cool white fluorescent (W 3900 - 4500K)",
778 15 : "White fluorescent (WW 3200 - 3700K)",
779 17 : "Standard light A",
780 18 : "Standard light B",
781 19 : "Standard light C",
782 20 : "D55",
783 21 : "D65",
784 22 : "D75",
785 23 : "D50",
786 24 : "ISO studio tungsten",
787 255 : "Other"
788 },
789 Flash : {
790 0x0000 : "Flash did not fire",
791 0x0001 : "Flash fired",
792 0x0005 : "Strobe return light not detected",
793 0x0007 : "Strobe return light detected",
794 0x0009 : "Flash fired, compulsory flash mode",
795 0x000D : "Flash fired, compulsory flash mode, return light not detected",
796 0x000F : "Flash fired, compulsory flash mode, return light detected",
797 0x0010 : "Flash did not fire, compulsory flash mode",
798 0x0018 : "Flash did not fire, auto mode",
799 0x0019 : "Flash fired, auto mode",
800 0x001D : "Flash fired, auto mode, return light not detected",
801 0x001F : "Flash fired, auto mode, return light detected",
802 0x0020 : "No flash function",
803 0x0041 : "Flash fired, red-eye reduction mode",
804 0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
805 0x0047 : "Flash fired, red-eye reduction mode, return light detected",
806 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
807 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
808 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
809 0x0059 : "Flash fired, auto mode, red-eye reduction mode",
810 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
811 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
812 },
813 SensingMethod : {
814 1 : "Not defined",
815 2 : "One-chip color area sensor",
816 3 : "Two-chip color area sensor",
817 4 : "Three-chip color area sensor",
818 5 : "Color sequential area sensor",
819 7 : "Trilinear sensor",
820 8 : "Color sequential linear sensor"
821 },
822 SceneCaptureType : {
823 0 : "Standard",
824 1 : "Landscape",
825 2 : "Portrait",
826 3 : "Night scene"
827 },
828 SceneType : {
829 1 : "Directly photographed"
830 },
831 CustomRendered : {
832 0 : "Normal process",
833 1 : "Custom process"
834 },
835 WhiteBalance : {
836 0 : "Auto white balance",
837 1 : "Manual white balance"
838 },
839 GainControl : {
840 0 : "None",
841 1 : "Low gain up",
842 2 : "High gain up",
843 3 : "Low gain down",
844 4 : "High gain down"
845 },
846 Contrast : {
847 0 : "Normal",
848 1 : "Soft",
849 2 : "Hard"
850 },
851 Saturation : {
852 0 : "Normal",
853 1 : "Low saturation",
854 2 : "High saturation"
855 },
856 Sharpness : {
857 0 : "Normal",
858 1 : "Soft",
859 2 : "Hard"
860 },
861 SubjectDistanceRange : {
862 0 : "Unknown",
863 1 : "Macro",
864 2 : "Close view",
865 3 : "Distant view"
866 },
867 FileSource : {
868 3 : "DSC"
869 },
870
871 Components : {
872 0 : "",
873 1 : "Y",
874 2 : "Cb",
875 3 : "Cr",
876 4 : "R",
877 5 : "G",
878 6 : "B"
879 }
880 };
881
882 function addEvent(element, event, handler) {
883 if (element.addEventListener) {
884 element.addEventListener(event, handler, false);
885 } else if (element.attachEvent) {
886 element.attachEvent("on" + event, handler);
887 }
888 }
889
890 function imageHasData(img) {
891 return !!(img.exifdata);
892 }
893
894 function base64ToArrayBuffer(base64, contentType) {
895 contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
896 base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
897 var binary = atob(base64);
898 var len = binary.length;
899 var buffer = new ArrayBuffer(len);
900 var view = new Uint8Array(buffer);
901 for (var i = 0; i < len; i++) {
902 view[i] = binary.charCodeAt(i);
903 }
904 return buffer;
905 }
906
907 function objectURLToBlob(url, callback) {
908 var http = new XMLHttpRequest();
909 http.open("GET", url, true);
910 http.responseType = "blob";
911 http.onload = function(e) {
912 if (this.status == 200 || this.status === 0) {
913 callback(this.response);
914 }
915 };
916 http.send();
917 }
918
919 function getImageData(img, callback) {
920 function handleBinaryFile(binFile) {
921 var data = findEXIFinJPEG(binFile);
922 var iptcdata = findIPTCinJPEG(binFile);
923 img.exifdata = data || {};
924 img.iptcdata = iptcdata || {};
925 if (callback) {
926 callback.call(img);
927 }
928 }
929
930 if (img.src) {
931 if (/^data\:/i.test(img.src)) { // Data URI
932 var arrayBuffer = base64ToArrayBuffer(img.src);
933 handleBinaryFile(arrayBuffer);
934
935 } else if (/^blob\:/i.test(img.src)) { // Object URL
936 var fileReader = new FileReader();
937 fileReader.onload = function(e) {
938 handleBinaryFile(e.target.result);
939 };
940 objectURLToBlob(img.src, function (blob) {
941 fileReader.readAsArrayBuffer(blob);
942 });
943 } else {
944 var http = new XMLHttpRequest();
945 http.onload = function() {
946 if (this.status == 200 || this.status === 0) {
947 handleBinaryFile(http.response);
948 } else {
949 throw "Could not load image";
950 }
951 http = null;
952 };
953 http.open("GET", img.src, true);
954 http.responseType = "arraybuffer";
955 http.send(null);
956 }
957 } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {
958 var fileReader = new FileReader();
959 fileReader.onload = function(e) {
960 if (debug) console.log("Got file of length " + e.target.result.byteLength);
961 handleBinaryFile(e.target.result);
962 };
963
964 fileReader.readAsArrayBuffer(img);
965 }
966 }
967
968 function findEXIFinJPEG(file) {
969 var dataView = new DataView(file);
970
971 if (debug) console.log("Got file of length " + file.byteLength);
972 if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
973 if (debug) console.log("Not a valid JPEG");
974 return false; // not a valid jpeg
975 }
976
977 var offset = 2,
978 length = file.byteLength,
979 marker;
980
981 while (offset < length) {
982 if (dataView.getUint8(offset) != 0xFF) {
983 if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset));
984 return false; // not a valid marker, something is wrong
985 }
986
987 marker = dataView.getUint8(offset + 1);
988 if (debug) console.log(marker);
989
990 // we could implement handling for other markers here,
991 // but we're only looking for 0xFFE1 for EXIF data
992
993 if (marker == 225) {
994 if (debug) console.log("Found 0xFFE1 marker");
995
996 return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
997
998 // offset += 2 + file.getShortAt(offset+2, true);
999
1000 } else {
1001 offset += 2 + dataView.getUint16(offset+2);
1002 }
1003
1004 }
1005
1006 }
1007
1008 function findIPTCinJPEG(file) {
1009 var dataView = new DataView(file);
1010
1011 if (debug) console.log("Got file of length " + file.byteLength);
1012 if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
1013 if (debug) console.log("Not a valid JPEG");
1014 return false; // not a valid jpeg
1015 }
1016
1017 var offset = 2,
1018 length = file.byteLength;
1019
1020 var isFieldSegmentStart = function(dataView, offset){
1021 return (
1022 dataView.getUint8(offset) === 0x38 &&
1023 dataView.getUint8(offset+1) === 0x42 &&
1024 dataView.getUint8(offset+2) === 0x49 &&
1025 dataView.getUint8(offset+3) === 0x4D &&
1026 dataView.getUint8(offset+4) === 0x04 &&
1027 dataView.getUint8(offset+5) === 0x04
1028 );
1029 };
1030
1031 while (offset < length) {
1032
1033 if ( isFieldSegmentStart(dataView, offset )){
1034
1035 // Get the length of the name header (which is padded to an even number of bytes)
1036 var nameHeaderLength = dataView.getUint8(offset+7);
1037 if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
1038 // Check for pre photoshop 6 format
1039 if(nameHeaderLength === 0) {
1040 // Always 4
1041 nameHeaderLength = 4;
1042 }
1043
1044 var startOffset = offset + 8 + nameHeaderLength;
1045 var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
1046
1047 return readIPTCData(file, startOffset, sectionLength);
1048
1049 break;
1050
1051 }
1052
1053 // Not the marker, continue searching
1054 offset++;
1055
1056 }
1057
1058 }
1059 var IptcFieldMap = {
1060 0x78 : 'caption',
1061 0x6E : 'credit',
1062 0x19 : 'keywords',
1063 0x37 : 'dateCreated',
1064 0x50 : 'byline',
1065 0x55 : 'bylineTitle',
1066 0x7A : 'captionWriter',
1067 0x69 : 'headline',
1068 0x74 : 'copyright',
1069 0x0F : 'category'
1070 };
1071 function readIPTCData(file, startOffset, sectionLength){
1072 var dataView = new DataView(file);
1073 var data = {};
1074 var fieldValue, fieldName, dataSize, segmentType, segmentSize;
1075 var segmentStartPos = startOffset;
1076 while(segmentStartPos < startOffset+sectionLength) {
1077 if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){
1078 segmentType = dataView.getUint8(segmentStartPos+2);
1079 if(segmentType in IptcFieldMap) {
1080 dataSize = dataView.getInt16(segmentStartPos+3);
1081 segmentSize = dataSize + 5;
1082 fieldName = IptcFieldMap[segmentType];
1083 fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);
1084 // Check if we already stored a value with this name
1085 if(data.hasOwnProperty(fieldName)) {
1086 // Value already stored with this name, create multivalue field
1087 if(data[fieldName] instanceof Array) {
1088 data[fieldName].push(fieldValue);
1089 }
1090 else {
1091 data[fieldName] = [data[fieldName], fieldValue];
1092 }
1093 }
1094 else {
1095 data[fieldName] = fieldValue;
1096 }
1097 }
1098
1099 }
1100 segmentStartPos++;
1101 }
1102 return data;
1103 }
1104
1105 function readTags(file, tiffStart, dirStart, strings, bigEnd) {
1106 var entries = file.getUint16(dirStart, !bigEnd),
1107 tags = {},
1108 entryOffset, tag,
1109 i;
1110
1111 for (i=0;i<entries;i++) {
1112 entryOffset = dirStart + i*12 + 2;
1113 tag = strings[file.getUint16(entryOffset, !bigEnd)];
1114 if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
1115 tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
1116 }
1117 return tags;
1118 }
1119
1120 function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
1121 var type = file.getUint16(entryOffset+2, !bigEnd),
1122 numValues = file.getUint32(entryOffset+4, !bigEnd),
1123 valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,
1124 offset,
1125 vals, val, n,
1126 numerator, denominator;
1127
1128 switch (type) {
1129 case 1: // byte, 8-bit unsigned int
1130 case 7: // undefined, 8-bit byte, value depending on field
1131 if (numValues == 1) {
1132 return file.getUint8(entryOffset + 8, !bigEnd);
1133 } else {
1134 offset = numValues > 4 ? valueOffset : (entryOffset + 8);
1135 vals = [];
1136 for (n=0;n<numValues;n++) {
1137 vals[n] = file.getUint8(offset + n);
1138 }
1139 return vals;
1140 }
1141
1142 case 2: // ascii, 8-bit byte
1143 offset = numValues > 4 ? valueOffset : (entryOffset + 8);
1144 return getStringFromDB(file, offset, numValues-1);
1145
1146 case 3: // short, 16 bit int
1147 if (numValues == 1) {
1148 return file.getUint16(entryOffset + 8, !bigEnd);
1149 } else {
1150 offset = numValues > 2 ? valueOffset : (entryOffset + 8);
1151 vals = [];
1152 for (n=0;n<numValues;n++) {
1153 vals[n] = file.getUint16(offset + 2*n, !bigEnd);
1154 }
1155 return vals;
1156 }
1157
1158 case 4: // long, 32 bit int
1159 if (numValues == 1) {
1160 return file.getUint32(entryOffset + 8, !bigEnd);
1161 } else {
1162 vals = [];
1163 for (n=0;n<numValues;n++) {
1164 vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);
1165 }
1166 return vals;
1167 }
1168
1169 case 5: // rational = two long values, first is numerator, second is denominator
1170 if (numValues == 1) {
1171 numerator = file.getUint32(valueOffset, !bigEnd);
1172 denominator = file.getUint32(valueOffset+4, !bigEnd);
1173 val = new Number(numerator / denominator);
1174 val.numerator = numerator;
1175 val.denominator = denominator;
1176 return val;
1177 } else {
1178 vals = [];
1179 for (n=0;n<numValues;n++) {
1180 numerator = file.getUint32(valueOffset + 8*n, !bigEnd);
1181 denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);
1182 vals[n] = new Number(numerator / denominator);
1183 vals[n].numerator = numerator;
1184 vals[n].denominator = denominator;
1185 }
1186 return vals;
1187 }
1188
1189 case 9: // slong, 32 bit signed int
1190 if (numValues == 1) {
1191 return file.getInt32(entryOffset + 8, !bigEnd);
1192 } else {
1193 vals = [];
1194 for (n=0;n<numValues;n++) {
1195 vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);
1196 }
1197 return vals;
1198 }
1199
1200 case 10: // signed rational, two slongs, first is numerator, second is denominator
1201 if (numValues == 1) {
1202 return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);
1203 } else {
1204 vals = [];
1205 for (n=0;n<numValues;n++) {
1206 vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);
1207 }
1208 return vals;
1209 }
1210 }
1211 }
1212
1213 function getStringFromDB(buffer, start, length) {
1214 var outstr = "";
1215 for (var n = start; n < start+length; n++) {
1216 outstr += String.fromCharCode(buffer.getUint8(n));
1217 }
1218 return outstr;
1219 }
1220
1221 function readEXIFData(file, start) {
1222 if (getStringFromDB(file, start, 4) != "Exif") {
1223 if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
1224 return false;
1225 }
1226
1227 var bigEnd,
1228 tags, tag,
1229 exifData, gpsData,
1230 tiffOffset = start + 6;
1231
1232 // test for TIFF validity and endianness
1233 if (file.getUint16(tiffOffset) == 0x4949) {
1234 bigEnd = false;
1235 } else if (file.getUint16(tiffOffset) == 0x4D4D) {
1236 bigEnd = true;
1237 } else {
1238 if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
1239 return false;
1240 }
1241
1242 if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
1243 if (debug) console.log("Not valid TIFF data! (no 0x002A)");
1244 return false;
1245 }
1246
1247 var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
1248
1249 if (firstIFDOffset < 0x00000008) {
1250 if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset+4, !bigEnd));
1251 return false;
1252 }
1253
1254 tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
1255
1256 if (tags.ExifIFDPointer) {
1257 exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
1258 for (tag in exifData) {
1259 switch (tag) {
1260 case "LightSource" :
1261 case "Flash" :
1262 case "MeteringMode" :
1263 case "ExposureProgram" :
1264 case "SensingMethod" :
1265 case "SceneCaptureType" :
1266 case "SceneType" :
1267 case "CustomRendered" :
1268 case "WhiteBalance" :
1269 case "GainControl" :
1270 case "Contrast" :
1271 case "Saturation" :
1272 case "Sharpness" :
1273 case "SubjectDistanceRange" :
1274 case "FileSource" :
1275 exifData[tag] = StringValues[tag][exifData[tag]];
1276 break;
1277
1278 case "ExifVersion" :
1279 case "FlashpixVersion" :
1280 exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
1281 break;
1282
1283 case "ComponentsConfiguration" :
1284 exifData[tag] =
1285 StringValues.Components[exifData[tag][0]] +
1286 StringValues.Components[exifData[tag][1]] +
1287 StringValues.Components[exifData[tag][2]] +
1288 StringValues.Components[exifData[tag][3]];
1289 break;
1290 }
1291 tags[tag] = exifData[tag];
1292 }
1293 }
1294
1295 if (tags.GPSInfoIFDPointer) {
1296 gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
1297 for (tag in gpsData) {
1298 switch (tag) {
1299 case "GPSVersionID" :
1300 gpsData[tag] = gpsData[tag][0] +
1301 "." + gpsData[tag][1] +
1302 "." + gpsData[tag][2] +
1303 "." + gpsData[tag][3];
1304 break;
1305 }
1306 tags[tag] = gpsData[tag];
1307 }
1308 }
1309
1310 return tags;
1311 }
1312
1313 this.getData = function(img, callback) {
1314 if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false;
1315
1316 if (!imageHasData(img)) {
1317 getImageData(img, callback);
1318 } else {
1319 if (callback) {
1320 callback.call(img);
1321 }
1322 }
1323 return true;
1324 }
1325
1326 this.getTag = function(img, tag) {
1327 if (!imageHasData(img)) return;
1328 return img.exifdata[tag];
1329 }
1330
1331 this.getAllTags = function(img) {
1332 if (!imageHasData(img)) return {};
1333 var a,
1334 data = img.exifdata,
1335 tags = {};
1336 for (a in data) {
1337 if (data.hasOwnProperty(a)) {
1338 tags[a] = data[a];
1339 }
1340 }
1341 return tags;
1342 }
1343
1344 this.pretty = function(img) {
1345 if (!imageHasData(img)) return "";
1346 var a,
1347 data = img.exifdata,
1348 strPretty = "";
1349 for (a in data) {
1350 if (data.hasOwnProperty(a)) {
1351 if (typeof data[a] == "object") {
1352 if (data[a] instanceof Number) {
1353 strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
1354 } else {
1355 strPretty += a + " : [" + data[a].length + " values]\r\n";
1356 }
1357 } else {
1358 strPretty += a + " : " + data[a] + "\r\n";
1359 }
1360 }
1361 }
1362 return strPretty;
1363 }
1364
1365 this.readFromBinaryFile = function(file) {
1366 return findEXIFinJPEG(file);
1367 }
1368 }]);
1369
1370 crop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropEXIF', function($document, CropAreaCircle, CropAreaSquare, cropEXIF) {
1371 /* STATIC FUNCTIONS */
1372
1373 // Get Element's Offset
1374 var getElementOffset=function(elem) {
1375 var box = elem.getBoundingClientRect();
1376
1377 var body = document.body;
1378 var docElem = document.documentElement;
1379
1380 var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
1381 var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
1382
1383 var clientTop = docElem.clientTop || body.clientTop || 0;
1384 var clientLeft = docElem.clientLeft || body.clientLeft || 0;
1385
1386 var top = box.top + scrollTop - clientTop;
1387 var left = box.left + scrollLeft - clientLeft;
1388
1389 return { top: Math.round(top), left: Math.round(left) };
1390 };
1391
1392 return function(elCanvas, opts, events){
1393 /* PRIVATE VARIABLES */
1394
1395 // Object Pointers
1396 var ctx=null,
1397 image=null,
1398 theArea=null;
1399
1400 // Dimensions
1401 var minCanvasDims=[100,100],
1402 maxCanvasDims=[300,300];
1403
1404 // Result Image size
1405 var resImgSize=200;
1406
1407 // Result Image type
1408 var resImgFormat='image/png';
1409
1410 // Result Image quality
1411 var resImgQuality=null;
1412
1413 /* PRIVATE FUNCTIONS */
1414
1415 // Draw Scene
1416 function drawScene() {
1417 // clear canvas
1418 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1419
1420 if(image!==null) {
1421 // draw source image
1422 ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height);
1423
1424 ctx.save();
1425
1426 // and make it darker
1427 ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
1428 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1429
1430 ctx.restore();
1431
1432 // draw Area
1433 theArea.draw();
1434 }
1435 }
1436
1437 // Resets CropHost
1438 var resetCropHost=function() {
1439 if(image!==null) {
1440 theArea.setImage(image);
1441 var imageDims=[image.width, image.height],
1442 imageRatio=image.width/image.height,
1443 canvasDims=imageDims;
1444
1445 if(canvasDims[0]>maxCanvasDims[0]) {
1446 canvasDims[0]=maxCanvasDims[0];
1447 canvasDims[1]=canvasDims[0]/imageRatio;
1448 } else if(canvasDims[0]<minCanvasDims[0]) {
1449 canvasDims[0]=minCanvasDims[0];
1450 canvasDims[1]=canvasDims[0]/imageRatio;
1451 }
1452 if(canvasDims[1]>maxCanvasDims[1]) {
1453 canvasDims[1]=maxCanvasDims[1];
1454 canvasDims[0]=canvasDims[1]*imageRatio;
1455 } else if(canvasDims[1]<minCanvasDims[1]) {
1456 canvasDims[1]=minCanvasDims[1];
1457 canvasDims[0]=canvasDims[1]*imageRatio;
1458 }
1459 elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
1460
1461 theArea.setX(ctx.canvas.width/2);
1462 theArea.setY(ctx.canvas.height/2);
1463 theArea.setSize(Math.min(200, ctx.canvas.width/2, ctx.canvas.height/2));
1464 } else {
1465 elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
1466 }
1467
1468 drawScene();
1469 };
1470
1471 /**
1472 * Returns event.changedTouches directly if event is a TouchEvent.
1473 * If event is a jQuery event, return changedTouches of event.originalEvent
1474 */
1475 var getChangedTouches=function(event){
1476 if(angular.isDefined(event.changedTouches)){
1477 return event.changedTouches;
1478 }else{
1479 return event.originalEvent.changedTouches;
1480 }
1481 };
1482
1483 var onMouseMove=function(e) {
1484 if(image!==null) {
1485 var offset=getElementOffset(ctx.canvas),
1486 pageX, pageY;
1487 if(e.type === 'touchmove') {
1488 pageX=getChangedTouches(e)[0].pageX;
1489 pageY=getChangedTouches(e)[0].pageY;
1490 } else {
1491 pageX=e.pageX;
1492 pageY=e.pageY;
1493 }
1494 theArea.processMouseMove(pageX-offset.left, pageY-offset.top);
1495 drawScene();
1496 }
1497 };
1498
1499 var onMouseDown=function(e) {
1500 e.preventDefault();
1501 e.stopPropagation();
1502 if(image!==null) {
1503 var offset=getElementOffset(ctx.canvas),
1504 pageX, pageY;
1505 if(e.type === 'touchstart') {
1506 pageX=getChangedTouches(e)[0].pageX;
1507 pageY=getChangedTouches(e)[0].pageY;
1508 } else {
1509 pageX=e.pageX;
1510 pageY=e.pageY;
1511 }
1512 theArea.processMouseDown(pageX-offset.left, pageY-offset.top);
1513 drawScene();
1514 }
1515 };
1516
1517 var onMouseUp=function(e) {
1518 if(image!==null) {
1519 var offset=getElementOffset(ctx.canvas),
1520 pageX, pageY;
1521 if(e.type === 'touchend') {
1522 pageX=getChangedTouches(e)[0].pageX;
1523 pageY=getChangedTouches(e)[0].pageY;
1524 } else {
1525 pageX=e.pageX;
1526 pageY=e.pageY;
1527 }
1528 theArea.processMouseUp(pageX-offset.left, pageY-offset.top);
1529 drawScene();
1530 }
1531 };
1532
1533 this.getResultImageDataURI=function() {
1534 var temp_ctx, temp_canvas;
1535 temp_canvas = angular.element('<canvas></canvas>')[0];
1536 temp_ctx = temp_canvas.getContext('2d');
1537 temp_canvas.width = resImgSize;
1538 temp_canvas.height = resImgSize;
1539 if(image!==null){
1540 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);
1541 }
1542 if (resImgQuality!==null ){
1543 return temp_canvas.toDataURL(resImgFormat, resImgQuality);
1544 }
1545 return temp_canvas.toDataURL(resImgFormat);
1546 };
1547
1548 this.setNewImageSource=function(imageSource) {
1549 image=null;
1550 resetCropHost();
1551 events.trigger('image-updated');
1552 if(!!imageSource) {
1553 var newImage = new Image();
1554 if(imageSource.substring(0,4).toLowerCase()==='http') {
1555 newImage.crossOrigin = 'anonymous';
1556 }
1557 newImage.onload = function(){
1558 events.trigger('load-done');
1559
1560 cropEXIF.getData(newImage,function(){
1561 var orientation=cropEXIF.getTag(newImage,'Orientation');
1562
1563 if([3,6,8].indexOf(orientation)>-1) {
1564 var canvas = document.createElement("canvas"),
1565 ctx=canvas.getContext("2d"),
1566 cw = newImage.width, ch = newImage.height, cx = 0, cy = 0, deg=0;
1567 switch(orientation) {
1568 case 3:
1569 cx=-newImage.width;
1570 cy=-newImage.height;
1571 deg=180;
1572 break;
1573 case 6:
1574 cw = newImage.height;
1575 ch = newImage.width;
1576 cy=-newImage.height;
1577 deg=90;
1578 break;
1579 case 8:
1580 cw = newImage.height;
1581 ch = newImage.width;
1582 cx=-newImage.width;
1583 deg=270;
1584 break;
1585 }
1586
1587 canvas.width = cw;
1588 canvas.height = ch;
1589 ctx.rotate(deg*Math.PI/180);
1590 ctx.drawImage(newImage, cx, cy);
1591
1592 image=new Image();
1593 image.src = canvas.toDataURL("image/png");
1594 } else {
1595 image=newImage;
1596 }
1597 resetCropHost();
1598 events.trigger('image-updated');
1599 });
1600 };
1601 newImage.onerror=function() {
1602 events.trigger('load-error');
1603 };
1604 events.trigger('load-start');
1605 newImage.src=imageSource;
1606 }
1607 };
1608
1609 this.setMaxDimensions=function(width, height) {
1610 maxCanvasDims=[width,height];
1611
1612 if(image!==null) {
1613 var curWidth=ctx.canvas.width,
1614 curHeight=ctx.canvas.height;
1615
1616 var imageDims=[image.width, image.height],
1617 imageRatio=image.width/image.height,
1618 canvasDims=imageDims;
1619
1620 if(canvasDims[0]>maxCanvasDims[0]) {
1621 canvasDims[0]=maxCanvasDims[0];
1622 canvasDims[1]=canvasDims[0]/imageRatio;
1623 } else if(canvasDims[0]<minCanvasDims[0]) {
1624 canvasDims[0]=minCanvasDims[0];
1625 canvasDims[1]=canvasDims[0]/imageRatio;
1626 }
1627 if(canvasDims[1]>maxCanvasDims[1]) {
1628 canvasDims[1]=maxCanvasDims[1];
1629 canvasDims[0]=canvasDims[1]*imageRatio;
1630 } else if(canvasDims[1]<minCanvasDims[1]) {
1631 canvasDims[1]=minCanvasDims[1];
1632 canvasDims[0]=canvasDims[1]*imageRatio;
1633 }
1634 elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
1635
1636 var ratioNewCurWidth=ctx.canvas.width/curWidth,
1637 ratioNewCurHeight=ctx.canvas.height/curHeight,
1638 ratioMin=Math.min(ratioNewCurWidth, ratioNewCurHeight);
1639
1640 theArea.setX(theArea.getX()*ratioNewCurWidth);
1641 theArea.setY(theArea.getY()*ratioNewCurHeight);
1642 theArea.setSize(theArea.getSize()*ratioMin);
1643 } else {
1644 elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
1645 }
1646
1647 drawScene();
1648
1649 };
1650
1651 this.setAreaMinSize=function(size) {
1652 size=parseInt(size,10);
1653 if(!isNaN(size)) {
1654 theArea.setMinSize(size);
1655 drawScene();
1656 }
1657 };
1658
1659 this.setResultImageSize=function(size) {
1660 size=parseInt(size,10);
1661 if(!isNaN(size)) {
1662 resImgSize=size;
1663 }
1664 };
1665
1666 this.setResultImageFormat=function(format) {
1667 resImgFormat = format;
1668 };
1669
1670 this.setResultImageQuality=function(quality){
1671 quality = parseFloat(quality);
1672 if (!isNaN(quality) && quality>=0 && quality<=1){
1673 resImgQuality = quality;
1674 }
1675 };
1676
1677 this.setAreaType=function(type) {
1678 var curSize=theArea.getSize(),
1679 curMinSize=theArea.getMinSize(),
1680 curX=theArea.getX(),
1681 curY=theArea.getY();
1682
1683 var AreaClass=CropAreaCircle;
1684 if(type==='square') {
1685 AreaClass=CropAreaSquare;
1686 }
1687 theArea = new AreaClass(ctx, events);
1688 theArea.setMinSize(curMinSize);
1689 theArea.setSize(curSize);
1690 theArea.setX(curX);
1691 theArea.setY(curY);
1692
1693 // resetCropHost();
1694 if(image!==null) {
1695 theArea.setImage(image);
1696 }
1697
1698 drawScene();
1699 };
1700
1701 /* Life Cycle begins */
1702
1703 // Init Context var
1704 ctx = elCanvas[0].getContext('2d');
1705
1706 // Init CropArea
1707 theArea = new CropAreaCircle(ctx, events);
1708
1709 // Init Mouse Event Listeners
1710 $document.on('mousemove',onMouseMove);
1711 elCanvas.on('mousedown',onMouseDown);
1712 $document.on('mouseup',onMouseUp);
1713
1714 // Init Touch Event Listeners
1715 $document.on('touchmove',onMouseMove);
1716 elCanvas.on('touchstart',onMouseDown);
1717 $document.on('touchend',onMouseUp);
1718
1719 // CropHost Destructor
1720 this.destroy=function() {
1721 $document.off('mousemove',onMouseMove);
1722 elCanvas.off('mousedown',onMouseDown);
1723 $document.off('mouseup',onMouseMove);
1724
1725 $document.off('touchmove',onMouseMove);
1726 elCanvas.off('touchstart',onMouseDown);
1727 $document.off('touchend',onMouseMove);
1728
1729 elCanvas.remove();
1730 };
1731 };
1732
1733 }]);
1734
1735
1736 crop.factory('cropPubSub', [function() {
1737 return function() {
1738 var events = {};
1739 // Subscribe
1740 this.on = function(names, handler) {
1741 names.split(' ').forEach(function(name) {
1742 if (!events[name]) {
1743 events[name] = [];
1744 }
1745 events[name].push(handler);
1746 });
1747 return this;
1748 };
1749 // Publish
1750 this.trigger = function(name, args) {
1751 angular.forEach(events[name], function(handler) {
1752 handler.call(null, args);
1753 });
1754 return this;
1755 };
1756 };
1757 }]);
1758
1759 crop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function($timeout, CropHost, CropPubSub) {
1760 return {
1761 restrict: 'E',
1762 scope: {
1763 image: '=',
1764 resultImage: '=',
1765
1766 changeOnFly: '=',
1767 areaType: '@',
1768 areaMinSize: '=',
1769 resultImageSize: '=',
1770 resultImageFormat: '@',
1771 resultImageQuality: '=',
1772
1773 onChange: '&',
1774 onLoadBegin: '&',
1775 onLoadDone: '&',
1776 onLoadError: '&'
1777 },
1778 template: '<canvas></canvas>',
1779 controller: ['$scope', function($scope) {
1780 $scope.events = new CropPubSub();
1781 }],
1782 link: function(scope, element/*, attrs*/) {
1783 // Init Events Manager
1784 var events = scope.events;
1785
1786 // Init Crop Host
1787 var cropHost=new CropHost(element.find('canvas'), {}, events);
1788
1789 // Store Result Image to check if it's changed
1790 var storedResultImage;
1791
1792 var updateResultImage=function(scope) {
1793 var resultImage=cropHost.getResultImageDataURI();
1794 if(storedResultImage!==resultImage) {
1795 storedResultImage=resultImage;
1796 if(angular.isDefined(scope.resultImage)) {
1797 scope.resultImage=resultImage;
1798 }
1799 scope.onChange({$dataURI: scope.resultImage});
1800 }
1801 };
1802
1803 // Wrapper to safely exec functions within $apply on a running $digest cycle
1804 var fnSafeApply=function(fn) {
1805 return function(){
1806 $timeout(function(){
1807 scope.$apply(function(scope){
1808 fn(scope);
1809 });
1810 });
1811 };
1812 };
1813
1814 // Setup CropHost Event Handlers
1815 events
1816 .on('load-start', fnSafeApply(function(scope){
1817 scope.onLoadBegin({});
1818 }))
1819 .on('load-done', fnSafeApply(function(scope){
1820 scope.onLoadDone({});
1821 }))
1822 .on('load-error', fnSafeApply(function(scope){
1823 scope.onLoadError({});
1824 }))
1825 .on('area-move area-resize', fnSafeApply(function(scope){
1826 if(!!scope.changeOnFly) {
1827 updateResultImage(scope);
1828 }
1829 }))
1830 .on('area-move-end area-resize-end image-updated', fnSafeApply(function(scope){
1831 updateResultImage(scope);
1832 }));
1833
1834 // Sync CropHost with Directive's options
1835 scope.$watch('image',function(){
1836 cropHost.setNewImageSource(scope.image);
1837 });
1838 scope.$watch('areaType',function(){
1839 cropHost.setAreaType(scope.areaType);
1840 updateResultImage(scope);
1841 });
1842 scope.$watch('areaMinSize',function(){
1843 cropHost.setAreaMinSize(scope.areaMinSize);
1844 updateResultImage(scope);
1845 });
1846 scope.$watch('resultImageSize',function(){
1847 cropHost.setResultImageSize(scope.resultImageSize);
1848 updateResultImage(scope);
1849 });
1850 scope.$watch('resultImageFormat',function(){
1851 cropHost.setResultImageFormat(scope.resultImageFormat);
1852 updateResultImage(scope);
1853 });
1854 scope.$watch('resultImageQuality',function(){
1855 cropHost.setResultImageQuality(scope.resultImageQuality);
1856 updateResultImage(scope);
1857 });
1858
1859 // Update CropHost dimensions when the directive element is resized
1860 scope.$watch(
1861 function () {
1862 return [element[0].clientWidth, element[0].clientHeight];
1863 },
1864 function (value) {
1865 cropHost.setMaxDimensions(value[0],value[1]);
1866 updateResultImage(scope);
1867 },
1868 true
1869 );
1870
1871 // Destroy CropHost Instance when the directive is destroying
1872 scope.$on('$destroy', function(){
1873 cropHost.destroy();
1874 });
1875 }
1876 };
1877 }]);
1878 }());
+0
-270
demo/src/main/webapp/js/upload.js less more
0 'use strict';
1
2
3 var app = angular.module('fileUpload', ['ngFileUpload']);
4 var version = '11.0.0';
5
6 app.controller('MyCtrl', ['$scope', '$http', '$timeout', '$compile', 'Upload', function ($scope, $http, $timeout, $compile, Upload) {
7 $scope.usingFlash = FileAPI && FileAPI.upload != null;
8 //Upload.setDefaults({ngfKeep: true, ngfPattern:'image/*'});
9 $scope.changeAngularVersion = function () {
10 window.location.hash = $scope.angularVersion;
11 window.location.reload(true);
12 };
13 $scope.angularVersion = window.location.hash.length > 1 ? (window.location.hash.indexOf('/') === 1 ?
14 window.location.hash.substring(2) : window.location.hash.substring(1)) : '1.2.24';
15
16 $scope.invalidFiles = [];
17
18 $scope.$watch('files', function (files) {
19 $scope.formUpload = false;
20 if (files != null) {
21 if (!angular.isArray(files)) {
22 $timeout(function () {
23 $scope.files = files = [files];
24 });
25 return;
26 }
27 for (var i = 0; i < files.length; i++) {
28 Upload.imageDimensions(files[i]).then(function (d) {
29 $scope.d = d;
30 });
31 $scope.errorMsg = null;
32 (function (f) {
33 $scope.upload(f, true);
34 })(files[i]);
35 }
36 }
37 });
38
39 $scope.uploadPic = function (file) {
40 $scope.formUpload = true;
41 if (file != null) {
42 $scope.upload(file);
43 }
44 };
45
46 $scope.upload = function(file, resumable) {
47 $scope.errorMsg = null;
48 if ($scope.howToSend === 1) {
49 uploadUsingUpload(file, resumable);
50 } else if ($scope.howToSend == 2) {
51 uploadUsing$http(file);
52 } else {
53 uploadS3(file);
54 }
55 };
56
57 $scope.isResumeSupported = Upload.isResumeSupported();
58
59 $scope.restart = function(file) {
60 if (Upload.isResumeSupported()) {
61 $http.get('https://angular-file-upload-cors-srv.appspot.com/upload?restart=true&name=' + encodeURIComponent(file.name)).then(function () {
62 $scope.upload(file, true);
63 });
64 } else {
65 $scope.upload(file);
66 }
67 };
68
69 $scope.chunkSize = 100000;
70 function uploadUsingUpload(file, resumable) {
71 file.upload = Upload.upload({
72 url: 'https://angular-file-upload-cors-srv.appspot.com/upload' + $scope.getReqParams(),
73 resumeSizeUrl: resumable ? 'https://angular-file-upload-cors-srv.appspot.com/upload?name=' + encodeURIComponent(file.name) : null,
74 resumeChunkSize: resumable ? $scope.chunkSize : null,
75 headers: {
76 'optional-header': 'header-value'
77 },
78 data: {username: $scope.username, file: file}
79 });
80
81 file.upload.then(function (response) {
82 $timeout(function () {
83 file.result = response.data;
84 });
85 }, function (response) {
86 if (response.status > 0)
87 $scope.errorMsg = response.status + ': ' + response.data;
88 }, function (evt) {
89 // Math.min is to fix IE which reports 200% sometimes
90 file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
91 });
92
93 file.upload.xhr(function (xhr) {
94 // xhr.upload.addEventListener('abort', function(){console.log('abort complete')}, false);
95 });
96 }
97
98 function uploadUsing$http(file) {
99 file.upload = Upload.http({
100 url: 'https://angular-file-upload-cors-srv.appspot.com/upload' + $scope.getReqParams(),
101 method: 'POST',
102 headers: {
103 'Content-Type': file.type
104 },
105 data: file
106 });
107
108 file.upload.then(function (response) {
109 file.result = response.data;
110 }, function (response) {
111 if (response.status > 0)
112 $scope.errorMsg = response.status + ': ' + response.data;
113 });
114
115 file.upload.progress(function (evt) {
116 file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
117 });
118 }
119
120 function uploadS3(file) {
121 file.upload = Upload.upload({
122 url: $scope.s3url,
123 method: 'POST',
124 data: {
125 key: file.name,
126 AWSAccessKeyId: $scope.AWSAccessKeyId,
127 acl: $scope.acl,
128 policy: $scope.policy,
129 signature: $scope.signature,
130 'Content-Type': file.type === null || file.type === '' ? 'application/octet-stream' : file.type,
131 filename: file.name,
132 file: file
133 }
134 });
135
136 file.upload.then(function (response) {
137 $timeout(function () {
138 file.result = response.data;
139 });
140 }, function (response) {
141 if (response.status > 0)
142 $scope.errorMsg = response.status + ': ' + response.data;
143 });
144
145 file.upload.progress(function (evt) {
146 file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
147 });
148 storeS3UploadConfigInLocalStore();
149 }
150
151 $scope.generateSignature = function () {
152 $http.post('/s3sign?aws-secret-key=' + encodeURIComponent($scope.AWSSecretKey), $scope.jsonPolicy).
153 success(function (data) {
154 $scope.policy = data.policy;
155 $scope.signature = data.signature;
156 });
157 };
158
159 if (localStorage) {
160 $scope.s3url = localStorage.getItem('s3url');
161 $scope.AWSAccessKeyId = localStorage.getItem('AWSAccessKeyId');
162 $scope.acl = localStorage.getItem('acl');
163 $scope.success_action_redirect = localStorage.getItem('success_action_redirect');
164 $scope.policy = localStorage.getItem('policy');
165 $scope.signature = localStorage.getItem('signature');
166 }
167
168 $scope.success_action_redirect = $scope.success_action_redirect || window.location.protocol + '//' + window.location.host;
169 $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}';
170 $scope.acl = $scope.acl || 'private';
171
172 function storeS3UploadConfigInLocalStore() {
173 if ($scope.howToSend === 3 && localStorage) {
174 localStorage.setItem('s3url', $scope.s3url);
175 localStorage.setItem('AWSAccessKeyId', $scope.AWSAccessKeyId);
176 localStorage.setItem('acl', $scope.acl);
177 localStorage.setItem('success_action_redirect', $scope.success_action_redirect);
178 localStorage.setItem('policy', $scope.policy);
179 localStorage.setItem('signature', $scope.signature);
180 }
181 }
182
183 (function handleDynamicEditingOfScriptsAndHtml($scope) {
184 $scope.defaultHtml = document.getElementById('editArea').innerHTML.replace(/\t\t\t\t/g, '').replace(/&amp;/g, '&');
185
186 var fromLocal = (localStorage && localStorage.getItem('editHtml' + version));
187 $scope.editHtml = fromLocal || $scope.defaultHtml;
188 function htmlEdit() {
189 document.getElementById('editArea').innerHTML = $scope.editHtml;
190 $compile(document.getElementById('editArea'))($scope);
191 $scope.editHtml && localStorage && localStorage.setItem('editHtml' + version, $scope.editHtml);
192 if ($scope.editHtml != $scope.htmlEditor.getValue()) $scope.htmlEditor.setValue($scope.editHtml);
193 }
194
195 $scope.$watch('editHtml', htmlEdit);
196
197 $scope.htmlEditor = CodeMirror(document.getElementById('htmlEdit'), {
198 lineNumbers: true, indentUnit: 4,
199 mode: 'htmlmixed'
200 });
201 $scope.htmlEditor.on('change', function () {
202 if ($scope.editHtml != $scope.htmlEditor.getValue()) {
203 $scope.editHtml = $scope.htmlEditor.getValue();
204 htmlEdit();
205 }
206 });
207 })($scope, $http);
208
209 $scope.confirm = function () {
210 return confirm('Are you sure? Your local changes will be lost.');
211 };
212
213 $scope.getReqParams = function () {
214 return $scope.generateErrorOnServer ? '?errorCode=' + $scope.serverErrorCode +
215 '&errorMessage=' + $scope.serverErrorMsg : '';
216 };
217
218 angular.element(window).bind('dragover', function (e) {
219 e.preventDefault();
220 });
221 angular.element(window).bind('drop', function (e) {
222 e.preventDefault();
223 });
224
225 $scope.modelOptionsObj = {};
226 $scope.$watch('validate+dragOverClass+modelOptions+resize+resizeIf', function (v) {
227 $scope.validateObj = eval('(function(){return ' + $scope.validate + ';})()');
228 $scope.dragOverClassObj = eval('(function(){return ' + $scope.dragOverClass + ';})()');
229 $scope.modelOptionsObj = eval('(function(){return ' + $scope.modelOptions + ';})()');
230 $scope.resizeObj = eval('(function($file){return ' + $scope.resize + ';})()');
231 $scope.resizeIfFn = eval('(function(){var fn = function($file, $width, $height){return ' + $scope.resizeIf + ';};return fn;})()');
232 });
233
234 $timeout(function () {
235 $scope.capture = localStorage.getItem('capture' + version) || 'camera';
236 $scope.pattern = localStorage.getItem('pattern' + version) || 'image/*,audio/*,video/*';
237 $scope.acceptSelect = localStorage.getItem('acceptSelect' + version) || 'image/*,audio/*,video/*';
238 $scope.modelOptions = localStorage.getItem('modelOptions' + version) || '{debounce:100}';
239 $scope.dragOverClass = localStorage.getItem('dragOverClass' + version) || '{accept:\'dragover\', reject:\'dragover-err\', pattern:\'image/*,audio/*,video/*,text/*\'}';
240 $scope.disabled = localStorage.getItem('disabled' + version) == 'true' || false;
241 $scope.multiple = localStorage.getItem('multiple' + version) == 'true' || false;
242 $scope.allowDir = localStorage.getItem('allowDir' + version) == 'true' || true;
243 $scope.validate = localStorage.getItem('validate' + version) || '{size: {max: \'20MB\', min: \'10B\'}, height: {max: 12000}, width: {max: 12000}, duration: {max: \'5m\'}}';
244 $scope.keep = localStorage.getItem('keep' + version) == 'true' || false;
245 $scope.keepDistinct = localStorage.getItem('keepDistinct' + version) == 'true' || false;
246 $scope.orientation = localStorage.getItem('orientation' + version) == 'true' || false;
247 $scope.resize = localStorage.getItem('resize' + version) || "{width: 1000, height: 1000, centerCrop: true}";
248 $scope.resizeIf = localStorage.getItem('resizeIf' + version) || "$width > 5000 || $height > 5000";
249 $scope.dimensions = localStorage.getItem('dimensions' + version) || "$width < 12000 || $height < 12000";
250 $scope.duration = localStorage.getItem('duration' + version) || "$duration < 10000";
251 $scope.$watch('validate+capture+pattern+acceptSelect+disabled+capture+multiple+allowDir+keep+orientation+' +
252 'keepDistinct+modelOptions+dragOverClass+resize+resizeIf', function () {
253 localStorage.setItem('capture' + version, $scope.capture);
254 localStorage.setItem('pattern' + version, $scope.pattern);
255 localStorage.setItem('acceptSelect' + version, $scope.acceptSelect);
256 localStorage.setItem('disabled' + version, $scope.disabled);
257 localStorage.setItem('multiple' + version, $scope.multiple);
258 localStorage.setItem('allowDir' + version, $scope.allowDir);
259 localStorage.setItem('validate' + version, $scope.validate);
260 localStorage.setItem('keep' + version, $scope.keep);
261 localStorage.setItem('orientation' + version, $scope.orientation);
262 localStorage.setItem('keepDistinct' + version, $scope.keepDistinct);
263 localStorage.setItem('dragOverClass' + version, $scope.dragOverClass);
264 localStorage.setItem('modelOptions' + version, $scope.modelOptions);
265 localStorage.setItem('resize' + version, $scope.resize);
266 localStorage.setItem('resizeIf' + version, $scope.resizeIf);
267 });
268 });
269 }]);
Binary diff not shown
0 /*! 12.2.13 */
1 /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git
2 * 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.
3 */
4 !function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;h<b.length;h+=1)g[h]=b.charCodeAt(h);return i=a.split(",")[0].split(":")[1].split(";")[0],c?new Blob([d?g:f],{type:i}):(j=new e,j.append(f),j.getBlob(i))};a.HTMLCanvasElement&&!b.toBlob&&(b.mozGetAsFile?b.toBlob=function(a,c,d){a(d&&b.toDataURL&&f?f(this.toDataURL(c,d)):this.mozGetAsFile("blob",c))}:b.toDataURL&&f&&(b.toBlob=function(a,b,c){a(f(this.toDataURL(b,c)))})),a.dataURLtoBlob=f}(window),function(a,b){"use strict";function c(a,b,c,d,e){var f={type:c.type||c,target:a,result:d};S(f,e),b(f)}function d(a){return u&&!!u.prototype["readAs"+a]}function e(a,e,f,g){if(Y.isBlob(a)&&d(f)){var h=new u;T(h,M,function j(b){var d=b.type;"progress"==d?c(a,e,b,b.target.result,{loaded:b.loaded,total:b.total}):"loadend"==d?(U(h,M,j),h=null):c(a,e,b,b.target.result)});try{g?h["readAs"+f](a,g):h["readAs"+f](a)}catch(i){c(a,e,"error",b,{error:i.toString()})}}else c(a,e,"error",b,{error:"FileReader_not_support_"+f})}function f(a,b){if(!a.type&&a.size%4096===0&&a.size<=102400)if(u)try{var c=new u;V(c,M,function(a){var d="error"!=a.type;b(d),d&&c.abort()}),c.readAsDataURL(a)}catch(d){b(!1)}else b(null);else b(!0)}function g(a){var b;return a.getAsEntry?b=a.getAsEntry():a.webkitGetAsEntry&&(b=a.webkitGetAsEntry()),b}function h(a,b){if(a)if(a.isFile)a.file(function(c){c.fullPath=a.fullPath,b(!1,[c])},function(a){b("FileError.code: "+a.code)});else if(a.isDirectory){var c=a.createReader(),d=[];c.readEntries(function(a){Y.afor(a,function(a,c){h(c,function(c,e){c?Y.log(c):d=d.concat(e),a?a():b(!1,d)})})},function(a){b("directory_reader: "+a)})}else h(g(a),b);else b("invalid entry")}function i(a){var b={};return R(a,function(a,c){a&&"object"==typeof a&&void 0===a.nodeType&&(a=S({},a)),b[c]=a}),b}function j(a){return F.test(a&&a.tagName)}function k(a){return(a.originalEvent||a||"").dataTransfer||{}}function l(a){var b;for(b in a)if(a.hasOwnProperty(b)&&!(a[b]instanceof Object||"overlay"===b||"filter"===b))return!0;return!1}var m=1,n=function(){},o=a.document,p=o.doctype||{},q=a.navigator.userAgent,r=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL&&webkitURL,s=a.Blob,t=a.File,u=a.FileReader,v=a.FormData,w=a.XMLHttpRequest,x=a.jQuery,y=!(!(t&&u&&(a.Uint8Array||v||w.prototype.sendAsBinary))||/safari\//i.test(q)&&!/chrome\//i.test(q)&&/windows/i.test(q)),z=y&&"withCredentials"in new w,A=y&&!!s&&!!(s.prototype.webkitSlice||s.prototype.mozSlice||s.prototype.slice),B=a.dataURLtoBlob,C=/img/i,D=/canvas/i,E=/img|canvas/i,F=/input/i,G=/^data:[^,]+,/,H={}.toString,I=a.Math,J=function(b){return b=new a.Number(I.pow(1024,b)),b.from=function(a){return I.round(a*this)},b},K={},L=[],M="abort progress error load loadend",N="status statusText readyState response responseXML responseText responseBody".split(" "),O="currentTarget",P="preventDefault",Q=function(a){return a&&"length"in a},R=function(a,b,c){if(a)if(Q(a))for(var d=0,e=a.length;e>d;d++)d in a&&b.call(c,a[d],d,a);else for(var f in a)a.hasOwnProperty(f)&&b.call(c,a[f],f,a)},S=function(a){for(var b=arguments,c=1,d=function(b,c){a[c]=b};c<b.length;c++)R(b[c],d);return a},T=function(a,b,c){if(a){var d=Y.uid(a);K[d]||(K[d]={});var e=u&&a&&a instanceof u;R(b.split(/\s+/),function(b){x&&!e?x.event.add(a,b,c):(K[d][b]||(K[d][b]=[]),K[d][b].push(c),a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c)})}},U=function(a,b,c){if(a){var d=Y.uid(a),e=K[d]||{},f=u&&a&&a instanceof u;R(b.split(/\s+/),function(b){if(x&&!f)x.event.remove(a,b,c);else{for(var d=e[b]||[],g=d.length;g--;)if(d[g]===c){d.splice(g,1);break}a.addEventListener?a.removeEventListener(b,c,!1):a.detachEvent?a.detachEvent("on"+b,c):a["on"+b]=null}})}},V=function(a,b,c){T(a,b,function d(e){U(a,b,d),c(e)})},W=function(b){return b.target||(b.target=a.event&&a.event.srcElement||o),3===b.target.nodeType&&(b.target=b.target.parentNode),b},X=function(a){var b=o.createElement("input");return b.setAttribute("type","file"),a in b},Y={version:"2.0.7",cors:!1,html5:!0,media:!1,formData:!0,multiPassResize:!0,debug:!1,pingUrl:!1,multiFlash:!1,flashAbortTimeout:0,withCredentials:!0,staticPath:"./dist/",flashUrl:0,flashImageUrl:0,postNameConcat:function(a,b){return a+(null!=b?"["+b+"]":"")},ext2mime:{jpg:"image/jpeg",tif:"image/tiff",txt:"text/plain"},accept:{"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","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","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"},uploadRetry:0,networkDownRetryTimeout:5e3,chunkSize:0,chunkUploadRetry:0,chunkNetworkDownRetryTimeout:2e3,KB:J(1),MB:J(2),GB:J(3),TB:J(4),EMPTY_PNG:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=",expando:"fileapi"+(new Date).getTime(),uid:function(a){return a?a[Y.expando]=a[Y.expando]||Y.uid():(++m,Y.expando+m)},log:function(){Y.debug&&Y._supportConsoleLog&&(Y._supportConsoleLogApply?console.log.apply(console,arguments):console.log([].join.call(arguments," ")))},newImage:function(a,b){var c=o.createElement("img");return b&&Y.event.one(c,"error load",function(a){b("error"==a.type,c),c=null}),c.src=a,c},getXHR:function(){var b;if(w)b=new w;else if(a.ActiveXObject)try{b=new ActiveXObject("MSXML2.XMLHttp.3.0")}catch(c){b=new ActiveXObject("Microsoft.XMLHTTP")}return b},isArray:Q,support:{dnd:z&&"ondrop"in o.createElement("div"),cors:z,html5:y,chunked:A,dataURI:!0,accept:X("accept"),multiple:X("multiple")},event:{on:T,off:U,one:V,fix:W},throttle:function(b,c){var d,e;return function(){e=arguments,d||(b.apply(a,e),d=setTimeout(function(){d=0,b.apply(a,e)},c))}},F:function(){},parseJSON:function(b){var c;return c=a.JSON&&JSON.parse?JSON.parse(b):new Function("return ("+b.replace(/([\r\n])/g,"\\$1")+");")()},trim:function(a){return a=String(a),a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},defer:function(){var a,c,d=[],e={resolve:function(b,f){for(e.resolve=n,c=b||!1,a=f;f=d.shift();)f(c,a)},then:function(e){c!==b?e(c,a):d.push(e)}};return e},queue:function(a){var b=0,c=0,d=!1,e=!1,f={inc:function(){c++},next:function(){b++,setTimeout(f.check,0)},check:function(){b>=c&&!d&&f.end()},isFail:function(){return d},fail:function(){!d&&a(d=!0)},end:function(){e||(e=!0,a())}};return f},each:R,afor:function(a,b){var c=0,d=a.length;Q(a)&&d--?!function e(){b(d!=c&&e,a[c],c++)}():b(!1)},extend:S,isFile:function(a){return"[object File]"===H.call(a)},isBlob:function(a){return this.isFile(a)||"[object Blob]"===H.call(a)},isCanvas:function(a){return a&&D.test(a.nodeName)},getFilesFilter:function(a){return a="string"==typeof a?a:a.getAttribute&&a.getAttribute("accept")||"",a?new RegExp("("+a.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(a,b){Y.isCanvas(a)?c(a,b,"load",Y.toDataURL(a)):e(a,b,"DataURL")},readAsBinaryString:function(a,b){d("BinaryString")?e(a,b,"BinaryString"):e(a,function(a){if("load"==a.type)try{a.result=Y.toBinaryString(a.result)}catch(c){a.type="error",a.message=c.toString()}b(a)},"DataURL")},readAsArrayBuffer:function(a,b){e(a,b,"ArrayBuffer")},readAsText:function(a,b,c){c||(c=b,b="utf-8"),e(a,c,"Text",b)},toDataURL:function(a,b){return"string"==typeof a?a:a.toDataURL?a.toDataURL(b||"image/png"):void 0},toBinaryString:function(b){return a.atob(Y.toDataURL(b).replace(G,""))},readAsImage:function(a,d,e){if(Y.isFile(a))if(r){var f=r.createObjectURL(a);f===b?c(a,d,"error"):Y.readAsImage(f,d,e)}else Y.readAsDataURL(a,function(b){"load"==b.type?Y.readAsImage(b.result,d,e):(e||"error"==b.type)&&c(a,d,b,null,{loaded:b.loaded,total:b.total})});else if(Y.isCanvas(a))c(a,d,"load",a);else if(C.test(a.nodeName))if(a.complete)c(a,d,"load",a);else{var g="error abort load";V(a,g,function i(b){"load"==b.type&&r&&r.revokeObjectURL(a.src),U(a,g,i),c(a,d,b,a)})}else if(a.iframe)c(a,d,{type:"error"});else{var h=Y.newImage(a.dataURL||a);Y.readAsImage(h,d,e)}},checkFileObj:function(a){var b={},c=Y.accept;return"object"==typeof a?b=a:b.name=(a+"").split(/\\|\//g).pop(),null==b.type&&(b.type=b.name.split(".").pop()),R(c,function(a,c){a=new RegExp(a.replace(/\s/g,"|"),"i"),(a.test(b.type)||Y.ext2mime[b.type])&&(b.type=Y.ext2mime[b.type]||c.split("/")[0]+"/"+b.type)}),b},getDropFiles:function(a,b){var c=[],d=k(a),e=Q(d.items)&&d.items[0]&&g(d.items[0]),i=Y.queue(function(){b(c)});R((e?d.items:d.files)||[],function(a){i.inc();try{e?h(a,function(a,b){a?Y.log("[err] getDropFiles:",a):c.push.apply(c,b),i.next()}):f(a,function(b){b&&c.push(a),i.next()})}catch(b){i.next(),Y.log("[err] getDropFiles: ",b)}}),i.check()},getFiles:function(a,b,c){var d=[];return c?(Y.filterFiles(Y.getFiles(a),b,c),null):(a.jquery&&(a.each(function(){d=d.concat(Y.getFiles(this))}),a=d,d=[]),"string"==typeof b&&(b=Y.getFilesFilter(b)),a.originalEvent?a=W(a.originalEvent):a.srcElement&&(a=W(a)),a.dataTransfer?a=a.dataTransfer:a.target&&(a=a.target),a.files?(d=a.files,y||(d[0].blob=a,d[0].iframe=!0)):!y&&j(a)?Y.trim(a.value)&&(d=[Y.checkFileObj(a.value)],d[0].blob=a,d[0].iframe=!0):Q(a)&&(d=a),Y.filter(d,function(a){return!b||b.test(a.name)}))},getTotalSize:function(a){for(var b=0,c=a&&a.length;c--;)b+=a[c].size;return b},getInfo:function(a,b){var c={},d=L.concat();Y.isFile(a)?!function e(){var f=d.shift();f?f.test(a.type)?f(a,function(a,d){a?b(a):(S(c,d),e())}):e():b(!1,c)}():b("not_support_info",c)},addInfoReader:function(a,b){b.test=function(b){return a.test(b)},L.push(b)},filter:function(a,b){for(var c,d=[],e=0,f=a.length;f>e;e++)e in a&&(c=a[e],b.call(c,c,e,a)&&d.push(c));return d},filterFiles:function(a,b,c){if(a.length){var d,e=a.concat(),f=[],g=[];!function h(){e.length?(d=e.shift(),Y.getInfo(d,function(a,c){(b(d,a?!1:c)?f:g).push(d),h()})):c(f,g)}()}else c([],a)},upload:function(a){a=S({jsonp:"callback",prepare:Y.F,beforeupload:Y.F,upload:Y.F,fileupload:Y.F,fileprogress:Y.F,filecomplete:Y.F,progress:Y.F,complete:Y.F,pause:Y.F,imageOriginal:!0,chunkSize:Y.chunkSize,chunkUploadRetry:Y.chunkUploadRetry,uploadRetry:Y.uploadRetry},a),a.imageAutoOrientation&&!a.imageTransform&&(a.imageTransform={rotate:"auto"});var b,c=new Y.XHR(a),d=this._getFilesDataArray(a.files),e=this,f=0,g=0,h=!1;return R(d,function(a){f+=a.size}),c.files=[],R(d,function(a){c.files.push(a.file)}),c.total=f,c.loaded=0,c.filesLeft=d.length,a.beforeupload(c,a),b=function(){var j=d.shift(),k=j&&j.file,l=!1,m=i(a);if(c.filesLeft=d.length,k&&k.name===Y.expando&&(k=null,Y.log("[warn] FileAPI.upload() — called without files")),("abort"!=c.statusText||c.current)&&j){if(h=!1,c.currentFile=k,k&&a.prepare(k,m)===!1)return void b.call(e);m.file=k,e._getFormData(m,j,function(h){g||a.upload(c,a);var i=new Y.XHR(S({},m,{upload:k?function(){a.fileupload(k,i,m)}:n,progress:k?function(b){l||(l=b.loaded===b.total,a.fileprogress({type:"progress",total:j.total=b.total,loaded:j.loaded=b.loaded},k,i,m),a.progress({type:"progress",total:f,loaded:c.loaded=g+j.size*(b.loaded/b.total)|0},k,i,m))}:n,complete:function(d){R(N,function(a){c[a]=i[a]}),k&&(j.total=j.total||j.size,j.loaded=j.total,d||(this.progress(j),l=!0,g+=j.size,c.loaded=g),a.filecomplete(d,i,k,m)),setTimeout(function(){b.call(e)},0)}}));c.abort=function(a){a||(d.length=0),this.current=a,i.abort()},i.send(h)})}else{var o=200==c.status||201==c.status||204==c.status;a.complete(o?!1:c.statusText||"error",c,a),h=!0}},setTimeout(b,0),c.append=function(a,g){a=Y._getFilesDataArray([].concat(a)),R(a,function(a){f+=a.size,c.files.push(a.file),g?d.unshift(a):d.push(a)}),c.statusText="",h&&b.call(e)},c.remove=function(a){for(var b,c=d.length;c--;)d[c].file==a&&(b=d.splice(c,1),f-=b.size);return b},c},_getFilesDataArray:function(a){var b=[],c={};if(j(a)){var d=Y.getFiles(a);c[a.name||"file"]=null!==a.getAttribute("multiple")?d:d[0]}else Q(a)&&j(a[0])?R(a,function(a){c[a.name||"file"]=Y.getFiles(a)}):c=a;return R(c,function e(a,c){Q(a)?R(a,function(a){e(a,c)}):a&&(a.name||a.image)&&b.push({name:c,file:a,size:a.size,total:a.size,loaded:0})}),b.length||b.push({file:{name:Y.expando}}),b},_getFormData:function(a,b,c){var d=b.file,e=b.name,f=d.name,g=d.type,h=Y.support.transform&&a.imageTransform,i=new Y.Form,j=Y.queue(function(){c(i)}),k=h&&l(h),m=Y.postNameConcat;R(a.data,function n(a,b){"object"==typeof a?R(a,function(a,c){n(a,m(b,c))}):i.append(b,a)}),function o(b){b.image?(j.inc(),b.toData(function(a,b){f=f||(new Date).getTime()+".png",o(b),j.next()})):Y.Image&&h&&(/^image/.test(b.type)||E.test(b.nodeName))?(j.inc(),k&&(h=[h]),Y.Image.transform(b,h,a.imageAutoOrientation,function(c,d){if(k&&!c)B||Y.flashEngine||(i.multipart=!0),i.append(e,d[0],f,h[0].type||g);else{var l=0;c||R(d,function(a,b){B||Y.flashEngine||(i.multipart=!0),h[b].postName||(l=1),i.append(h[b].postName||m(e,b),a,f,h[b].type||g)}),(c||a.imageOriginal)&&i.append(m(e,l?"original":null),b,f,g)}j.next()})):f!==Y.expando&&i.append(e,b,f)}(d),j.check()},reset:function(a,b){var c,d;return x?(d=x(a).clone(!0).insertBefore(a).val("")[0],b||x(a).remove()):(c=a.parentNode,d=c.insertBefore(a.cloneNode(!0),a),d.value="",b||c.removeChild(a),R(K[Y.uid(a)],function(b,c){R(b,function(b){U(a,c,b),T(d,c,b)})})),d},load:function(a,b){var c=Y.getXHR();return c?(c.open("GET",a,!0),c.overrideMimeType&&c.overrideMimeType("text/plain; charset=x-user-defined"),T(c,"progress",function(a){a.lengthComputable&&b({type:a.type,loaded:a.loaded,total:a.total},c)}),c.onreadystatechange=function(){if(4==c.readyState)if(c.onreadystatechange=null,200==c.status){a=a.split("/");var d={name:a[a.length-1],size:c.getResponseHeader("Content-Length"),type:c.getResponseHeader("Content-Type")};d.dataURL="data:"+d.type+";base64,"+Y.encode64(c.responseBody||c.responseText),b({type:"load",result:d},c)}else b({type:"error"},c)},c.send(null)):b({type:"error"}),c},encode64:function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c="",d=0;for("string"!=typeof a&&(a=String(a));d<a.length;){var e,f,g=255&a.charCodeAt(d++),h=255&a.charCodeAt(d++),i=255&a.charCodeAt(d++),j=g>>2,k=(3&g)<<4|h>>4;isNaN(h)?e=f=64:(e=(15&h)<<2|i>>6,f=isNaN(i)?64:63&i),c+=b.charAt(j)+b.charAt(k)+b.charAt(e)+b.charAt(f)}return c}};Y.addInfoReader(/^image/,function(a,b){if(!a.__dimensions){var c=a.__dimensions=Y.defer();Y.readAsImage(a,function(a){var b=a.target;c.resolve("load"==a.type?!1:"error",{width:b.width,height:b.height}),b.src=Y.EMPTY_PNG,b=null})}a.__dimensions.then(b)}),Y.event.dnd=function(a,b,c){var d,e;c||(c=b,b=Y.F),u?(T(a,"dragenter dragleave dragover",b.ff=b.ff||function(a){for(var c=k(a).types,f=c&&c.length,g=!1;f--;)if(~c[f].indexOf("File")){a[P](),e!==a.type&&(e=a.type,"dragleave"!=e&&b.call(a[O],!0,a),g=!0);break}g&&(clearTimeout(d),d=setTimeout(function(){b.call(a[O],"dragleave"!=e,a)},50))}),T(a,"drop",c.ff=c.ff||function(a){a[P](),e=0,b.call(a[O],!1,a),Y.getDropFiles(a,function(b){c.call(a[O],b,a)})})):Y.log("Drag'n'Drop -- not supported")},Y.event.dnd.off=function(a,b,c){U(a,"dragenter dragleave dragover",b.ff),U(a,"drop",c.ff)},x&&!x.fn.dnd&&(x.fn.dnd=function(a,b){return this.each(function(){Y.event.dnd(this,a,b)})},x.fn.offdnd=function(a,b){return this.each(function(){Y.event.dnd.off(this,a,b)})}),a.FileAPI=S(Y,a.FileAPI),Y.log("FileAPI: "+Y.version),Y.log("protocol: "+a.location.protocol),Y.log("doctype: ["+p.name+"] "+p.publicId+" "+p.systemId),R(o.getElementsByTagName("meta"),function(a){/x-ua-compatible/i.test(a.getAttribute("http-equiv"))&&Y.log("meta.http-equiv: "+a.getAttribute("content"))});try{Y._supportConsoleLog=!!console.log,Y._supportConsoleLogApply=!!console.log.apply}catch(Z){}Y.flashUrl||(Y.flashUrl=Y.staticPath+"FileAPI.flash.swf"),Y.flashImageUrl||(Y.flashImageUrl=Y.staticPath+"FileAPI.flash.image.swf"),Y.flashWebcamUrl||(Y.flashWebcamUrl=Y.staticPath+"FileAPI.flash.camera.swf")}(window,void 0),function(a,b,c){"use strict";function d(b){if(b instanceof d){var c=new d(b.file);return a.extend(c.matrix,b.matrix),c}return this instanceof d?(this.file=b,this.size=b.size||100,void(this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0,quality:1,filter:0})):new d(b)}var e=Math.min,f=Math.round,g=function(){return b.createElement("canvas")},h=!1,i={8:270,3:180,6:90,7:270,4:180,5:90};try{h=g().toDataURL("image/png").indexOf("data:image/png")>-1}catch(j){}d.prototype={image:!0,constructor:d,set:function(b){return a.extend(this.matrix,b),this},crop:function(a,b,d,e){return d===c&&(d=a,e=b,a=b=0),this.set({sx:a,sy:b,sw:d,sh:e||d})},resize:function(a,b,c){return/min|max/.test(b)&&(c=b,b=a),this.set({dw:a,dh:b||a,resize:c})},preview:function(a,b){return this.resize(a,b||a,"preview")},rotate:function(a){return this.set({deg:a})},filter:function(a){return this.set({filter:a})},overlay:function(a){return this.set({overlay:a})},clone:function(){return new d(this)},_load:function(b,c){var d=this;/img|video/i.test(b.nodeName)?c.call(d,null,b):a.readAsImage(b,function(a){c.call(d,"load"!=a.type,a.result)})},_apply:function(b,c){var f,h=g(),i=this.getMatrix(b),j=h.getContext("2d"),k=b.videoWidth||b.width,l=b.videoHeight||b.height,m=i.deg,n=i.dw,o=i.dh,p=k,q=l,r=i.filter,s=b,t=i.overlay,u=a.queue(function(){b.src=a.EMPTY_PNG,c(!1,h)}),v=a.renderImageToCanvas;for(m-=360*Math.floor(m/360),b._type=this.file.type;i.multipass&&e(p/n,q/o)>2;)p=p/2+.5|0,q=q/2+.5|0,f=g(),f.width=p,f.height=q,s!==b?(v(f,s,0,0,s.width,s.height,0,0,p,q),s=f):(s=f,v(s,b,i.sx,i.sy,i.sw,i.sh,0,0,p,q),i.sx=i.sy=i.sw=i.sh=0);h.width=m%180?o:n,h.height=m%180?n:o,h.type=i.type,h.quality=i.quality,j.rotate(m*Math.PI/180),v(j.canvas,s,i.sx,i.sy,i.sw||s.width,i.sh||s.height,180==m||270==m?-n:0,90==m||180==m?-o:0,n,o),n=h.width,o=h.height,t&&a.each([].concat(t),function(b){u.inc();var c=new window.Image,d=function(){var e=0|b.x,f=0|b.y,g=b.w||c.width,h=b.h||c.height,i=b.rel;e=1==i||4==i||7==i?(n-g+e)/2:2==i||5==i||8==i?n-(g+e):e,f=3==i||4==i||5==i?(o-h+f)/2:i>=6?o-(h+f):f,a.event.off(c,"error load abort",d);try{j.globalAlpha=b.opacity||1,j.drawImage(c,e,f,g,h)}catch(k){}u.next()};a.event.on(c,"error load abort",d),c.src=b.src,c.complete&&d()}),r&&(u.inc(),d.applyFilter(h,r,u.next)),u.check()},getMatrix:function(b){var c=a.extend({},this.matrix),d=c.sw=c.sw||b.videoWidth||b.naturalWidth||b.width,g=c.sh=c.sh||b.videoHeight||b.naturalHeight||b.height,h=c.dw=c.dw||d,i=c.dh=c.dh||g,j=d/g,k=h/i,l=c.resize;if("preview"==l){if(h!=d||i!=g){var m,n;k>=j?(m=d,n=m/k):(n=g,m=n*k),(m!=d||n!=g)&&(c.sx=~~((d-m)/2),c.sy=~~((g-n)/2),d=m,g=n)}}else l&&(d>h||g>i?"min"==l?(h=f(k>j?e(d,h):i*j),i=f(k>j?h/j:e(g,i))):(h=f(j>=k?e(d,h):i*j),i=f(j>=k?h/j:e(g,i))):(h=d,i=g));return c.sw=d,c.sh=g,c.dw=h,c.dh=i,c.multipass=a.multiPassResize,c},_trans:function(b){this._load(this.file,function(c,d){if(c)b(c);else try{this._apply(d,b)}catch(c){a.log("[err] FileAPI.Image.fn._apply:",c),b(c)}})},get:function(b){if(a.support.transform){var c=this,d=c.matrix;"auto"==d.deg?a.getInfo(c.file,function(a,e){d.deg=i[e&&e.exif&&e.exif.Orientation]||0,c._trans(b)}):c._trans(b)}else b("not_support_transform");return this},toData:function(a){return this.get(a)}},d.exifOrientation=i,d.transform=function(b,e,f,g){function h(h,i){var j={},k=a.queue(function(a){g(a,j)});h?k.fail():a.each(e,function(a,e){if(!k.isFail()){var g=new d(i.nodeType?i:b),h="function"==typeof a;if(h?a(i,g):a.width?g[a.preview?"preview":"resize"](a.width,a.height,a.strategy):a.maxWidth&&(i.width>a.maxWidth||i.height>a.maxHeight)&&g.resize(a.maxWidth,a.maxHeight,"max"),a.crop){var l=a.crop;g.crop(0|l.x,0|l.y,l.w||l.width,l.h||l.height)}a.rotate===c&&f&&(a.rotate="auto"),g.set({type:g.matrix.type||a.type||b.type||"image/png"}),h||g.set({deg:a.rotate,overlay:a.overlay,filter:a.filter,quality:a.quality||1}),k.inc(),g.toData(function(a,b){a?k.fail():(j[e]=b,k.next())})}})}b.width?h(!1,b):a.getInfo(b,h)},a.each(["TOP","CENTER","BOTTOM"],function(b,c){a.each(["LEFT","CENTER","RIGHT"],function(a,e){d[b+"_"+a]=3*c+e,d[a+"_"+b]=3*c+e})}),d.toCanvas=function(a){var c=b.createElement("canvas");return c.width=a.videoWidth||a.width,c.height=a.videoHeight||a.height,c.getContext("2d").drawImage(a,0,0),c},d.fromDataURL=function(b,c,d){var e=a.newImage(b);a.extend(e,c),d(e)},d.applyFilter=function(b,c,e){"function"==typeof c?c(b,e):window.Caman&&window.Caman("IMG"==b.tagName?d.toCanvas(b):b,function(){"string"==typeof c?this[c]():a.each(c,function(a,b){this[b](a)},this),this.render(e)})},a.renderImageToCanvas=function(b,c,d,e,f,g,h,i,j,k){try{return b.getContext("2d").drawImage(c,d,e,f,g,h,i,j,k)}catch(l){throw a.log("renderImageToCanvas failed"),l}},a.support.canvas=a.support.transform=h,a.Image=d}(FileAPI,document),function(a){"use strict";a(FileAPI)}(function(a){"use strict";if(window.navigator&&window.navigator.platform&&/iP(hone|od|ad)/.test(window.navigator.platform)){var b=a.renderImageToCanvas;a.detectSubsampling=function(a){var b,c;return a.width*a.height>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-a.width+1,0),0===c.getImageData(0,0,1,1).data[3]):!1},a.detectVerticalSquash=function(a,b){var c,d,e,f,g,h=a.naturalHeight||a.height,i=document.createElement("canvas"),j=i.getContext("2d");for(b&&(h/=2),i.width=1,i.height=h,j.drawImage(a,0,0),c=j.getImageData(0,0,1,h).data,d=0,e=h,f=h;f>d;)g=c[4*(f-1)+3],0===g?e=f:d=f,f=e+d>>1;return f/h||1},a.renderImageToCanvas=function(c,d,e,f,g,h,i,j,k,l){if("image/jpeg"===d._type){var m,n,o,p,q=c.getContext("2d"),r=document.createElement("canvas"),s=1024,t=r.getContext("2d");if(r.width=s,r.height=s,q.save(),m=a.detectSubsampling(d),m&&(e/=2,f/=2,g/=2,h/=2),n=a.detectVerticalSquash(d,m),m||1!==n){for(f*=n,k=Math.ceil(s*k/g),l=Math.ceil(s*l/h/n),j=0,p=0;h>p;){for(i=0,o=0;g>o;)t.clearRect(0,0,s,s),t.drawImage(d,e,f,g,h,-o,-p,g,h),q.drawImage(r,0,0,s,s,i,j,k,l),o+=s,i+=k;p+=s,j+=l}return q.restore(),c}}return b(c,d,e,f,g,h,i,j,k,l)}}}),function(a,b){"use strict";function c(b,c,d){var e=b.blob,f=b.file;if(f){if(!e.toDataURL)return void a.readAsBinaryString(e,function(a){"load"==a.type&&c(b,a.result)});var g={"image/jpeg":".jpe?g","image/png":".png"},h=g[b.type]?b.type:"image/png",i=g[h]||".png",j=e.quality||1;f.match(new RegExp(i+"$","i"))||(f+=i.replace("?","")),b.file=f,b.type=h,!d&&e.toBlob?e.toBlob(function(a){c(b,a)},h,j):c(b,a.toBinaryString(e.toDataURL(h,j)))}else c(b,e)}var d=b.document,e=b.FormData,f=function(){this.items=[]},g=b.encodeURIComponent;f.prototype={append:function(a,b,c,d){this.items.push({name:a,blob:b&&b.blob||(void 0==b?"":b),file:b&&(c||b.name),type:b&&(d||b.type)})},each:function(a){for(var b=0,c=this.items.length;c>b;b++)a.call(this,this.items[b])},toData:function(b,c){c._chunked=a.support.chunked&&c.chunkSize>0&&1==a.filter(this.items,function(a){return a.file}).length,a.support.html5?a.formData&&!this.multipart&&e?c._chunked?(a.log("FileAPI.Form.toPlainData"),this.toPlainData(b)):(a.log("FileAPI.Form.toFormData"),this.toFormData(b)):(a.log("FileAPI.Form.toMultipartData"),this.toMultipartData(b)):(a.log("FileAPI.Form.toHtmlData"),this.toHtmlData(b))},_to:function(b,c,d,e){var f=a.queue(function(){c(b)});this.each(function(a){d(a,b,f,e)}),f.check()},toHtmlData:function(b){this._to(d.createDocumentFragment(),b,function(b,c){var e,f=b.blob;b.file?(a.reset(f,!0),f.name=b.name,f.disabled=!1,c.appendChild(f)):(e=d.createElement("input"),e.name=b.name,e.type="hidden",e.value=f,c.appendChild(e))})},toPlainData:function(a){this._to({},a,function(a,b,d){a.file&&(b.type=a.file),a.blob.toBlob?(d.inc(),c(a,function(a,c){b.name=a.name,b.file=c,b.size=c.length,b.type=a.type,d.next()})):a.file?(b.name=a.blob.name,b.file=a.blob,b.size=a.blob.size,b.type=a.type):(b.params||(b.params=[]),b.params.push(g(a.name)+"="+g(a.blob))),b.start=-1,b.end=b.file&&b.file.FileAPIReadPosition||-1,b.retry=0})},toFormData:function(a){this._to(new e,a,function(a,b,d){a.blob&&a.blob.toBlob?(d.inc(),c(a,function(a,c){b.append(a.name,c,a.file),d.next()})):a.file?b.append(a.name,a.blob,a.file):b.append(a.name,a.blob),a.file&&b.append("_"+a.name,a.file)})},toMultipartData:function(b){this._to([],b,function(a,b,d,e){d.inc(),c(a,function(a,c){b.push("--_"+e+('\r\nContent-Disposition: form-data; name="'+a.name+'"'+(a.file?'; filename="'+g(a.file)+'"':"")+(a.file?"\r\nContent-Type: "+(a.type||"application/octet-stream"):"")+"\r\n\r\n"+(a.file?c:g(c))+"\r\n")),d.next()},!0)},a.expando)}},a.Form=f}(FileAPI,window),function(a,b){"use strict";var c=function(){},d=a.document,e=function(a){this.uid=b.uid(),this.xhr={abort:c,getResponseHeader:c,getAllResponseHeaders:c},this.options=a},f={"":1,XML:1,Text:1,Body:1};e.prototype={status:0,statusText:"",constructor:e,getResponseHeader:function(a){return this.xhr.getResponseHeader(a)},getAllResponseHeaders:function(){return this.xhr.getAllResponseHeaders()||{}},end:function(d,e){var f=this,g=f.options;f.end=f.abort=c,f.status=d,e&&(f.statusText=e),b.log("xhr.end:",d,e),g.complete(200==d||201==d?!1:f.statusText||"unknown",f),f.xhr&&f.xhr.node&&setTimeout(function(){var b=f.xhr.node;try{b.parentNode.removeChild(b)}catch(c){}try{delete a[f.uid]}catch(c){}a[f.uid]=f.xhr.node=null},9)},abort:function(){this.end(0,"abort"),this.xhr&&(this.xhr.aborted=!0,this.xhr.abort())},send:function(a){var b=this,c=this.options;a.toData(function(a){c.upload(c,b),b._send.call(b,c,a)},c)},_send:function(c,e){var g,h=this,i=h.uid,j=h.uid+"Load",k=c.url;if(b.log("XHR._send:",e),c.cache||(k+=(~k.indexOf("?")?"&":"?")+b.uid()),e.nodeName){var l=c.jsonp;k=k.replace(/([a-z]+)=(\?)/i,"$1="+i),c.upload(c,h);var m=function(a){if(~k.indexOf(a.origin))try{var c=b.parseJSON(a.data);c.id==i&&n(c.status,c.statusText,c.response)}catch(d){n(0,d.message)}},n=a[i]=function(c,d,e){h.readyState=4,h.responseText=e,h.end(c,d),b.event.off(a,"message",m),a[i]=g=p=a[j]=null};h.xhr.abort=function(){try{p.stop?p.stop():p.contentWindow.stop?p.contentWindow.stop():p.contentWindow.document.execCommand("Stop")}catch(a){}n(0,"abort")},b.event.on(a,"message",m),a[j]=function(){try{var a=p.contentWindow,c=a.document,d=a.result||b.parseJSON(c.body.innerHTML);n(d.status,d.statusText,d.response)}catch(e){b.log("[transport.onload]",e)}},g=d.createElement("div"),g.innerHTML='<form target="'+i+'" action="'+k+'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;"><iframe name="'+i+'" src="javascript:false;" onload="'+j+'()"></iframe>'+(l&&c.url.indexOf("=?")<0?'<input value="'+i+'" name="'+l+'" type="hidden"/>':"")+"</form>";var o=g.getElementsByTagName("form")[0],p=g.getElementsByTagName("iframe")[0];o.appendChild(e),b.log(o.parentNode.innerHTML),d.body.appendChild(g),h.xhr.node=g,h.readyState=2,o.submit(),o=null}else{if(k=k.replace(/([a-z]+)=(\?)&?/i,""),this.xhr&&this.xhr.aborted)return void b.log("Error: already aborted");if(g=h.xhr=b.getXHR(),e.params&&(k+=(k.indexOf("?")<0?"?":"&")+e.params.join("&")),g.open("POST",k,!0),b.withCredentials&&(g.withCredentials="true"),c.headers&&c.headers["X-Requested-With"]||g.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.each(c.headers,function(a,b){g.setRequestHeader(b,a)}),c._chunked){g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){e.retry||c.progress({type:a.type,total:e.size,loaded:e.start+a.loaded,totalSize:e.size},h,c)},100),!1),g.onreadystatechange=function(){var a=parseInt(g.getResponseHeader("X-Last-Known-Byte"),10);if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){try{for(var d in f)h["response"+d]=g["response"+d]}catch(i){}if(g.onreadystatechange=null,!g.status||g.status-201>0)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status||416==g.status)&&++e.retry<=c.chunkUploadRetry){var j=g.status?0:b.chunkNetworkDownRetryTimeout;c.pause(e.file,c),b.log("X-Last-Known-Byte: "+a),a?e.end=a:(e.end=e.start-1,416==g.status&&(e.end=e.end-c.chunkSize)),setTimeout(function(){h._send(c,e)},j)}else h.end(g.status);else e.retry=0,e.end==e.size-1?h.end(g.status):(b.log("X-Last-Known-Byte: "+a),a&&(e.end=a),e.file.FileAPIReadPosition=e.end,setTimeout(function(){h._send(c,e)},0));g=null}},e.start=e.end+1,e.end=Math.max(Math.min(e.start+c.chunkSize,e.size)-1,e.start);var q=e.file,r=(q.slice||q.mozSlice||q.webkitSlice).call(q,e.start,e.end+1);e.size&&!r.size?setTimeout(function(){h.end(-1)}):(g.setRequestHeader("Content-Range","bytes "+e.start+"-"+e.end+"/"+e.size),g.setRequestHeader("Content-Disposition","attachment; filename="+encodeURIComponent(e.name)),g.setRequestHeader("Content-Type",e.type||"application/octet-stream"),g.send(r)),q=r=null}else if(g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){c.progress(a,h,c)},100),!1),g.onreadystatechange=function(){if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var a in f)h["response"+a]=g["response"+a];if(g.onreadystatechange=null,!g.status||g.status>201)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status)&&(c.retry||0)<c.uploadRetry){c.retry=(c.retry||0)+1;var d=b.networkDownRetryTimeout;c.pause(c.file,c),setTimeout(function(){h._send(c,e)},d)}else h.end(g.status);else h.end(g.status);g=null}},b.isArray(e)){g.setRequestHeader("Content-Type","multipart/form-data; boundary=_"+b.expando);var s=e.join("")+"--_"+b.expando+"--";if(g.sendAsBinary)g.sendAsBinary(s);else{var t=Array.prototype.map.call(s,function(a){return 255&a.charCodeAt(0)});g.send(new Uint8Array(t).buffer)}}else g.send(e)}}},b.XHR=e}(window,FileAPI),function(a,b){"use strict";function c(a){return a>=0?a+"px":a}function d(a){var b,c=f.createElement("canvas"),d=!1;try{b=c.getContext("2d"),b.drawImage(a,0,0,1,1),d=255!=b.getImageData(0,0,1,1).data[4]}catch(e){}return d}var e=a.URL||a.webkitURL,f=a.document,g=a.navigator,h=g.getUserMedia||g.webkitGetUserMedia||g.mozGetUserMedia||g.msGetUserMedia,i=!!h;b.support.media=i;var j=function(a){this.video=a};j.prototype={isActive:function(){return!!this._active},start:function(a){var b,c,f=this,i=f.video,j=function(d){f._active=!d,clearTimeout(c),clearTimeout(b),a&&a(d,f)};h.call(g,{video:!0},function(a){f.stream=a,i.src=e.createObjectURL(a),b=setInterval(function(){d(i)&&j(null)},1e3),c=setTimeout(function(){j("timeout")},5e3),i.play()},j)},stop:function(){try{this._active=!1,this.video.pause(),this.stream.stop()}catch(a){}},shot:function(){return new k(this.video)}},j.get=function(a){return new j(a.firstChild)},j.publish=function(d,e,g){"function"==typeof e&&(g=e,e={}),e=b.extend({},{width:"100%",height:"100%",start:!0},e),d.jquery&&(d=d[0]);var h=function(a){if(a)g(a);else{var b=j.get(d);e.start?b.start(g):g(null,b)}};if(d.style.width=c(e.width),d.style.height=c(e.height),b.html5&&i){var k=f.createElement("video");k.style.width=c(e.width),k.style.height=c(e.height),a.jQuery?jQuery(d).empty():d.innerHTML="",d.appendChild(k),h()}else j.fallback(d,e,h)},j.fallback=function(a,b,c){c("not_support_camera")};var k=function(a){var c=a.nodeName?b.Image.toCanvas(a):a,d=b.Image(c);return d.type="image/png",d.width=c.width,d.height=c.height,d.size=c.width*c.height*4,d};j.Shot=k,b.Camera=j}(window,FileAPI),function(a,b,c){"use strict";var d=a.document,e=a.location,f=a.navigator,g=c.each;c.support.flash=function(){var b=f.mimeTypes,d=!1;if(f.plugins&&"object"==typeof f.plugins["Shockwave Flash"])d=f.plugins["Shockwave Flash"].description&&!(b&&b["application/x-shockwave-flash"]&&!b["application/x-shockwave-flash"].enabledPlugin);
5 else try{d=!(!a.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(g){c.log("Flash -- does not supported.")}return d&&/^file:/i.test(e)&&c.log("[warn] Flash does not work on `file:` protocol."),d}(),c.support.flash&&(0||!c.html5||!c.support.html5||c.cors&&!c.support.cors||c.media&&!c.support.media)&&function(){function h(a){return('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(a.width||"100%")+'" height="'+(a.height||"100%")+'"><param name="movie" value="#src#" /><param name="flashvars" value="#flashvars#" /><param name="swliveconnect" value="true" /><param name="allowscriptaccess" value="always" /><param name="allownetworking" value="all" /><param name="menu" value="false" /><param name="wmode" value="#wmode#" /><embed flashvars="#flashvars#" swliveconnect="true" allownetworking="all" allowscriptaccess="always" name="#id#" src="#src#" width="'+(a.width||"100%")+'" height="'+(a.height||"100%")+'" menu="false" wmode="transparent" type="application/x-shockwave-flash"></embed></object>').replace(/#(\w+)#/gi,function(b,c){return a[c]})}function i(a,b){if(a&&a.style){var c,d;for(c in b){d=b[c],"number"==typeof d&&(d+="px");try{a.style[c]=d}catch(e){}}}}function j(a,b){g(b,function(b,c){var d=a[c];a[c]=function(){return this.parent=d,b.apply(this,arguments)}})}function k(a){return a&&!a.flashId}function l(a){var b=a.wid=c.uid();return v._fn[b]=a,"FileAPI.Flash._fn."+b}function m(a){try{v._fn[a.wid]=null,delete v._fn[a.wid]}catch(b){}}function n(a,b){if(!u.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0)){var c=e.pathname;c=c.substr(0,c.lastIndexOf("/")),a=(c+"/"+a).replace("/./","/")}"//"!=a.substr(0,2)&&(a="//"+e.host+a),u.test(a)||(a=e.protocol+a)}return b&&(a+=(/\?/.test(a)?"&":"?")+b),a}function o(a,b,e){function f(){try{var a=v.get(j);a.setImage(b)}catch(d){c.log('[err] FlashAPI.Preview.setImage -- can not set "base64":',d)}}var g,j=c.uid(),k=d.createElement("div"),o=10;for(g in a)k.setAttribute(g,a[g]),k[g]=a[g];i(k,a),a.width="100%",a.height="100%",k.innerHTML=h(c.extend({id:j,src:n(c.flashImageUrl,"r="+c.uid()),wmode:"opaque",flashvars:"scale="+a.scale+"&callback="+l(function p(){return m(p),--o>0&&f(),!0})},a)),e(!1,k),k=null}function p(a){return{id:a.id,name:a.name,matrix:a.matrix,flashId:a.flashId}}function q(a){function b(a){var b,c;if(b=c=0,a.offsetParent)do b+=a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent);return{left:b,top:c}}a.getBoundingClientRect(),d.body,(a&&a.ownerDocument).documentElement;return{top:b(a).top,left:b(a).left,width:a.offsetWidth,height:a.offsetHeight}}var r=c.uid(),s=0,t={},u=/^https?:/i,v={_fn:{},publish:function(a,b,d){d=d||{},a.innerHTML=h({id:b,src:n(c.flashUrl,"r="+c.version),wmode:d.camera?"":"transparent",flashvars:"callback="+(d.onEvent||"FileAPI.Flash.onEvent")+"&flashId="+b+"&storeKey="+f.userAgent.match(/\d/gi).join("")+"_"+c.version+(v.isReady||(c.pingUrl?"&ping="+c.pingUrl:""))+"&timeout="+c.flashAbortTimeout+(d.camera?"&useCamera="+n(c.flashWebcamUrl):"")+"&debug="+(c.debug?"1":"")},d)},init:function(){var a=d.body&&d.body.firstChild;if(a)do if(1==a.nodeType){c.log("FlashAPI.state: awaiting");var b=d.createElement("div");return b.id="_"+r,i(b,{top:1,right:1,width:5,height:5,position:"absolute",zIndex:1e6+""}),a.parentNode.insertBefore(b,a),void v.publish(b,r)}while(a=a.nextSibling);10>s&&setTimeout(v.init,50*++s)},ready:function(){c.log("FlashAPI.state: ready"),v.ready=c.F,v.isReady=!0,v.patch(),v.patchCamera&&v.patchCamera(),c.event.on(d,"mouseover",v.mouseover),c.event.on(d,"click",function(a){v.mouseover(a)&&(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getEl:function(){return d.getElementById("_"+r)},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==d.body)},disableMouseover:!1,mouseover:function(a){if(!v.disableMouseover){var b=c.event.fix(a).target;if(/input/i.test(b.nodeName)&&"file"==b.type&&!b.disabled){var e=b.getAttribute(r),f=v.getWrapper(b);if(c.multiFlash){if("i"==e||"r"==e)return!1;if("p"!=e){b.setAttribute(r,"i");var g=d.createElement("div");if(!f)return void c.log("[err] FlashAPI.mouseover: js-fileapi-wrapper not found");i(g,{top:0,left:0,width:b.offsetWidth,height:b.offsetHeight,zIndex:1e6+"",position:"absolute"}),f.appendChild(g),v.publish(g,c.uid()),b.setAttribute(r,"p")}return!0}if(f){var h=q(f);i(v.getEl(),h),v.curInp=b}}else/object|embed/i.test(b.nodeName)||i(v.getEl(),{top:1,left:1,width:5,height:5})}},onEvent:function(a){var b=a.type;if("ready"==b){try{v.getInput(a.flashId).setAttribute(r,"r")}catch(d){}return v.ready(),setTimeout(function(){v.mouseenter(a)},50),!0}"ping"===b?c.log("(flash -> js).ping:",[a.status,a.savedStatus],a.error):"log"===b?c.log("(flash -> js).log:",a.target):b in v&&setTimeout(function(){c.log("FlashAPI.event."+a.type+":",a),v[b](a)},1)},mouseDown:function(){v.disableMouseover=!0},cancel:function(){v.disableMouseover=!1},mouseenter:function(a){var b=v.getInput(a.flashId);if(b){v.cmd(a,"multiple",null!=b.getAttribute("multiple"));var d=[],e={};g((b.getAttribute("accept")||"").split(/,\s*/),function(a){c.accept[a]&&g(c.accept[a].split(" "),function(a){e[a]=1})}),g(e,function(a,b){d.push(b)}),v.cmd(a,"accept",d.length?d.join(",")+","+d.join(",").toUpperCase():"*")}},get:function(b){return d[b]||a[b]||d.embeds[b]},getInput:function(a){if(!c.multiFlash)return v.curInp;try{var b=v.getWrapper(v.get(a));if(b)return b.getElementsByTagName("input")[0]}catch(d){c.log('[err] Can not find "input" by flashId:',a,d)}},select:function(a){try{var e,f=v.getInput(a.flashId),h=c.uid(f),i=a.target.files;g(i,function(a){c.checkFileObj(a)}),t[h]=i,d.createEvent?(e=d.createEvent("Event"),e.files=i,e.initEvent("change",!0,!0),f.dispatchEvent(e)):b?b(f).trigger({type:"change",files:i}):(e=d.createEventObject(),e.files=i,f.fireEvent("onchange",e))}finally{v.disableMouseover=!1}},interval:null,cmd:function(a,b,c,d){v.uploadInProgress&&v.readInProgress?setTimeout(function(){v.cmd(a,b,c,d)},100):this.cmdFn(a,b,c,d)},cmdFn:function(a,b,d,e){try{return c.log("(js -> flash)."+b+":",d),v.get(a.flashId||a).cmd(b,d)}catch(f){c.log("(js -> flash).onError:",f),e||setTimeout(function(){v.cmd(a,b,d,!0)},50)}},patch:function(){c.flashEngine=!0,j(c,{readAsDataURL:function(a,b){k(a)?this.parent.apply(this,arguments):(c.log("FlashAPI.readAsBase64"),v.readInProgress=!0,v.cmd(a,"readAsBase64",{id:a.id,callback:l(function d(e,f){v.readInProgress=!1,m(d),c.log("FlashAPI.readAsBase64:",e),b({type:e?"error":"load",error:e,result:"data:"+a.type+";base64,"+f})})}))},readAsText:function(b,d,e){e?c.log("[warn] FlashAPI.readAsText not supported `encoding` param"):e=d,c.readAsDataURL(b,function(b){if("load"==b.type)try{b.result=a.atob(b.result.split(";base64,")[1])}catch(c){b.type="error",b.error=c.toString()}e(b)})},getFiles:function(a,b,d){if(d)return c.filterFiles(c.getFiles(a),b,d),null;var e=c.isArray(a)?a:t[c.uid(a.target||a.srcElement||a)];return e?(b&&(b=c.getFilesFilter(b),e=c.filter(e,function(a){return b.test(a.name)})),e):this.parent.apply(this,arguments)},getInfo:function(a,b){if(k(a))this.parent.apply(this,arguments);else if(a.isShot)b(null,a.info={width:a.width,height:a.height});else{if(!a.__info){var d=a.__info=c.defer();d.resolve(null,a.info=null)}a.__info.then(b)}}}),c.support.transform=!0,c.Image&&j(c.Image.prototype,{get:function(a,b){return this.set({scaleMode:b||"noScale"}),this.parent(a)},_load:function(a,b){if(c.log("FlashAPI.Image._load:",a),k(a))this.parent.apply(this,arguments);else{var d=this;c.getInfo(a,function(c){b.call(d,c,a)})}},_apply:function(a,b){if(c.log("FlashAPI.Image._apply:",a),k(a))this.parent.apply(this,arguments);else{var d=this.getMatrix(a.info),e=b;v.cmd(a,"imageTransform",{id:a.id,matrix:d,callback:l(function f(g,h){c.log("FlashAPI.Image._apply.callback:",g),m(f),g?e(g):c.support.html5||c.support.dataURI&&!(h.length>3e4)?(d.filter&&(e=function(a,e){a?b(a):c.Image.applyFilter(e,d.filter,function(){b(a,this.canvas)})}),c.newImage("data:"+a.type+";base64,"+h,e)):o({width:d.deg%180?d.dh:d.dw,height:d.deg%180?d.dw:d.dh,scale:d.scaleMode},h,e)})})}},toData:function(a){var b=this.file,d=b.info,e=this.getMatrix(d);c.log("FlashAPI.Image.toData"),k(b)?this.parent.apply(this,arguments):("auto"==e.deg&&(e.deg=c.Image.exifOrientation[d&&d.exif&&d.exif.Orientation]||0),a.call(this,!b.info,{id:b.id,flashId:b.flashId,name:b.name,type:b.type,matrix:e}))}}),c.Image&&j(c.Image,{fromDataURL:function(a,b,d){!c.support.dataURI||a.length>3e4?o(c.extend({scale:"exactFit"},b),a.replace(/^data:[^,]+,/,""),function(a,b){d(b)}):this.parent(a,b,d)}}),j(c.Form.prototype,{toData:function(a){for(var b=this.items,d=b.length;d--;)if(b[d].file&&k(b[d].blob))return this.parent.apply(this,arguments);c.log("FlashAPI.Form.toData"),a(b)}}),j(c.XHR.prototype,{_send:function(a,b){if(b.nodeName||b.append&&c.support.html5||c.isArray(b)&&"string"==typeof b[0])return this.parent.apply(this,arguments);var d,e,f={},h={},i=this;if(g(b,function(a){a.file?(h[a.name]=a=p(a.blob),e=a.id,d=a.flashId):f[a.name]=a.blob}),e||(d=r),!d)return c.log("[err] FlashAPI._send: flashId -- undefined"),this.parent.apply(this,arguments);c.log("FlashAPI.XHR._send: "+d+" -> "+e),i.xhr={headers:{},abort:function(){v.uploadInProgress=!1,v.cmd(d,"abort",{id:e})},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var j=c.queue(function(){v.uploadInProgress=!0,v.cmd(d,"upload",{url:n(a.url.replace(/([a-z]+)=(\?)&?/i,"")),data:f,files:e?h:null,headers:a.headers||{},callback:l(function b(d){var e=d.type,f=d.result;c.log("FlashAPI.upload."+e),"progress"==e?(d.loaded=Math.min(d.loaded,d.total),d.lengthComputable=!0,a.progress(d)):"complete"==e?(v.uploadInProgress=!1,m(b),"string"==typeof f&&(i.responseText=f.replace(/%22/g,'"').replace(/%5c/g,"\\").replace(/%26/g,"&").replace(/%25/g,"%")),i.end(d.status||200)):("abort"==e||"error"==e)&&(v.uploadInProgress=!1,i.end(d.status||0,d.message),m(b))})})});g(h,function(a){j.inc(),c.getInfo(a,j.next)}),j.check()}})}};c.Flash=v,c.newImage("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==",function(a,b){c.support.dataURI=!(1!=b.width||1!=b.height),v.init()})}()}(window,window.jQuery,FileAPI),function(a,b,c){"use strict";var d=c.each,e=[];c.support.flash&&c.media&&!c.support.media&&!function(){function a(a){var b=a.wid=c.uid();return c.Flash._fn[b]=a,"FileAPI.Flash._fn."+b}function b(a){try{c.Flash._fn[a.wid]=null,delete c.Flash._fn[a.wid]}catch(b){}}var f=c.Flash;c.extend(c.Flash,{patchCamera:function(){c.Camera.fallback=function(d,e,g){var h=c.uid();c.log("FlashAPI.Camera.publish: "+h),f.publish(d,h,c.extend(e,{camera:!0,onEvent:a(function i(a){"camera"===a.type&&(b(i),a.error?(c.log("FlashAPI.Camera.publish.error: "+a.error),g(a.error)):(c.log("FlashAPI.Camera.publish.success: "+h),g(null)))})}))},d(e,function(a){c.Camera.fallback.apply(c.Camera,a)}),e=[],c.extend(c.Camera.prototype,{_id:function(){return this.video.id},start:function(d){var e=this;f.cmd(this._id(),"camera.on",{callback:a(function g(a){b(g),a.error?(c.log("FlashAPI.camera.on.error: "+a.error),d(a.error,e)):(c.log("FlashAPI.camera.on.success: "+e._id()),e._active=!0,d(null,e))})})},stop:function(){this._active=!1,f.cmd(this._id(),"camera.off")},shot:function(){c.log("FlashAPI.Camera.shot:",this._id());var a=c.Flash.cmd(this._id(),"shot",{});return a.type="image/png",a.flashId=this._id(),a.isShot=!0,new c.Camera.Shot(a)}})}}),c.Camera.fallback=function(){e.push(arguments)}}()}(window,window.jQuery,FileAPI),"function"==typeof define&&define.amd&&define("FileAPI",[],function(){return FileAPI});
00 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
1 * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
22 * progress, resize, thumbnail, preview, validation and CORS
33 * FileAPI Flash shim for old browsers not supporting FormData
44 * @author Danial <danial.farid@gmail.com>
5 * @version 12.0.4
5 * @version 12.2.13
66 */
77
88 (function () {
423423 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
424424 * progress, resize, thumbnail, preview, validation and CORS
425425 * @author Danial <danial.farid@gmail.com>
426 * @version 12.0.4
426 * @version 12.2.13
427427 */
428428
429429 if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
444444
445445 var ngFileUpload = angular.module('ngFileUpload', []);
446446
447 ngFileUpload.version = '12.0.4';
447 ngFileUpload.version = '12.2.13';
448448
449449 ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
450450 var upload = this;
511511 function uploadWithAngular() {
512512 $http(config).then(function (r) {
513513 if (resumeSupported && config._chunkSize && !config._finished && config._file) {
514 var fileSize = config._file && config._file.size || 0;
514515 notifyProgress({
515 loaded: config._end,
516 total: config._file && config._file.size,
517 config: config, type: 'progress'
516 loaded: Math.min(config._end, fileSize),
517 total: fileSize,
518 config: config,
519 type: 'progress'
518520 }
519521 );
520522 upload.upload(config, true);
553555 } else if (config.resumeSize) {
554556 config.resumeSize().then(function (size) {
555557 config._start = size;
558 if (config._chunkSize) {
559 config._end = config._start + config._chunkSize;
560 }
556561 uploadWithAngular();
557562 }, function (e) {
558563 throw e;
606611 };
607612
608613 upload.promisesCount++;
609 promise['finally'](function () {
610 upload.promisesCount--;
611 });
614 if (promise['finally'] && promise['finally'] instanceof Function) {
615 promise['finally'](function () {
616 upload.promisesCount--;
617 });
618 }
612619 return promise;
613620 }
614621
792799 var arrayBufferView = new Uint8Array(resp.data);
793800 var type = resp.headers('content-type') || 'image/WebP';
794801 var blob = new window.Blob([arrayBufferView], {type: type});
802 var matches = url.match(/.*\/(.+?)(\?.*)?$/);
803 if (matches.length > 1) {
804 blob.name = matches[1];
805 }
795806 defer.resolve(blob);
796 //var split = type.split('[/;]');
797 //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
798807 }, function (e) {
799808 defer.reject(e);
800809 });
842851 };
843852
844853 upload.shouldUpdateOn = function (type, attr, scope) {
845 var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
854 var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);
846855 if (modelOptions && modelOptions.updateOn) {
847856 return modelOptions.updateOn.split(' ').indexOf(type) > -1;
848857 }
892901 return $q.all(promises);
893902 }
894903
895 function resize(files, attr, scope) {
904 function resizeFile(files, attr, scope, ngModel) {
896905 var resizeVal = upload.attrGetter('ngfResize', attr, scope);
897906 if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
898907 if (resizeVal instanceof Function) {
899908 var defer = $q.defer();
900 resizeVal(files).then(function (p) {
901 resizeWithParams(p, files, attr, scope).then(function (r) {
909 return resizeVal(files).then(function (p) {
910 resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {
902911 defer.resolve(r);
903912 }, function (e) {
904913 defer.reject(e);
907916 defer.reject(e);
908917 });
909918 } else {
910 return resizeWithParams(resizeVal, files, attr, scope);
919 return resizeWithParams(resizeVal, files, attr, scope, ngModel);
911920 }
912921 }
913922
914 function resizeWithParams(param, files, attr, scope) {
923 function resizeWithParams(params, files, attr, scope, ngModel) {
915924 var promises = [upload.emptyPromise()];
916925
917926 function handleFile(f, i) {
918927 if (f.type.indexOf('image') === 0) {
919 if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
920 var promise = upload.resize(f, param.width, param.height, param.quality,
921 param.type, param.ratio, param.centerCrop, function (width, height) {
922 return upload.attrGetter('ngfResizeIf', attr, scope,
923 {$width: width, $height: height, $file: f});
924 }, param.restoreExif !== false);
928 if (params.pattern && !upload.validatePattern(f, params.pattern)) return;
929 params.resizeIf = function (width, height) {
930 return upload.attrGetter('ngfResizeIf', attr, scope,
931 {$width: width, $height: height, $file: f});
932 };
933 var promise = upload.resize(f, params);
925934 promises.push(promise);
926935 promise.then(function (resizedFile) {
927936 files.splice(i, 1, resizedFile);
928937 }, function (e) {
929938 f.$error = 'resize';
939 (f.$errorMessages = (f.$errorMessages || {})).resize = true;
930940 f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
941 ngModel.$ngfValidations.push({name: 'resize', valid: false});
942 upload.applyModelValidation(ngModel, files);
931943 });
932944 }
933945 }
10141026 return angular.isArray(v) ? v : [v];
10151027 }
10161028
1017 function separateInvalids() {
1018 valids = [];
1019 invalids = [];
1020 angular.forEach(allNewFiles, function (file) {
1021 if (file.$error) {
1022 invalids.push(file);
1023 } else {
1024 valids.push(file);
1025 }
1026 });
1027 }
1028
10291029 function resizeAndUpdate() {
10301030 function updateModel() {
10311031 $timeout(function () {
10351035 }, options && options.debounce ? options.debounce.change || options.debounce : 0);
10361036 }
10371037
1038 resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () {
1038 var resizingFiles = validateAfterResize ? allNewFiles : valids;
1039 resizeFile(resizingFiles, attr, scope, ngModel).then(function () {
10391040 if (validateAfterResize) {
1040 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
1041 separateInvalids();
1042 updateModel();
1043 });
1041 upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
1042 .then(function (validationResult) {
1043 valids = validationResult.validsFiles;
1044 invalids = validationResult.invalidsFiles;
1045 updateModel();
1046 });
10441047 } else {
10451048 updateModel();
10461049 }
1047 }, function (e) {
1048 throw 'Could not resize files ' + e;
1050 }, function () {
1051 for (var i = 0; i < resizingFiles.length; i++) {
1052 var f = resizingFiles[i];
1053 if (f.$error === 'resize') {
1054 var index = valids.indexOf(f);
1055 if (index > -1) {
1056 valids.splice(index, 1);
1057 invalids.push(f);
1058 }
1059 updateModel();
1060 }
1061 }
10491062 });
10501063 }
10511064
10751088
10761089 var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
10771090
1078 var options = upload.attrGetter('ngModelOptions', attr, scope);
1079 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
1091 var options = upload.attrGetter('ngfModelOptions', attr, scope);
1092 upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
1093 .then(function (validationResult) {
10801094 if (noDelay) {
10811095 update(allNewFiles, [], files, dupFiles, isSingleModel);
10821096 } else {
10831097 if ((!options || !options.allowInvalid) && !validateAfterResize) {
1084 separateInvalids();
1098 valids = validationResult.validFiles;
1099 invalids = validationResult.invalidFiles;
10851100 } else {
10861101 valids = allNewFiles;
10871102 }
11181133 /** @namespace attr.ngfSelect */
11191134 /** @namespace attr.ngfChange */
11201135 /** @namespace attr.ngModel */
1121 /** @namespace attr.ngModelOptions */
1136 /** @namespace attr.ngfModelOptions */
11221137 /** @namespace attr.ngfMultiple */
11231138 /** @namespace attr.ngfCapture */
11241139 /** @namespace attr.ngfValidate */
11381153 function changeFn(evt) {
11391154 if (upload.shouldUpdateOn('change', attr, scope)) {
11401155 var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
1156 /* Handle duplicate call in IE11 */
1157 if (!fileList) return;
11411158 for (var i = 0; i < fileList.length; i++) {
11421159 files.push(fileList[i]);
11431160 }
11491166 upload.registerModelChangeValidator(ngModel, attr, scope);
11501167
11511168 var unwatches = [];
1152 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
1153 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
1169 if (attrGetter('ngfMultiple')) {
1170 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
1171 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
1172 }));
1173 }
1174 if (attrGetter('ngfCapture')) {
1175 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
1176 fileElem.attr('capture', attrGetter('ngfCapture', scope));
1177 }));
1178 }
1179 if (attrGetter('ngfAccept')) {
1180 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
1181 fileElem.attr('accept', attrGetter('ngfAccept', scope));
1182 }));
1183 }
1184 unwatches.push(attr.$observe('accept', function () {
1185 fileElem.attr('accept', attrGetter('accept'));
11541186 }));
1155 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
1156 fileElem.attr('capture', attrGetter('ngfCapture', scope));
1157 }));
1158 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
1159 fileElem.attr('accept', attrGetter('ngfAccept', scope));
1160 }));
1161 attr.$observe('accept', function () {
1162 fileElem.attr('accept', attrGetter('accept'));
1163 });
1164 unwatches.push(function () {
1165 if (attr.$$observers) delete attr.$$observers.accept;
1166 });
1167 function bindAttrToFileInput(fileElem) {
1168 if (elem !== fileElem) {
1169 for (var i = 0; i < elem[0].attributes.length; i++) {
1170 var attribute = elem[0].attributes[i];
1171 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
1172 if (attribute.value == null || attribute.value === '') {
1173 if (attribute.name === 'required') attribute.value = 'required';
1174 if (attribute.name === 'multiple') attribute.value = 'multiple';
1175 }
1176 fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
1187 function bindAttrToFileInput(fileElem, label) {
1188 function updateId(val) {
1189 fileElem.attr('id', 'ngf-' + val);
1190 label.attr('id', 'ngf-label-' + val);
1191 }
1192
1193 for (var i = 0; i < elem[0].attributes.length; i++) {
1194 var attribute = elem[0].attributes[i];
1195 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
1196 if (attribute.name === 'id') {
1197 updateId(attribute.value);
1198 unwatches.push(attr.$observe('id', updateId));
1199 } else {
1200 fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||
1201 attribute.name === 'multiple')) ? attribute.name : attribute.value);
11771202 }
11781203 }
11791204 }
11851210 }
11861211
11871212 var fileElem = angular.element('<input type="file">');
1188
1189 bindAttrToFileInput(fileElem);
11901213
11911214 var label = angular.element('<label>upload</label>');
11921215 label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
11931216 .css('width', '0px').css('height', '0px').css('border', 'none')
11941217 .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
1218 bindAttrToFileInput(fileElem, label);
1219
11951220 generatedElems.push({el: elem, ref: label});
11961221
11971222 document.body.appendChild(label.append(fileElem)[0]);
11981223
11991224 return fileElem;
12001225 }
1201
1202 var initialTouchStartY = 0;
12031226
12041227 function clickHandler(evt) {
12051228 if (elem.attr('disabled')) return false;
12061229 if (attrGetter('ngfSelectDisabled', scope)) return;
12071230
1208 var r = handleTouch(evt);
1231 var r = detectSwipe(evt);
1232 // prevent the click if it is a swipe
12091233 if (r != null) return r;
12101234
12111235 resetModel(evt);
12171241 document.body.appendChild(fileElem.parent()[0]);
12181242 fileElem.bind('change', changeFn);
12191243 }
1220 } catch(e){/*ignore*/}
1244 } catch (e) {/*ignore*/
1245 }
12211246
12221247 if (isDelayedClickSupported(navigator.userAgent)) {
12231248 setTimeout(function () {
12301255 return false;
12311256 }
12321257
1233 function handleTouch(evt) {
1258
1259 var initialTouchStartY = 0;
1260 var initialTouchStartX = 0;
1261
1262 function detectSwipe(evt) {
12341263 var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
1235 if (evt.type === 'touchstart') {
1236 initialTouchStartY = touches ? touches[0].clientY : 0;
1237 return true; // don't block event default
1238 } else {
1239 evt.stopPropagation();
1240 evt.preventDefault();
1241
1242 // prevent scroll from triggering event
1243 if (evt.type === 'touchend') {
1244 var currentLocation = touches ? touches[0].clientY : 0;
1245 if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
1264 if (touches) {
1265 if (evt.type === 'touchstart') {
1266 initialTouchStartX = touches[0].clientX;
1267 initialTouchStartY = touches[0].clientY;
1268 return true; // don't block event default
1269 } else {
1270 // prevent scroll from triggering event
1271 if (evt.type === 'touchend') {
1272 var currentX = touches[0].clientX;
1273 var currentY = touches[0].clientY;
1274 if ((Math.abs(currentX - initialTouchStartX) > 20) ||
1275 (Math.abs(currentY - initialTouchStartY) > 20)) {
1276 evt.stopPropagation();
1277 evt.preventDefault();
1278 return false;
1279 }
1280 }
1281 return true;
12461282 }
12471283 }
12481284 }
14731509 var size = resizeParams;
14741510 if (directiveName === 'ngfThumbnail') {
14751511 if (!size) {
1476 size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
1512 size = {
1513 width: elem[0].naturalWidth || elem[0].clientWidth,
1514 height: elem[0].naturalHeight || elem[0].clientHeight
1515 };
14771516 }
14781517 if (size.width === 0 && window.getComputedStyle) {
14791518 var style = getComputedStyle(elem[0]);
1480 size = {
1481 width: parseInt(style.width.slice(0, -2)),
1482 height: parseInt(style.height.slice(0, -2))
1483 };
1519 if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {
1520 size = {
1521 width: parseInt(style.width.slice(0, -2)),
1522 height: parseInt(style.height.slice(0, -2))
1523 };
1524 }
14841525 }
14851526 }
14861527
14951536 if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
14961537 (!isBackground || file.type.indexOf('image') === 0)) {
14971538 if (size && Upload.isResizeSupported()) {
1498 Upload.resize(file, size.width, size.height, size.quality).then(
1539 size.resizeIf = function (width, height) {
1540 return Upload.attrGetter('ngfResizeIf', attr, scope,
1541 {$width: width, $height: height, $file: file});
1542 };
1543 Upload.resize(file, size).then(
14991544 function (f) {
15001545 constructDataUrl(f);
15011546 }, function (e) {
15571602 }]);
15581603
15591604 ngFileUpload.config(['$compileProvider', function ($compileProvider) {
1560 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1561 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1605 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);
1606 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);
15621607 }]);
15631608
15641609 ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
16501695 if (ngModel) {
16511696 ngModel.$formatters.push(function (files) {
16521697 if (ngModel.$dirty) {
1698 var filesArray = files;
16531699 if (files && !angular.isArray(files)) {
1654 files = [files];
1655 }
1656 upload.validate(files, 0, ngModel, attr, scope).then(function () {
1657 upload.applyModelValidation(ngModel, files);
1700 filesArray = [files];
1701 }
1702 upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {
1703 upload.applyModelValidation(ngModel, filesArray);
16581704 });
16591705 }
1706 return files;
16601707 });
16611708 }
16621709 };
17061753 return upload.attrGetter(name, attr, scope, params);
17071754 };
17081755
1756 var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');
1757 var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);
1758
17091759 if (files == null || files.length === 0) {
1710 return upload.emptyPromise(ngModel);
1760 return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});
17111761 }
17121762
17131763 files = files.length === undefined ? [files] : files.slice(0);
1764 var invalidFiles = [];
17141765
17151766 function validateSync(name, validationName, fn) {
17161767 if (files) {
17211772 var val = upload.getValidationAttr(attr, scope, name, validationName, file);
17221773 if (val != null) {
17231774 if (!fn(file, val, i)) {
1724 file.$error = name;
1725 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1726 file.$errorParam = val;
1727 files.splice(i, 1);
1728 valid = false;
1775 if (ignoredErrors.indexOf(name) === -1) {
1776 file.$error = name;
1777 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1778 file.$errorParam = val;
1779 if (invalidFiles.indexOf(file) === -1) {
1780 invalidFiles.push(file);
1781 }
1782 if (!runAllValidation) {
1783 files.splice(i, 1);
1784 }
1785 valid = false;
1786 } else {
1787 files.splice(i, 1);
1788 }
17291789 }
17301790 }
17311791 }
17361796 }
17371797 }
17381798
1739 validateSync('maxFiles', null, function (file, val, i) {
1740 return prevLength + i < val;
1741 });
17421799 validateSync('pattern', null, upload.validatePattern);
17431800 validateSync('minSize', 'size.min', function (file, val) {
17441801 return file.size + 0.1 >= upload.translateScalars(val);
17611818 });
17621819
17631820 if (!files.length) {
1764 return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
1821 return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});
17651822 }
17661823
17671824 function validateAsync(name, validationName, type, asyncFn, fn) {
17681825 function resolveResult(defer, file, val) {
1769 if (val != null) {
1770 asyncFn(file, val).then(function (d) {
1771 if (!fn(d, val)) {
1826 function resolveInternal(fn) {
1827 if (fn()) {
1828 if (ignoredErrors.indexOf(name) === -1) {
17721829 file.$error = name;
17731830 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
17741831 file.$errorParam = val;
1775 defer.reject();
1832 if (invalidFiles.indexOf(file) === -1) {
1833 invalidFiles.push(file);
1834 }
1835 if (!runAllValidation) {
1836 var i = files.indexOf(file);
1837 if (i > -1) files.splice(i, 1);
1838 }
1839 defer.resolve(false);
17761840 } else {
1777 defer.resolve();
1841 var j = files.indexOf(file);
1842 if (j > -1) files.splice(j, 1);
1843 defer.resolve(true);
17781844 }
1845 } else {
1846 defer.resolve(true);
1847 }
1848 }
1849
1850 if (val != null) {
1851 asyncFn(file, val).then(function (d) {
1852 resolveInternal(function () {
1853 return !fn(d, val);
1854 });
17791855 }, function () {
1780 if (attrGetter('ngfValidateForce', {$file: file})) {
1781 file.$error = name;
1782 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1783 file.$errorParam = val;
1784 defer.reject();
1785 } else {
1786 defer.resolve();
1787 }
1856 resolveInternal(function () {
1857 return attrGetter('ngfValidateForce', {$file: file});
1858 });
17881859 });
17891860 } else {
1790 defer.resolve();
1791 }
1792 }
1793
1794 var promises = [upload.emptyPromise()];
1861 defer.resolve(true);
1862 }
1863 }
1864
1865 var promises = [upload.emptyPromise(true)];
17951866 if (files) {
17961867 files = files.length === undefined ? [files] : files;
17971868 angular.forEach(files, function (file) {
17981869 var defer = $q.defer();
17991870 promises.push(defer.promise);
18001871 if (type && (file.type == null || file.type.search(type) !== 0)) {
1801 defer.resolve();
1872 defer.resolve(true);
18021873 return;
18031874 }
18041875 if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {
18061877 resolveResult(defer, file,
18071878 attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));
18081879 }, function () {
1809 defer.reject();
1880 defer.resolve(false);
18101881 });
18111882 } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {
18121883 upload.mediaDuration(file).then(function (d) {
18131884 resolveResult(defer, file,
18141885 attrGetter('ngfDuration', {$file: file, $duration: d}));
18151886 }, function () {
1816 defer.reject();
1887 defer.resolve(false);
18171888 });
18181889 } else {
18191890 resolveResult(defer, file,
18201891 upload.getValidationAttr(attr, scope, name, validationName, file));
18211892 }
18221893 });
1823 return $q.all(promises).then(function () {
1824 ngModel.$ngfValidations.push({name: name, valid: true});
1825 }, function () {
1826 ngModel.$ngfValidations.push({name: name, valid: false});
1827 });
1828 }
1894 }
1895 var deffer = $q.defer();
1896 $q.all(promises).then(function (values) {
1897 var isValid = true;
1898 for (var i = 0; i < values.length; i++) {
1899 if (!values[i]) {
1900 isValid = false;
1901 break;
1902 }
1903 }
1904 ngModel.$ngfValidations.push({name: name, valid: isValid});
1905 deffer.resolve(isValid);
1906 });
1907 return deffer.promise;
18291908 }
18301909
18311910 var deffer = $q.defer();
18321911 var promises = [];
18331912
1834 promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/,
1913 promises.push(validateAsync('maxHeight', 'height.max', /image/,
18351914 this.imageDimensions, function (d, val) {
18361915 return d.height <= val;
1837 })));
1838 promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/,
1916 }));
1917 promises.push(validateAsync('minHeight', 'height.min', /image/,
18391918 this.imageDimensions, function (d, val) {
18401919 return d.height >= val;
1841 })));
1842 promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/,
1920 }));
1921 promises.push(validateAsync('maxWidth', 'width.max', /image/,
18431922 this.imageDimensions, function (d, val) {
18441923 return d.width <= val;
1845 })));
1846 promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/,
1924 }));
1925 promises.push(validateAsync('minWidth', 'width.min', /image/,
18471926 this.imageDimensions, function (d, val) {
18481927 return d.width >= val;
1849 })));
1850 promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/,
1928 }));
1929 promises.push(validateAsync('dimensions', null, /image/,
18511930 function (file, val) {
18521931 return upload.emptyPromise(val);
18531932 }, function (r) {
18541933 return r;
1855 })));
1856 promises.push(upload.happyPromise(validateAsync('ratio', null, /image/,
1934 }));
1935 promises.push(validateAsync('ratio', null, /image/,
18571936 this.imageDimensions, function (d, val) {
18581937 var split = val.toString().split(','), valid = false;
18591938 for (var i = 0; i < split.length; i++) {
1860 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
1939 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {
18611940 valid = true;
18621941 }
18631942 }
18641943 return valid;
1865 })));
1866 promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/,
1944 }));
1945 promises.push(validateAsync('maxRatio', 'ratio.max', /image/,
18671946 this.imageDimensions, function (d, val) {
18681947 return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
1869 })));
1870 promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/,
1948 }));
1949 promises.push(validateAsync('minRatio', 'ratio.min', /image/,
18711950 this.imageDimensions, function (d, val) {
18721951 return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
1873 })));
1874 promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/,
1952 }));
1953 promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,
18751954 this.mediaDuration, function (d, val) {
18761955 return d <= upload.translateScalars(val);
1877 })));
1878 promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/,
1956 }));
1957 promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,
18791958 this.mediaDuration, function (d, val) {
18801959 return d >= upload.translateScalars(val);
1881 })));
1882 promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/,
1960 }));
1961 promises.push(validateAsync('duration', null, /audio|video/,
18831962 function (file, val) {
18841963 return upload.emptyPromise(val);
18851964 }, function (r) {
18861965 return r;
1887 })));
1888
1889 promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null,
1966 }));
1967
1968 promises.push(validateAsync('validateAsyncFn', null, null,
18901969 function (file, val) {
18911970 return val;
18921971 }, function (r) {
18931972 return r === true || r === null || r === '';
1894 })));
1895
1896 return $q.all(promises).then(function () {
1897 deffer.resolve(ngModel, ngModel.$ngfValidations);
1898 });
1973 }));
1974
1975 $q.all(promises).then(function () {
1976
1977 if (runAllValidation) {
1978 for (var i = 0; i < files.length; i++) {
1979 var file = files[i];
1980 if (file.$error) {
1981 files.splice(i--, 1);
1982 }
1983 }
1984 }
1985
1986 runAllValidation = false;
1987 validateSync('maxFiles', null, function (file, val, i) {
1988 return prevLength + i < val;
1989 });
1990
1991 deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});
1992 });
1993 return deffer.promise;
18991994 };
19001995
19011996 upload.imageDimensions = function (file) {
19202015 .css('max-width', 'none !important').css('max-height', 'none !important');
19212016
19222017 function success() {
1923 var width = img[0].clientWidth;
1924 var height = img[0].clientHeight;
2018 var width = img[0].naturalWidth || img[0].clientWidth;
2019 var height = img[0].naturalHeight || img[0].clientHeight;
19252020 img.remove();
19262021 file.$ngfWidth = width;
19272022 file.$ngfHeight = height;
19352030
19362031 img.on('load', success);
19372032 img.on('error', error);
1938 var count = 0;
1939
1940 function checkLoadError() {
2033
2034 var secondsCounter = 0;
2035 function checkLoadErrorInCaseOfNoCallback() {
19412036 $timeout(function () {
19422037 if (img[0].parentNode) {
19432038 if (img[0].clientWidth) {
19442039 success();
1945 } else if (count > 10) {
2040 } else if (secondsCounter++ > 10) {
19462041 error();
19472042 } else {
1948 checkLoadError();
2043 checkLoadErrorInCaseOfNoCallback();
19492044 }
19502045 }
19512046 }, 1000);
19522047 }
19532048
1954 checkLoadError();
2049 checkLoadErrorInCaseOfNoCallback();
19552050
19562051 angular.element(document.getElementsByTagName('body')[0]).append(img);
19572052 }, function () {
20622157 var deferred = $q.defer();
20632158 var canvasElement = document.createElement('canvas');
20642159 var imageElement = document.createElement('img');
2160 imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');
2161 document.body.appendChild(imageElement);
20652162
20662163 imageElement.onload = function () {
2067 if (resizeIf != null && resizeIf(imageElement.width, imageElement.height) === false) {
2164 var imgWidth = imageElement.width, imgHeight = imageElement.height;
2165 imageElement.parentNode.removeChild(imageElement);
2166 if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {
20682167 deferred.reject('resizeIf');
20692168 return;
20702169 }
20712170 try {
20722171 if (ratio) {
20732172 var ratioFloat = upload.ratioToFloat(ratio);
2074 var imgRatio = imageElement.width / imageElement.height;
2173 var imgRatio = imgWidth / imgHeight;
20752174 if (imgRatio < ratioFloat) {
2076 width = imageElement.width;
2175 width = imgWidth;
20772176 height = width / ratioFloat;
20782177 } else {
2079 height = imageElement.height;
2178 height = imgHeight;
20802179 width = height * ratioFloat;
20812180 }
20822181 }
20832182 if (!width) {
2084 width = imageElement.width;
2183 width = imgWidth;
20852184 }
20862185 if (!height) {
2087 height = imageElement.height;
2088 }
2089 var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
2186 height = imgHeight;
2187 }
2188 var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);
20902189 canvasElement.width = Math.min(dimensions.width, width);
20912190 canvasElement.height = Math.min(dimensions.height, height);
20922191 var context = canvasElement.getContext('2d');
20992198 }
21002199 };
21012200 imageElement.onerror = function () {
2201 imageElement.parentNode.removeChild(imageElement);
21022202 deferred.reject();
21032203 };
21042204 imageElement.src = imagen;
21352235 });
21362236 }
21372237
2138 upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) {
2238 upload.resize = function (file, options) {
21392239 if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
21402240
21412241 var deferred = $q.defer();
21422242 upload.dataUrl(file, true).then(function (url) {
2143 resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf)
2243 resize(url, options.width, options.height, options.quality, options.type || file.type,
2244 options.ratio, options.centerCrop, options.resizeIf)
21442245 .then(function (dataUrl) {
2145 if (file.type === 'image/jpeg' && restoreExif) {
2246 if (file.type === 'image/jpeg' && options.restoreExif !== false) {
21462247 try {
21472248 dataUrl = upload.restoreExif(url, dataUrl);
21482249 } catch (e) {
21712272 }]);
21722273
21732274 (function () {
2174 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
2175 function ($parse, $timeout, $location, Upload, $http, $q) {
2275 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',
2276 function ($parse, $timeout, $window, Upload, $http, $q) {
21762277 return {
21772278 restrict: 'AEC',
21782279 require: '?ngModel',
21792280 link: function (scope, elem, attr, ngModel) {
2180 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
2281 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);
21812282 }
21822283 };
21832284 }]);
22022303 };
22032304 }]);
22042305
2205 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
2306 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {
22062307 var available = dropAvailable();
22072308
22082309 var attrGetter = function (name, scope, params) {
22782379 if (stopPropagation(scope)) evt.stopPropagation();
22792380 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
22802381 actualDragOverClass = null;
2281 var items = evt.dataTransfer.items;
2282 var html;
2283 try {
2284 html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
2285 } catch (e) {/* Fix IE11 that throw error calling getData */
2286 }
2287
2288 extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
2289 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
2290 if (files.length) {
2291 updateModel(files, evt);
2292 } else {
2293 extractFilesFromHtml('dropUrl', html).then(function (files) {
2294 updateModel(files, evt);
2295 });
2296 }
2297 });
2382 extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');
22982383 }, false);
22992384 elem[0].addEventListener('paste', function (evt) {
23002385 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
23022387 evt.preventDefault();
23032388 }
23042389 if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
2305 var files = [];
2306 var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
2307 if (clipboard && clipboard.items) {
2308 for (var k = 0; k < clipboard.items.length; k++) {
2309 if (clipboard.items[k].type.indexOf('image') !== -1) {
2310 files.push(clipboard.items[k].getAsFile());
2311 }
2312 }
2313 }
2314 if (files.length) {
2315 updateModel(files, evt);
2316 } else {
2317 extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
2318 updateModel(files, evt);
2319 });
2320 }
2390 extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');
23212391 }, false);
23222392
23232393 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
23302400 });
23312401 }
23322402
2403 function extractFilesAndUpdateModel(source, evt, updateOnType) {
2404 if (!source) return;
2405 // html needs to be calculated on the same process otherwise the data will be wiped
2406 // after promise resolve or setTimeout.
2407 var html;
2408 try {
2409 html = source && source.getData && source.getData('text/html');
2410 } catch (e) {/* Fix IE11 that throw error calling getData */
2411 }
2412 extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,
2413 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
2414 if (files.length) {
2415 updateModel(files, evt);
2416 } else {
2417 extractFilesFromHtml(updateOnType, html).then(function (files) {
2418 updateModel(files, evt);
2419 });
2420 }
2421 });
2422 }
2423
23332424 function updateModel(files, evt) {
23342425 upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
23352426 }
23362427
23372428 function extractFilesFromHtml(updateOn, html) {
2338 if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
2429 if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);
23392430 var urls = [];
23402431 html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
23412432 urls.push(src);
23862477 }
23872478
23882479 function extractFiles(items, fileList, allowDir, multiple) {
2389 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
2390 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
2480 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');
2481 if (maxFiles == null) {
2482 maxFiles = Number.MAX_VALUE;
2483 }
2484 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');
2485 if (maxTotalSize == null) {
2486 maxTotalSize = Number.MAX_VALUE;
2487 }
23912488 var includeDir = attrGetter('ngfIncludeDir', scope);
23922489 var files = [], totalSize = 0;
23932490
23982495 var promises = [upload.emptyPromise()];
23992496 if (includeDir) {
24002497 var file = {type: 'directory'};
2401 file.name = file.path = (path || '') + entry.name + entry.name;
2498 file.name = file.path = (path || '') + entry.name;
24022499 files.push(file);
24032500 }
24042501 var dirReader = entry.createReader();
24522549
24532550 var promises = [upload.emptyPromise()];
24542551
2455 if (items && items.length > 0 && $location.protocol() !== 'file') {
2552 if (items && items.length > 0 && $window.location.protocol !== 'file:') {
24562553 for (var i = 0; i < items.length; i++) {
24572554 if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
24582555 var entry = items[i].webkitGetAsEntry();
0 /*! 12.2.13 */
1 !function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}if(window.FileAPI||(window.FileAPI={}),!window.XMLHttpRequest)throw"AJAX is not supported. XMLHttpRequest is not defined.";if(FileAPI.shouldLoad=!window.FormData||FileAPI.forceLoad,FileAPI.shouldLoad){var c=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,d,e){c(this),this.__url=d;try{a.apply(this,[b,d,e])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",e]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,d){if("__setXHR_"===b){c(this);var e=d(this);e instanceof Function&&e(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=d,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var d=arguments[0],e={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){a&&angular.isString(a)&&-1!==a.indexOf("#2174")&&(a=null),c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0===d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0===d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},progress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};e.data={},e.files={};for(var f=0;f<d.data.length;f++){var g=d.data[f];null!=g.val&&null!=g.val.name&&null!=g.val.size&&null!=g.val.type?e.files[g.key]=g.val:e.data[g.key]=g.val}setTimeout(function(){if(!FileAPI.hasFlash)throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';c.__fileApiXHR=FileAPI.upload(e)},1)}else{if(this.__origError)throw this.__origError;a.apply(c,arguments)}}}),window.XMLHttpRequest.__isFileAPIShim=!0,window.FormData=FormData=function(){return{append:function(a,b,c){b.__isFileAPIBlobShim&&(b=b.data[0]),this.data.push({key:a,val:b,name:c})},data:[],__isFileAPIShim:!0}},window.Blob=Blob=function(a){return{data:a,__isFileAPIBlobShim:!0}}}}(),function(){function a(a){return"input"===a[0].tagName.toLowerCase()&&a.attr("type")&&"file"===a.attr("type").toLowerCase()}function b(){try{var a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(a)return!0}catch(b){if(void 0!==navigator.mimeTypes["application/x-shockwave-flash"])return!0}return!1}function c(a){var b=0,c=0;if(window.jQuery)return jQuery(a).offset();if(a.offsetParent)do b+=a.offsetLeft-a.scrollLeft,c+=a.offsetTop-a.scrollTop,a=a.offsetParent;while(a);return{left:b,top:c}}if(FileAPI.shouldLoad){if(FileAPI.hasFlash=b(),FileAPI.forceLoad&&(FileAPI.html5=!1),!FileAPI.upload){var d,e,f,g,h,i=document.createElement("script"),j=document.getElementsByTagName("script");if(window.FileAPI.jsUrl)d=window.FileAPI.jsUrl;else if(window.FileAPI.jsPath)e=window.FileAPI.jsPath;else for(f=0;f<j.length;f++)if(h=j[f].src,g=h.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/),g>-1){e=h.substring(0,g+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=e),i.setAttribute("src",d||e+"FileAPI.min.js"),document.getElementsByTagName("head")[0].appendChild(i)}FileAPI.ngfFixIE=function(d,e,f){if(!b())throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';var g=function(){var b=e.parent();d.attr("disabled")?b&&b.removeClass("js-fileapi-wrapper"):(e.attr("__ngf_flash_")||(e.unbind("change"),e.unbind("click"),e.bind("change",function(a){h.apply(this,[a]),f.apply(this,[a])}),e.attr("__ngf_flash_","true")),b.addClass("js-fileapi-wrapper"),a(d)||(b.css("position","absolute").css("top",c(d[0]).top+"px").css("left",c(d[0]).left+"px").css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("filter","alpha(opacity=0)").css("display",d.css("display")).css("overflow","hidden").css("z-index","900000").css("visibility","visible"),e.css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("position","absolute").css("top","0px").css("left","0px")))};d.bind("mouseenter",g);var h=function(a){for(var b=FileAPI.getFiles(a),c=0;c<b.length;c++)void 0===b[c].size&&(b[c].size=0),void 0===b[c].name&&(b[c].name="file"),void 0===b[c].type&&(b[c].type="undefined");a.target||(a.target={}),a.target.files=b,a.target.files!==b&&(a.__files_=b),(a.__files_||a.target.files).item=function(b){return(a.__files_||a.target.files)[b]||null}}},FileAPI.disableFileInput=function(a,b){b?a.removeClass("js-fileapi-wrapper"):a.addClass("js-fileapi-wrapper")}}}(),window.FileReader||(window.FileReader=function(){var a=this,b=!1;this.listeners={},this.addEventListener=function(b,c){a.listeners[b]=a.listeners[b]||[],a.listeners[b].push(c)},this.removeEventListener=function(b,c){a.listeners[b]&&a.listeners[b].splice(a.listeners[b].indexOf(c),1)},this.dispatchEvent=function(b){var c=a.listeners[b.type];if(c)for(var d=0;d<c.length;d++)c[d].call(a,b)},this.onabort=this.onerror=this.onload=this.onloadstart=this.onloadend=this.onprogress=null;var c=function(b,c){var d={type:b,target:a,loaded:c.loaded,total:c.total,error:c.error};return null!=c.result&&(d.target.result=c.result),d},d=function(d){b||(b=!0,a.onloadstart&&a.onloadstart(c("loadstart",d)));var e;"load"===d.type?(a.onloadend&&a.onloadend(c("loadend",d)),e=c("load",d),a.onload&&a.onload(e),a.dispatchEvent(e)):"progress"===d.type?(e=c("progress",d),a.onprogress&&a.onprogress(e),a.dispatchEvent(e)):(e=c("error",d),a.onerror&&a.onerror(e),a.dispatchEvent(e))};this.readAsDataURL=function(a){FileAPI.readAsDataURL(a,d)},this.readAsText=function(a){FileAPI.readAsText(a,d)}}),!window.XMLHttpRequest||window.FileAPI&&FileAPI.shouldLoad||(window.XMLHttpRequest.prototype.setRequestHeader=function(a){return function(b,c){if("__setXHR_"===b){var d=c(this);d instanceof Function&&d(this)}else a.apply(this,arguments)}}(window.XMLHttpRequest.prototype.setRequestHeader));var ngFileUpload=angular.module("ngFileUpload",[]);ngFileUpload.version="12.2.13",ngFileUpload.service("UploadBase",["$http","$q","$timeout",function(a,b,c){function d(d){function e(a){j.notify&&j.notify(a),k.progressFunc&&c(function(){k.progressFunc(a)})}function h(a){return null!=d._start&&g?{loaded:a.loaded+d._start,total:d._file&&d._file.size||a.total,type:a.type,config:d,lengthComputable:!0,target:a.target}:a}function i(){a(d).then(function(a){if(g&&d._chunkSize&&!d._finished&&d._file){var b=d._file&&d._file.size||0;e({loaded:Math.min(d._end,b),total:b,config:d,type:"progress"}),f.upload(d,!0)}else d._finished&&delete d._finished,j.resolve(a)},function(a){j.reject(a)},function(a){j.notify(a)})}d.method=d.method||"POST",d.headers=d.headers||{};var j=d._deferred=d._deferred||b.defer(),k=j.promise;return d.disableProgress||(d.headers.__setXHR_=function(){return function(a){a&&a.upload&&a.upload.addEventListener&&(d.__XHR=a,d.xhrFn&&d.xhrFn(a),a.upload.addEventListener("progress",function(a){a.config=d,e(h(a))},!1),a.upload.addEventListener("load",function(a){a.lengthComputable&&(a.config=d,e(h(a)))},!1))}}),g?d._chunkSize&&d._end&&!d._finished?(d._start=d._end,d._end+=d._chunkSize,i()):d.resumeSizeUrl?a.get(d.resumeSizeUrl).then(function(a){d._start=d.resumeSizeResponseReader?d.resumeSizeResponseReader(a.data):parseInt((null==a.data.size?a.data:a.data.size).toString()),d._chunkSize&&(d._end=d._start+d._chunkSize),i()},function(a){throw a}):d.resumeSize?d.resumeSize().then(function(a){d._start=a,d._chunkSize&&(d._end=d._start+d._chunkSize),i()},function(a){throw a}):(d._chunkSize&&(d._start=0,d._end=d._start+d._chunkSize),i()):i(),k.success=function(a){return k.then(function(b){a(b.data,b.status,b.headers,d)}),k},k.error=function(a){return k.then(null,function(b){a(b.data,b.status,b.headers,d)}),k},k.progress=function(a){return k.progressFunc=a,k.then(null,null,function(b){a(b)}),k},k.abort=k.pause=function(){return d.__XHR&&c(function(){d.__XHR.abort()}),k},k.xhr=function(a){return d.xhrFn=function(b){return function(){b&&b.apply(k,arguments),a.apply(k,arguments)}}(d.xhrFn),k},f.promisesCount++,k["finally"]&&k["finally"]instanceof Function&&k["finally"](function(){f.promisesCount--}),k}function e(a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}var f=this;f.promisesCount=0,this.isResumeSupported=function(){return window.Blob&&window.Blob.prototype.slice};var g=this.isResumeSupported();this.isUploadInProgress=function(){return f.promisesCount>0},this.rename=function(a,b){return a.ngfName=b,a},this.jsonBlob=function(a){null==a||angular.isString(a)||(a=JSON.stringify(a));var b=new window.Blob([a],{type:"application/json"});return b._ngfBlob=!0,b},this.json=function(a){return angular.toJson(a)},this.isFile=function(a){return null!=a&&(a instanceof window.Blob||a.flashId&&a.name&&a.size)},this.upload=function(a,b){function c(b,c){if(b._ngfBlob)return b;if(a._file=a._file||b,null!=a._start&&g){a._end&&a._end>=b.size&&(a._finished=!0,a._end=b.size);var d=b.slice(a._start,a._end||b.size);return d.name=b.name,d.ngfName=b.ngfName,a._chunkSize&&(c.append("_chunkSize",a._chunkSize),c.append("_currentChunkSize",a._end-a._start),c.append("_chunkNumber",Math.floor(a._start/a._chunkSize)),c.append("_totalSize",a._file.size)),d}return b}function h(b,d,e){if(void 0!==d)if(angular.isDate(d)&&(d=d.toISOString()),angular.isString(d))b.append(e,d);else if(f.isFile(d)){var g=c(d,b),i=e.split(",");i[1]&&(g.ngfName=i[1].replace(/^\s+|\s+$/g,""),e=i[0]),a._fileKey=a._fileKey||e,b.append(e,g,g.ngfName||g.name)}else if(angular.isObject(d)){if(d.$$ngfCircularDetection)throw"ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: "+e;d.$$ngfCircularDetection=!0;try{for(var j in d)if(d.hasOwnProperty(j)&&"$$ngfCircularDetection"!==j){var k=null==a.objectKey?"[i]":a.objectKey;d.length&&parseInt(j)>-1&&(k=null==a.arrayKey?k:a.arrayKey),h(b,d[j],e+k.replace(/[ik]/g,j))}}finally{delete d.$$ngfCircularDetection}}else b.append(e,d)}function i(){a._chunkSize=f.translateScalars(a.resumeChunkSize),a._chunkSize=a._chunkSize?parseInt(a._chunkSize.toString()):null,a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?angular.isArray(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(b){var c,d=new window.FormData;b=b||a.fields||{},a.file&&(b.file=a.file);for(c in b)if(b.hasOwnProperty(c)){var e=b[c];a.formDataAppender?a.formDataAppender(d,c,e):h(d,e,c)}return d})}return b||(a=e(a)),a._isDigested||(a._isDigested=!0,i()),d(a)},this.http=function(b){return b=e(b),b.transformRequest=b.transformRequest||function(b){return window.ArrayBuffer&&b instanceof window.ArrayBuffer||b instanceof window.Blob?b:a.defaults.transformRequest[0].apply(this,arguments)},b._chunkSize=f.translateScalars(b.resumeChunkSize),b._chunkSize=b._chunkSize?parseInt(b._chunkSize.toString()):null,d(b)},this.translateScalars=function(a){if(angular.isString(a)){if(a.search(/kb/i)===a.length-2)return parseFloat(1024*a.substring(0,a.length-2));if(a.search(/mb/i)===a.length-2)return parseFloat(1048576*a.substring(0,a.length-2));if(a.search(/gb/i)===a.length-2)return parseFloat(1073741824*a.substring(0,a.length-2));if(a.search(/b/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/s/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/m/i)===a.length-1)return parseFloat(60*a.substring(0,a.length-1));if(a.search(/h/i)===a.length-1)return parseFloat(3600*a.substring(0,a.length-1))}return a},this.urlToBlob=function(c){var d=b.defer();return a({url:c,method:"get",responseType:"arraybuffer"}).then(function(a){var b=new Uint8Array(a.data),e=a.headers("content-type")||"image/WebP",f=new window.Blob([b],{type:e}),g=c.match(/.*\/(.+?)(\?.*)?$/);g.length>1&&(f.name=g[1]),d.resolve(f)},function(a){d.reject(a)}),d.promise},this.setDefaults=function(a){this.defaults=a||{}},this.defaults={},this.version=ngFileUpload.version}]),ngFileUpload.service("Upload",["$parse","$timeout","$compile","$q","UploadExif",function(a,b,c,d,e){function f(a,b,c){var e=[i.emptyPromise()];return angular.forEach(a,function(d,f){0===d.type.indexOf("image/jpeg")&&i.attrGetter("ngfFixOrientation",b,c,{$file:d})&&e.push(i.happyPromise(i.applyExifRotation(d),d).then(function(b){a.splice(f,1,b)}))}),d.all(e)}function g(a,b,c,e){var f=i.attrGetter("ngfResize",b,c);if(!f||!i.isResizeSupported()||!a.length)return i.emptyPromise();if(f instanceof Function){var g=d.defer();return f(a).then(function(d){h(d,a,b,c,e).then(function(a){g.resolve(a)},function(a){g.reject(a)})},function(a){g.reject(a)})}return h(f,a,b,c,e)}function h(a,b,c,e,f){function g(d,g){if(0===d.type.indexOf("image")){if(a.pattern&&!i.validatePattern(d,a.pattern))return;a.resizeIf=function(a,b){return i.attrGetter("ngfResizeIf",c,e,{$width:a,$height:b,$file:d})};var j=i.resize(d,a);h.push(j),j.then(function(a){b.splice(g,1,a)},function(a){d.$error="resize",(d.$errorMessages=d.$errorMessages||{}).resize=!0,d.$errorParam=(a?(a.message?a.message:a)+": ":"")+(d&&d.name),f.$ngfValidations.push({name:"resize",valid:!1}),i.applyModelValidation(f,b)})}}for(var h=[i.emptyPromise()],j=0;j<b.length;j++)g(b[j],j);return d.all(h)}var i=e;return i.getAttrWithDefaults=function(a,b){if(null!=a[b])return a[b];var c=i.defaults[b];return null==c?c:angular.isString(c)?c:JSON.stringify(c)},i.attrGetter=function(b,c,d,e){var f=this.getAttrWithDefaults(c,b);if(!d)return f;try{return e?a(f)(d,e):a(f)(d)}catch(g){if(b.search(/min|max|pattern/i))return f;throw g}},i.shouldUpdateOn=function(a,b,c){var d=i.attrGetter("ngfModelOptions",b,c);return d&&d.updateOn?d.updateOn.split(" ").indexOf(a)>-1:!0},i.emptyPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.resolve.apply(a,c)}),a.promise},i.rejectPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.reject.apply(a,c)}),a.promise},i.happyPromise=function(a,c){var e=d.defer();return a.then(function(a){e.resolve(a)},function(a){b(function(){throw a}),e.resolve(c)}),e.promise},i.updateModel=function(c,d,e,h,j,k,l){function m(f,g,j,l,m){d.$$ngfPrevValidFiles=f,d.$$ngfPrevInvalidFiles=g;var n=f&&f.length?f[0]:null,o=g&&g.length?g[0]:null;c&&(i.applyModelValidation(c,f),c.$setViewValue(m?n:f)),h&&a(h)(e,{$files:f,$file:n,$newFiles:j,$duplicateFiles:l,$invalidFiles:g,$invalidFile:o,$event:k});var p=i.attrGetter("ngfModelInvalid",d);p&&b(function(){a(p).assign(e,m?o:g)}),b(function(){})}function n(){function a(a,b){return a.name===b.name&&(a.$ngfOrigSize||a.size)===(b.$ngfOrigSize||b.size)&&a.type===b.type}function b(b){var c;for(c=0;c<r.length;c++)if(a(b,r[c]))return!0;for(c=0;c<s.length;c++)if(a(b,s[c]))return!0;return!1}if(j){q=[],t=[];for(var c=0;c<j.length;c++)b(j[c])?t.push(j[c]):q.push(j[c])}}function o(a){return angular.isArray(a)?a:[a]}function p(){function a(){b(function(){m(w?r.concat(v):v,w?s.concat(u):u,j,t,x)},z&&z.debounce?z.debounce.change||z.debounce:0)}var f=y?q:v;g(f,d,e,c).then(function(){y?i.validate(q,w?r.length:0,c,d,e).then(function(b){v=b.validsFiles,u=b.invalidsFiles,a()}):a()},function(){for(var b=0;b<f.length;b++){var c=f[b];if("resize"===c.$error){var d=v.indexOf(c);d>-1&&(v.splice(d,1),u.push(c)),a()}}})}var q,r,s,t=[],u=[],v=[];r=d.$$ngfPrevValidFiles||[],s=d.$$ngfPrevInvalidFiles||[],c&&c.$modelValue&&(r=o(c.$modelValue));var w=i.attrGetter("ngfKeep",d,e);q=(j||[]).slice(0),("distinct"===w||i.attrGetter("ngfKeepDistinct",d,e)===!0)&&n(d,e);var x=!w&&!i.attrGetter("ngfMultiple",d,e)&&!i.attrGetter("multiple",d);if(!w||q.length){i.attrGetter("ngfBeforeModelChange",d,e,{$files:j,$file:j&&j.length?j[0]:null,$newFiles:q,$duplicateFiles:t,$event:k});var y=i.attrGetter("ngfValidateAfterResize",d,e),z=i.attrGetter("ngfModelOptions",d,e);i.validate(q,w?r.length:0,c,d,e).then(function(a){l?m(q,[],j,t,x):(z&&z.allowInvalid||y?v=q:(v=a.validFiles,u=a.invalidFiles),i.attrGetter("ngfFixOrientation",d,e)&&i.isExifSupported()?f(v,d,e).then(function(){p()}):p())})}},i}]),ngFileUpload.directive("ngfSelect",["$parse","$timeout","$compile","Upload",function(a,b,c,d){function e(a){var b=a.match(/Android[^\d]*(\d+)\.(\d+)/);if(b&&b.length>2){var c=d.defaults.androidFixMinorVersion||4;return parseInt(b[1])<4||parseInt(b[1])===c&&parseInt(b[2])<c}return-1===a.indexOf("Chrome")&&/.*Windows.*Safari.*/.test(a)}function f(a,b,c,d,f,h,i,j){function k(){return"input"===b[0].tagName.toLowerCase()&&c.type&&"file"===c.type.toLowerCase()}function l(){return t("ngfChange")||t("ngfSelect")}function m(b){if(j.shouldUpdateOn("change",c,a)){var e=b.__files_||b.target&&b.target.files,f=[];if(!e)return;for(var g=0;g<e.length;g++)f.push(e[g]);j.updateModel(d,c,a,l(),f.length?f:null,b)}}function n(a,d){function e(b){a.attr("id","ngf-"+b),d.attr("id","ngf-label-"+b)}for(var f=0;f<b[0].attributes.length;f++){var g=b[0].attributes[f];"type"!==g.name&&"class"!==g.name&&"style"!==g.name&&("id"===g.name?(e(g.value),u.push(c.$observe("id",e))):a.attr(g.name,g.value||"required"!==g.name&&"multiple"!==g.name?g.value:g.name))}}function o(){if(k())return b;var a=angular.element('<input type="file">'),c=angular.element("<label>upload</label>");return c.css("visibility","hidden").css("position","absolute").css("overflow","hidden").css("width","0px").css("height","0px").css("border","none").css("margin","0px").css("padding","0px").attr("tabindex","-1"),n(a,c),g.push({el:b,ref:c}),document.body.appendChild(c.append(a)[0]),a}function p(c){if(b.attr("disabled"))return!1;if(!t("ngfSelectDisabled",a)){var d=q(c);if(null!=d)return d;r(c);try{k()||document.body.contains(x[0])||(g.push({el:b,ref:x.parent()}),document.body.appendChild(x.parent()[0]),x.bind("change",m))}catch(f){}return e(navigator.userAgent)?setTimeout(function(){x[0].click()},0):x[0].click(),!1}}function q(a){var b=a.changedTouches||a.originalEvent&&a.originalEvent.changedTouches;if(b){if("touchstart"===a.type)return w=b[0].clientX,v=b[0].clientY,!0;if("touchend"===a.type){var c=b[0].clientX,d=b[0].clientY;if(Math.abs(c-w)>20||Math.abs(d-v)>20)return a.stopPropagation(),a.preventDefault(),!1}return!0}}function r(b){j.shouldUpdateOn("click",c,a)&&x.val()&&(x.val(null),j.updateModel(d,c,a,l(),null,b,!0))}function s(a){if(x&&!x.attr("__ngf_ie10_Fix_")){if(!x[0].parentNode)return void(x=null);a.preventDefault(),a.stopPropagation(),x.unbind("click");var b=x.clone();return x.replaceWith(b),x=b,x.attr("__ngf_ie10_Fix_","true"),x.bind("change",m),x.bind("click",s),x[0].click(),!1}x.removeAttr("__ngf_ie10_Fix_")}var t=function(a,b){return j.attrGetter(a,c,b)};j.registerModelChangeValidator(d,c,a);var u=[];t("ngfMultiple")&&u.push(a.$watch(t("ngfMultiple"),function(){x.attr("multiple",t("ngfMultiple",a))})),t("ngfCapture")&&u.push(a.$watch(t("ngfCapture"),function(){x.attr("capture",t("ngfCapture",a))})),t("ngfAccept")&&u.push(a.$watch(t("ngfAccept"),function(){x.attr("accept",t("ngfAccept",a))})),u.push(c.$observe("accept",function(){x.attr("accept",t("accept"))}));var v=0,w=0,x=b;k()||(x=o()),x.bind("change",m),k()?b.bind("click",r):b.bind("click touchstart touchend",p),-1!==navigator.appVersion.indexOf("MSIE 10")&&x.bind("click",s),d&&d.$formatters.push(function(a){return(null==a||0===a.length)&&x.val()&&x.val(null),a}),a.$on("$destroy",function(){k()||x.parent().remove(),angular.forEach(u,function(a){a()})}),h(function(){for(var a=0;a<g.length;a++){var b=g[a];document.body.contains(b.el[0])||(g.splice(a,1),b.ref.remove())}}),window.FileAPI&&window.FileAPI.ngfFixIE&&window.FileAPI.ngfFixIE(b,x,m)}var g=[];return{restrict:"AEC",require:"?ngModel",link:function(e,g,h,i){f(e,g,h,i,a,b,c,d)}}}]),function(){function a(a){return"img"===a.tagName.toLowerCase()?"image":"audio"===a.tagName.toLowerCase()?"audio":"video"===a.tagName.toLowerCase()?"video":/./}function b(b,c,d,e,f,g,h,i){function j(a){var g=b.attrGetter("ngfNoObjectUrl",f,d);b.dataUrl(a,g)["finally"](function(){c(function(){var b=(g?a.$ngfDataUrl:a.$ngfBlobUrl)||a.$ngfDataUrl;i?e.css("background-image","url('"+(b||"")+"')"):e.attr("src",b),b?e.removeClass("ng-hide"):e.addClass("ng-hide")})})}c(function(){var c=d.$watch(f[g],function(c){var k=h;if("ngfThumbnail"===g&&(k||(k={width:e[0].naturalWidth||e[0].clientWidth,height:e[0].naturalHeight||e[0].clientHeight}),0===k.width&&window.getComputedStyle)){var l=getComputedStyle(e[0]);l.width&&l.width.indexOf("px")>-1&&l.height&&l.height.indexOf("px")>-1&&(k={width:parseInt(l.width.slice(0,-2)),height:parseInt(l.height.slice(0,-2))})}return angular.isString(c)?(e.removeClass("ng-hide"),i?e.css("background-image","url('"+c+"')"):e.attr("src",c)):void(!c||!c.type||0!==c.type.search(a(e[0]))||i&&0!==c.type.indexOf("image")?e.addClass("ng-hide"):k&&b.isResizeSupported()?(k.resizeIf=function(a,e){return b.attrGetter("ngfResizeIf",f,d,{$width:a,$height:e,$file:c})},b.resize(c,k).then(function(a){j(a)},function(a){throw a})):j(c))});d.$on("$destroy",function(){c()})})}ngFileUpload.service("UploadDataUrl",["UploadBase","$timeout","$q",function(a,b,c){var d=a;return d.base64DataUrl=function(a){if(angular.isArray(a)){var b=c.defer(),e=0;return angular.forEach(a,function(c){d.dataUrl(c,!0)["finally"](function(){if(e++,e===a.length){var c=[];angular.forEach(a,function(a){c.push(a.$ngfDataUrl)}),b.resolve(c,a)}})}),b.promise}return d.dataUrl(a,!0)},d.dataUrl=function(a,e){if(!a)return d.emptyPromise(a,a);if(e&&null!=a.$ngfDataUrl||!e&&null!=a.$ngfBlobUrl)return d.emptyPromise(e?a.$ngfDataUrl:a.$ngfBlobUrl,a);var f=e?a.$$ngfDataUrlPromise:a.$$ngfBlobUrlPromise;if(f)return f;var g=c.defer();return b(function(){if(window.FileReader&&a&&(!window.FileAPI||-1===navigator.userAgent.indexOf("MSIE 8")||a.size<2e4)&&(!window.FileAPI||-1===navigator.userAgent.indexOf("MSIE 9")||a.size<4e6)){var c=window.URL||window.webkitURL;if(c&&c.createObjectURL&&!e){var f;try{f=c.createObjectURL(a)}catch(h){return void b(function(){a.$ngfBlobUrl="",g.reject()})}b(function(){if(a.$ngfBlobUrl=f,f){g.resolve(f,a),d.blobUrls=d.blobUrls||[],d.blobUrlsTotalSize=d.blobUrlsTotalSize||0,d.blobUrls.push({url:f,size:a.size}),d.blobUrlsTotalSize+=a.size||0;for(var b=d.defaults.blobUrlsMaxMemory||268435456,e=d.defaults.blobUrlsMaxQueueSize||200;(d.blobUrlsTotalSize>b||d.blobUrls.length>e)&&d.blobUrls.length>1;){var h=d.blobUrls.splice(0,1)[0];c.revokeObjectURL(h.url),d.blobUrlsTotalSize-=h.size}}})}else{var i=new FileReader;i.onload=function(c){b(function(){a.$ngfDataUrl=c.target.result,g.resolve(c.target.result,a),b(function(){delete a.$ngfDataUrl},1e3)})},i.onerror=function(){b(function(){a.$ngfDataUrl="",g.reject()})},i.readAsDataURL(a)}}else b(function(){a[e?"$ngfDataUrl":"$ngfBlobUrl"]="",g.reject()})}),f=e?a.$$ngfDataUrlPromise=g.promise:a.$$ngfBlobUrlPromise=g.promise,f["finally"](function(){delete a[e?"$$ngfDataUrlPromise":"$$ngfBlobUrlPromise"]}),f},d}]),ngFileUpload.directive("ngfSrc",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfSrc",a.attrGetter("ngfResize",f,d),!1)}}}]),ngFileUpload.directive("ngfBackground",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfBackground",a.attrGetter("ngfResize",f,d),!0)}}}]),ngFileUpload.directive("ngfThumbnail",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){var g=a.attrGetter("ngfSize",f,d);b(a,c,d,e,f,"ngfThumbnail",g,a.attrGetter("ngfAsBackground",f,d))}}}]),ngFileUpload.config(["$compileProvider",function(a){a.imgSrcSanitizationWhitelist&&a.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/),a.aHrefSanitizationWhitelist&&a.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/)}]),ngFileUpload.filter("ngfDataUrl",["UploadDataUrl","$sce",function(a,b){return function(c,d,e){if(angular.isString(c))return b.trustAsResourceUrl(c);var f=c&&((d?c.$ngfDataUrl:c.$ngfBlobUrl)||c.$ngfDataUrl);return c&&!f?(!c.$ngfDataUrlFilterInProgress&&angular.isObject(c)&&(c.$ngfDataUrlFilterInProgress=!0,a.dataUrl(c,d)),""):(c&&delete c.$ngfDataUrlFilterInProgress,(c&&f?e?b.trustAsResourceUrl(f):f:c)||"")}}])}(),ngFileUpload.service("UploadValidate",["UploadDataUrl","$q","$timeout",function(a,b,c){function d(a){var b="",c=[];if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])b=a.substring(1,a.length-1);else{var e=a.split(",");if(e.length>1)for(var f=0;f<e.length;f++){var g=d(e[f]);g.regexp?(b+="("+g.regexp+")",f<e.length-1&&(b+="|")):c=c.concat(g.excludes)}else 0===a.indexOf("!")?c.push("^((?!"+d(a.substring(1)).regexp+").)*$"):(0===a.indexOf(".")&&(a="*"+a),b="^"+a.replace(new RegExp("[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]","g"),"\\$&")+"$",b=b.replace(/\\\*/g,".*").replace(/\\\?/g,"."))}return{regexp:b,excludes:c}}function e(a,b){null==b||a.$dirty||(a.$setDirty?a.$setDirty():a.$dirty=!0)}var f=a;return f.validatePattern=function(a,b){if(!b)return!0;var c=d(b),e=!0;if(c.regexp&&c.regexp.length){var f=new RegExp(c.regexp,"i");e=null!=a.type&&f.test(a.type)||null!=a.name&&f.test(a.name)}for(var g=c.excludes.length;g--;){var h=new RegExp(c.excludes[g],"i");e=e&&(null==a.type||h.test(a.type))&&(null==a.name||h.test(a.name))}return e},f.ratioToFloat=function(a){var b=a.toString(),c=b.search(/[x:]/i);return b=c>-1?parseFloat(b.substring(0,c))/parseFloat(b.substring(c+1)):parseFloat(b)},f.registerModelChangeValidator=function(a,b,c){a&&a.$formatters.push(function(d){if(a.$dirty){var e=d;d&&!angular.isArray(d)&&(e=[d]),f.validate(e,0,a,b,c).then(function(){f.applyModelValidation(a,e)})}return d})},f.applyModelValidation=function(a,b){e(a,b),angular.forEach(a.$ngfValidations,function(b){a.$setValidity(b.name,b.valid)})},f.getValidationAttr=function(a,b,c,d,e){var g="ngf"+c[0].toUpperCase()+c.substr(1),h=f.attrGetter(g,a,b,{$file:e});if(null==h&&(h=f.attrGetter("ngfValidate",a,b,{$file:e}))){var i=(d||c).split(".");h=h[i[0]],i.length>1&&(h=h&&h[i[1]])}return h},f.validate=function(a,c,d,e,g){function h(b,c,h){if(a){for(var i=a.length,j=null;i--;){var n=a[i];if(n){var o=f.getValidationAttr(e,g,b,c,n);null!=o&&(h(n,o,i)||(-1===k.indexOf(b)?(n.$error=b,(n.$errorMessages=n.$errorMessages||{})[b]=!0,n.$errorParam=o,-1===m.indexOf(n)&&m.push(n),l||a.splice(i,1),j=!1):a.splice(i,1)))}}null!==j&&d.$ngfValidations.push({name:b,valid:j})}}function i(c,h,i,n,o){function p(b,d,e){function f(f){if(f())if(-1===k.indexOf(c)){if(d.$error=c,(d.$errorMessages=d.$errorMessages||{})[c]=!0,d.$errorParam=e,-1===m.indexOf(d)&&m.push(d),!l){var g=a.indexOf(d);g>-1&&a.splice(g,1)}b.resolve(!1)}else{var h=a.indexOf(d);h>-1&&a.splice(h,1),b.resolve(!0)}else b.resolve(!0)}null!=e?n(d,e).then(function(a){f(function(){return!o(a,e)})},function(){f(function(){return j("ngfValidateForce",{$file:d})})}):b.resolve(!0)}var q=[f.emptyPromise(!0)];a&&(a=void 0===a.length?[a]:a,angular.forEach(a,function(a){var d=b.defer();return q.push(d.promise),!i||null!=a.type&&0===a.type.search(i)?void("dimensions"===c&&null!=f.attrGetter("ngfDimensions",e)?f.imageDimensions(a).then(function(b){p(d,a,j("ngfDimensions",{$file:a,$width:b.width,$height:b.height}))},function(){d.resolve(!1)}):"duration"===c&&null!=f.attrGetter("ngfDuration",e)?f.mediaDuration(a).then(function(b){p(d,a,j("ngfDuration",{$file:a,$duration:b}))},function(){d.resolve(!1)}):p(d,a,f.getValidationAttr(e,g,c,h,a))):void d.resolve(!0)}));var r=b.defer();return b.all(q).then(function(a){for(var b=!0,e=0;e<a.length;e++)if(!a[e]){b=!1;break}d.$ngfValidations.push({name:c,valid:b}),r.resolve(b)}),r.promise}d=d||{},d.$ngfValidations=d.$ngfValidations||[],angular.forEach(d.$ngfValidations,function(a){a.valid=!0});var j=function(a,b){return f.attrGetter(a,e,g,b)},k=(f.attrGetter("ngfIgnoreInvalid",e,g)||"").split(" "),l=f.attrGetter("ngfRunAllValidations",e,g);if(null==a||0===a.length)return f.emptyPromise({validFiles:a,invalidFiles:[]});a=void 0===a.length?[a]:a.slice(0);var m=[];h("pattern",null,f.validatePattern),h("minSize","size.min",function(a,b){return a.size+.1>=f.translateScalars(b)}),h("maxSize","size.max",function(a,b){return a.size-.1<=f.translateScalars(b)});var n=0;if(h("maxTotalSize",null,function(b,c){return n+=b.size,n>f.translateScalars(c)?(a.splice(0,a.length),!1):!0}),h("validateFn",null,function(a,b){return b===!0||null===b||""===b}),!a.length)return f.emptyPromise({validFiles:[],invalidFiles:m});var o=b.defer(),p=[];return p.push(i("maxHeight","height.max",/image/,this.imageDimensions,function(a,b){return a.height<=b})),p.push(i("minHeight","height.min",/image/,this.imageDimensions,function(a,b){return a.height>=b})),p.push(i("maxWidth","width.max",/image/,this.imageDimensions,function(a,b){return a.width<=b})),p.push(i("minWidth","width.min",/image/,this.imageDimensions,function(a,b){return a.width>=b})),p.push(i("dimensions",null,/image/,function(a,b){return f.emptyPromise(b)},function(a){return a})),p.push(i("ratio",null,/image/,this.imageDimensions,function(a,b){for(var c=b.toString().split(","),d=!1,e=0;e<c.length;e++)Math.abs(a.width/a.height-f.ratioToFloat(c[e]))<.01&&(d=!0);return d})),p.push(i("maxRatio","ratio.max",/image/,this.imageDimensions,function(a,b){return a.width/a.height-f.ratioToFloat(b)<1e-4})),p.push(i("minRatio","ratio.min",/image/,this.imageDimensions,function(a,b){return a.width/a.height-f.ratioToFloat(b)>-1e-4})),p.push(i("maxDuration","duration.max",/audio|video/,this.mediaDuration,function(a,b){return a<=f.translateScalars(b)})),p.push(i("minDuration","duration.min",/audio|video/,this.mediaDuration,function(a,b){return a>=f.translateScalars(b)})),p.push(i("duration",null,/audio|video/,function(a,b){return f.emptyPromise(b)},function(a){return a})),p.push(i("validateAsyncFn",null,null,function(a,b){return b},function(a){return a===!0||null===a||""===a})),b.all(p).then(function(){if(l)for(var b=0;b<a.length;b++){var d=a[b];d.$error&&a.splice(b--,1)}l=!1,h("maxFiles",null,function(a,b,d){return b>c+d}),o.resolve({validFiles:a,invalidFiles:m})}),o.promise},f.imageDimensions=function(a){if(a.$ngfWidth&&a.$ngfHeight){var d=b.defer();return c(function(){d.resolve({width:a.$ngfWidth,height:a.$ngfHeight})}),d.promise}if(a.$ngfDimensionPromise)return a.$ngfDimensionPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("image")?void e.reject("not image"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].naturalWidth||h[0].clientWidth,c=h[0].naturalHeight||h[0].clientHeight;h.remove(),a.$ngfWidth=b,a.$ngfHeight=c,e.resolve({width:b,height:c})}function f(){
2 h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].clientWidth?d():i++>10?f():g())},1e3)}var h=angular.element("<img>").attr("src",b).css("visibility","hidden").css("position","fixed").css("max-width","none !important").css("max-height","none !important");h.on("load",d),h.on("error",f);var i=0;g(),angular.element(document.getElementsByTagName("body")[0]).append(h)},function(){e.reject("load error")})}),a.$ngfDimensionPromise=e.promise,a.$ngfDimensionPromise["finally"](function(){delete a.$ngfDimensionPromise}),a.$ngfDimensionPromise},f.mediaDuration=function(a){if(a.$ngfDuration){var d=b.defer();return c(function(){d.resolve(a.$ngfDuration)}),d.promise}if(a.$ngfDurationPromise)return a.$ngfDurationPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("audio")&&0!==a.type.indexOf("video")?void e.reject("not media"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].duration;a.$ngfDuration=b,h.remove(),e.resolve(b)}function f(){h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].duration?d():i>10?f():g())},1e3)}var h=angular.element(0===a.type.indexOf("audio")?"<audio>":"<video>").attr("src",b).css("visibility","none").css("position","fixed");h.on("loadedmetadata",d),h.on("error",f);var i=0;g(),angular.element(document.body).append(h)},function(){e.reject("load error")})}),a.$ngfDurationPromise=e.promise,a.$ngfDurationPromise["finally"](function(){delete a.$ngfDurationPromise}),a.$ngfDurationPromise},f}]),ngFileUpload.service("UploadResize",["UploadValidate","$q",function(a,b){var c=a,d=function(a,b,c,d,e){var f=e?Math.max(c/a,d/b):Math.min(c/a,d/b);return{width:a*f,height:b*f,marginX:a*f-c,marginY:b*f-d}},e=function(a,e,f,g,h,i,j,k){var l=b.defer(),m=document.createElement("canvas"),n=document.createElement("img");return n.setAttribute("style","visibility:hidden;position:fixed;z-index:-100000"),document.body.appendChild(n),n.onload=function(){var a=n.width,b=n.height;if(n.parentNode.removeChild(n),null!=k&&k(a,b)===!1)return void l.reject("resizeIf");try{if(i){var o=c.ratioToFloat(i),p=a/b;o>p?(e=a,f=e/o):(f=b,e=f*o)}e||(e=a),f||(f=b);var q=d(a,b,e,f,j);m.width=Math.min(q.width,e),m.height=Math.min(q.height,f);var r=m.getContext("2d");r.drawImage(n,Math.min(0,-q.marginX/2),Math.min(0,-q.marginY/2),q.width,q.height),l.resolve(m.toDataURL(h||"image/WebP",g||.934))}catch(s){l.reject(s)}},n.onerror=function(){n.parentNode.removeChild(n),l.reject()},n.src=a,l.promise};return c.dataUrltoBlob=function(a,b,c){for(var d=a.split(","),e=d[0].match(/:(.*?);/)[1],f=atob(d[1]),g=f.length,h=new Uint8Array(g);g--;)h[g]=f.charCodeAt(g);var i=new window.Blob([h],{type:e});return i.name=b,i.$ngfOrigSize=c,i},c.isResizeSupported=function(){var a=document.createElement("canvas");return window.atob&&a.getContext&&a.getContext("2d")&&window.Blob},c.isResizeSupported()&&Object.defineProperty(window.Blob.prototype,"name",{get:function(){return this.$ngfName},set:function(a){this.$ngfName=a},configurable:!0}),c.resize=function(a,d){if(0!==a.type.indexOf("image"))return c.emptyPromise(a);var f=b.defer();return c.dataUrl(a,!0).then(function(b){e(b,d.width,d.height,d.quality,d.type||a.type,d.ratio,d.centerCrop,d.resizeIf).then(function(e){if("image/jpeg"===a.type&&d.restoreExif!==!1)try{e=c.restoreExif(b,e)}catch(g){setTimeout(function(){throw g},1)}try{var h=c.dataUrltoBlob(e,a.name,a.size);f.resolve(h)}catch(g){f.reject(g)}},function(b){"resizeIf"===b&&f.resolve(a),f.reject(b)})},function(a){f.reject(a)}),f.promise},c}]),function(){function a(a,c,d,e,f,g,h,i,j,k){function l(){return c.attr("disabled")||s("ngfDropDisabled",a)}function m(b,c,d){if(b){var e;try{e=b&&b.getData&&b.getData("text/html")}catch(f){}q(b.items,b.files,s("ngfAllowDir",a)!==!1,s("multiple")||s("ngfMultiple",a)).then(function(a){a.length?n(a,c):o(d,e).then(function(a){n(a,c)})})}}function n(b,c){i.updateModel(e,d,a,s("ngfChange")||s("ngfDrop"),b,c)}function o(b,c){if(!i.shouldUpdateOn(b,d,a)||"string"!=typeof c)return i.rejectPromise([]);var e=[];c.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi,function(a,b,c){e.push(c)});var f=[],g=[];if(e.length){angular.forEach(e,function(a){f.push(i.urlToBlob(a).then(function(a){g.push(a)}))});var h=k.defer();return k.all(f).then(function(){h.resolve(g)},function(a){h.reject(a)}),h.promise}return i.emptyPromise()}function p(a,b,c,d){var e=s("ngfDragOverClass",a,{$event:c}),f="dragover";if(angular.isString(e))f=e;else if(e&&(e.delay&&(w=e.delay),e.accept||e.reject)){var g=c.dataTransfer.items;if(null!=g&&g.length)for(var h=e.pattern||s("ngfPattern",a,{$event:c}),j=g.length;j--;){if(!i.validatePattern(g[j],h)){f=e.reject;break}f=e.accept}else f=e.accept}d(f)}function q(b,c,e,f){function g(a,b){var c=k.defer();if(null!=a)if(a.isDirectory){var d=[i.emptyPromise()];if(m){var e={type:"directory"};e.name=e.path=(b||"")+a.name,n.push(e)}var f=a.createReader(),h=[],p=function(){f.readEntries(function(e){try{e.length?(h=h.concat(Array.prototype.slice.call(e||[],0)),p()):(angular.forEach(h.slice(0),function(c){n.length<=j&&l>=o&&d.push(g(c,(b?b:"")+a.name+"/"))}),k.all(d).then(function(){c.resolve()},function(a){c.reject(a)}))}catch(f){c.reject(f)}},function(a){c.reject(a)})};p()}else a.file(function(a){try{a.path=(b?b:"")+a.name,m&&(a=i.rename(a,a.path)),n.push(a),o+=a.size,c.resolve()}catch(d){c.reject(d)}},function(a){c.reject(a)});return c.promise}var j=i.getValidationAttr(d,a,"maxFiles");null==j&&(j=Number.MAX_VALUE);var l=i.getValidationAttr(d,a,"maxTotalSize");null==l&&(l=Number.MAX_VALUE);var m=s("ngfIncludeDir",a),n=[],o=0,p=[i.emptyPromise()];if(b&&b.length>0&&"file:"!==h.location.protocol)for(var q=0;q<b.length;q++){if(b[q].webkitGetAsEntry&&b[q].webkitGetAsEntry()&&b[q].webkitGetAsEntry().isDirectory){var r=b[q].webkitGetAsEntry();if(r.isDirectory&&!e)continue;null!=r&&p.push(g(r))}else{var t=b[q].getAsFile();null!=t&&(n.push(t),o+=t.size)}if(n.length>j||o>l||!f&&n.length>0)break}else if(null!=c)for(var u=0;u<c.length;u++){var v=c.item(u);if((v.type||v.size>0)&&(n.push(v),o+=v.size),n.length>j||o>l||!f&&n.length>0)break}var w=k.defer();return k.all(p).then(function(){if(f||m||!n.length)w.resolve(n);else{for(var a=0;n[a]&&"directory"===n[a].type;)a++;w.resolve([n[a]])}},function(a){w.reject(a)}),w.promise}var r=b(),s=function(a,b,c){return i.attrGetter(a,d,b,c)};if(s("dropAvailable")&&g(function(){a[s("dropAvailable")]?a[s("dropAvailable")].value=r:a[s("dropAvailable")]=r}),!r)return void(s("ngfHideOnDropNotAvailable",a)===!0&&c.css("display","none"));null==s("ngfSelect")&&i.registerModelChangeValidator(e,d,a);var t,u=null,v=f(s("ngfStopPropagation")),w=1;c[0].addEventListener("dragover",function(b){if(!l()&&i.shouldUpdateOn("drop",d,a)){if(b.preventDefault(),v(a)&&b.stopPropagation(),navigator.userAgent.indexOf("Chrome")>-1){var e=b.dataTransfer.effectAllowed;b.dataTransfer.dropEffect="move"===e||"linkMove"===e?"move":"copy"}g.cancel(u),t||(t="C",p(a,d,b,function(d){t=d,c.addClass(t),s("ngfDrag",a,{$isDragging:!0,$class:t,$event:b})}))}},!1),c[0].addEventListener("dragenter",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),v(a)&&b.stopPropagation())},!1),c[0].addEventListener("dragleave",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),v(a)&&b.stopPropagation(),u=g(function(){t&&c.removeClass(t),t=null,s("ngfDrag",a,{$isDragging:!1,$event:b})},w||100))},!1),c[0].addEventListener("drop",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),v(a)&&b.stopPropagation(),t&&c.removeClass(t),t=null,m(b.dataTransfer,b,"dropUrl"))},!1),c[0].addEventListener("paste",function(b){navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&s("ngfEnableFirefoxPaste",a)&&b.preventDefault(),!l()&&i.shouldUpdateOn("paste",d,a)&&m(b.clipboardData||b.originalEvent.clipboardData,b,"pasteUrl")},!1),navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&s("ngfEnableFirefoxPaste",a)&&(c.attr("contenteditable",!0),c.on("keypress",function(a){a.metaKey||a.ctrlKey||a.preventDefault()}))}function b(){var a=document.createElement("div");return"draggable"in a&&"ondrop"in a&&!/Edge\/12./i.test(navigator.userAgent)}ngFileUpload.directive("ngfDrop",["$parse","$timeout","$window","Upload","$http","$q",function(b,c,d,e,f,g){return{restrict:"AEC",require:"?ngModel",link:function(h,i,j,k){a(h,i,j,k,b,c,d,e,f,g)}}}]),ngFileUpload.directive("ngfNoFileDrop",function(){return function(a,c){b()&&c.css("display","none")}}),ngFileUpload.directive("ngfDropAvailable",["$parse","$timeout","Upload",function(a,c,d){return function(e,f,g){if(b()){var h=a(d.attrGetter("ngfDropAvailable",g));c(function(){h(e),h.assign&&h.assign(e,!0)})}}}])}(),ngFileUpload.service("UploadExif",["UploadResize","$q",function(a,b){function c(a,b,c,d){switch(b){case 2:return a.transform(-1,0,0,1,c,0);case 3:return a.transform(-1,0,0,-1,c,d);case 4:return a.transform(1,0,0,-1,0,d);case 5:return a.transform(0,1,1,0,0,0);case 6:return a.transform(0,1,-1,0,d,0);case 7:return a.transform(0,-1,-1,0,d,c);case 8:return a.transform(0,-1,1,0,0,c)}}function d(a){for(var b="",c=new Uint8Array(a),d=c.byteLength,e=0;d>e;e++)b+=String.fromCharCode(c[e]);return window.btoa(b)}var e=a;return e.isExifSupported=function(){return window.FileReader&&(new FileReader).readAsArrayBuffer&&e.isResizeSupported()},e.readOrientation=function(a){var c=b.defer(),d=new FileReader,e=a.slice?a.slice(0,65536):a;return d.readAsArrayBuffer(e),d.onerror=function(a){return c.reject(a)},d.onload=function(a){var b={orientation:1},d=new DataView(this.result);if(65496!==d.getUint16(0,!1))return c.resolve(b);for(var e=d.byteLength,f=2;e>f;){var g=d.getUint16(f,!1);if(f+=2,65505===g){if(1165519206!==d.getUint32(f+=2,!1))return c.resolve(b);var h=18761===d.getUint16(f+=6,!1);f+=d.getUint32(f+4,h);var i=d.getUint16(f,h);f+=2;for(var j=0;i>j;j++)if(274===d.getUint16(f+12*j,h)){var k=d.getUint16(f+12*j+8,h);return k>=2&&8>=k&&(d.setUint16(f+12*j+8,1,h),b.fixedArrayBuffer=a.target.result),b.orientation=k,c.resolve(b)}}else{if(65280!==(65280&g))break;f+=d.getUint16(f,!1)}}return c.resolve(b)},c.promise},e.applyExifRotation=function(a){if(0!==a.type.indexOf("image/jpeg"))return e.emptyPromise(a);var f=b.defer();return e.readOrientation(a).then(function(b){return b.orientation<2||b.orientation>8?f.resolve(a):void e.dataUrl(a,!0).then(function(g){var h=document.createElement("canvas"),i=document.createElement("img");i.onload=function(){try{h.width=b.orientation>4?i.height:i.width,h.height=b.orientation>4?i.width:i.height;var g=h.getContext("2d");c(g,b.orientation,i.width,i.height),g.drawImage(i,0,0);var j=h.toDataURL(a.type||"image/WebP",.934);j=e.restoreExif(d(b.fixedArrayBuffer),j);var k=e.dataUrltoBlob(j,a.name);f.resolve(k)}catch(l){return f.reject(l)}},i.onerror=function(){f.reject()},i.src=g},function(a){f.reject(a)})},function(a){f.reject(a)}),f.promise},e.restoreExif=function(a,b){var c={};return c.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c.encode64=function(a){var b,c,d,e,f,g="",h="",i="",j=0;do b=a[j++],c=a[j++],h=a[j++],d=b>>2,e=(3&b)<<4|c>>4,f=(15&c)<<2|h>>6,i=63&h,isNaN(c)?f=i=64:isNaN(h)&&(i=64),g=g+this.KEY_STR.charAt(d)+this.KEY_STR.charAt(e)+this.KEY_STR.charAt(f)+this.KEY_STR.charAt(i),b=c=h="",d=e=f=i="";while(j<a.length);return g},c.restore=function(a,b){a.match("data:image/jpeg;base64,")&&(a=a.replace("data:image/jpeg;base64,",""));var c=this.decode64(a),d=this.slice2Segments(c),e=this.exifManipulation(b,d);return"data:image/jpeg;base64,"+this.encode64(e)},c.exifManipulation=function(a,b){var c=this.getExifArray(b),d=this.insertExif(a,c);return new Uint8Array(d)},c.getExifArray=function(a){for(var b,c=0;c<a.length;c++)if(b=a[c],255===b[0]&225===b[1])return b;return[]},c.insertExif=function(a,b){var c=a.replace("data:image/jpeg;base64,",""),d=this.decode64(c),e=d.indexOf(255,3),f=d.slice(0,e),g=d.slice(e),h=f;return h=h.concat(b),h=h.concat(g)},c.slice2Segments=function(a){for(var b=0,c=[];;){if(255===a[b]&218===a[b+1])break;if(255===a[b]&216===a[b+1])b+=2;else{var d=256*a[b+2]+a[b+3],e=b+d+2,f=a.slice(b,e);c.push(f),b=e}if(b>a.length)break}return c},c.decode64=function(a){var b,c,d,e,f,g="",h="",i=0,j=[],k=/[^A-Za-z0-9\+\/\=]/g;k.exec(a)&&console.log("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, NaNExpect errors in decoding."),a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");do d=this.KEY_STR.indexOf(a.charAt(i++)),e=this.KEY_STR.indexOf(a.charAt(i++)),f=this.KEY_STR.indexOf(a.charAt(i++)),h=this.KEY_STR.indexOf(a.charAt(i++)),b=d<<2|e>>4,c=(15&e)<<4|f>>2,g=(3&f)<<6|h,j.push(b),64!==f&&j.push(c),64!==h&&j.push(g),b=c=g="",d=e=f=h="";while(i<a.length);return j},c.restore(a,b)},e}]);
00 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
1 * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
22 * progress, resize, thumbnail, preview, validation and CORS
33 * FileAPI Flash shim for old browsers not supporting FormData
44 * @author Danial <danial.farid@gmail.com>
5 * @version 12.0.4
5 * @version 12.2.13
66 */
77
88 (function () {
0 /*! 12.2.13 */
1 !function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}if(window.FileAPI||(window.FileAPI={}),!window.XMLHttpRequest)throw"AJAX is not supported. XMLHttpRequest is not defined.";if(FileAPI.shouldLoad=!window.FormData||FileAPI.forceLoad,FileAPI.shouldLoad){var c=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,d,e){c(this),this.__url=d;try{a.apply(this,[b,d,e])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",e]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,d){if("__setXHR_"===b){c(this);var e=d(this);e instanceof Function&&e(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=d,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var d=arguments[0],e={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){a&&angular.isString(a)&&-1!==a.indexOf("#2174")&&(a=null),c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0===d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0===d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},progress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};e.data={},e.files={};for(var f=0;f<d.data.length;f++){var g=d.data[f];null!=g.val&&null!=g.val.name&&null!=g.val.size&&null!=g.val.type?e.files[g.key]=g.val:e.data[g.key]=g.val}setTimeout(function(){if(!FileAPI.hasFlash)throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';c.__fileApiXHR=FileAPI.upload(e)},1)}else{if(this.__origError)throw this.__origError;a.apply(c,arguments)}}}),window.XMLHttpRequest.__isFileAPIShim=!0,window.FormData=FormData=function(){return{append:function(a,b,c){b.__isFileAPIBlobShim&&(b=b.data[0]),this.data.push({key:a,val:b,name:c})},data:[],__isFileAPIShim:!0}},window.Blob=Blob=function(a){return{data:a,__isFileAPIBlobShim:!0}}}}(),function(){function a(a){return"input"===a[0].tagName.toLowerCase()&&a.attr("type")&&"file"===a.attr("type").toLowerCase()}function b(){try{var a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(a)return!0}catch(b){if(void 0!==navigator.mimeTypes["application/x-shockwave-flash"])return!0}return!1}function c(a){var b=0,c=0;if(window.jQuery)return jQuery(a).offset();if(a.offsetParent)do b+=a.offsetLeft-a.scrollLeft,c+=a.offsetTop-a.scrollTop,a=a.offsetParent;while(a);return{left:b,top:c}}if(FileAPI.shouldLoad){if(FileAPI.hasFlash=b(),FileAPI.forceLoad&&(FileAPI.html5=!1),!FileAPI.upload){var d,e,f,g,h,i=document.createElement("script"),j=document.getElementsByTagName("script");if(window.FileAPI.jsUrl)d=window.FileAPI.jsUrl;else if(window.FileAPI.jsPath)e=window.FileAPI.jsPath;else for(f=0;f<j.length;f++)if(h=j[f].src,g=h.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/),g>-1){e=h.substring(0,g+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=e),i.setAttribute("src",d||e+"FileAPI.min.js"),document.getElementsByTagName("head")[0].appendChild(i)}FileAPI.ngfFixIE=function(d,e,f){if(!b())throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';var g=function(){var b=e.parent();d.attr("disabled")?b&&b.removeClass("js-fileapi-wrapper"):(e.attr("__ngf_flash_")||(e.unbind("change"),e.unbind("click"),e.bind("change",function(a){h.apply(this,[a]),f.apply(this,[a])}),e.attr("__ngf_flash_","true")),b.addClass("js-fileapi-wrapper"),a(d)||(b.css("position","absolute").css("top",c(d[0]).top+"px").css("left",c(d[0]).left+"px").css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("filter","alpha(opacity=0)").css("display",d.css("display")).css("overflow","hidden").css("z-index","900000").css("visibility","visible"),e.css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("position","absolute").css("top","0px").css("left","0px")))};d.bind("mouseenter",g);var h=function(a){for(var b=FileAPI.getFiles(a),c=0;c<b.length;c++)void 0===b[c].size&&(b[c].size=0),void 0===b[c].name&&(b[c].name="file"),void 0===b[c].type&&(b[c].type="undefined");a.target||(a.target={}),a.target.files=b,a.target.files!==b&&(a.__files_=b),(a.__files_||a.target.files).item=function(b){return(a.__files_||a.target.files)[b]||null}}},FileAPI.disableFileInput=function(a,b){b?a.removeClass("js-fileapi-wrapper"):a.addClass("js-fileapi-wrapper")}}}(),window.FileReader||(window.FileReader=function(){var a=this,b=!1;this.listeners={},this.addEventListener=function(b,c){a.listeners[b]=a.listeners[b]||[],a.listeners[b].push(c)},this.removeEventListener=function(b,c){a.listeners[b]&&a.listeners[b].splice(a.listeners[b].indexOf(c),1)},this.dispatchEvent=function(b){var c=a.listeners[b.type];if(c)for(var d=0;d<c.length;d++)c[d].call(a,b)},this.onabort=this.onerror=this.onload=this.onloadstart=this.onloadend=this.onprogress=null;var c=function(b,c){var d={type:b,target:a,loaded:c.loaded,total:c.total,error:c.error};return null!=c.result&&(d.target.result=c.result),d},d=function(d){b||(b=!0,a.onloadstart&&a.onloadstart(c("loadstart",d)));var e;"load"===d.type?(a.onloadend&&a.onloadend(c("loadend",d)),e=c("load",d),a.onload&&a.onload(e),a.dispatchEvent(e)):"progress"===d.type?(e=c("progress",d),a.onprogress&&a.onprogress(e),a.dispatchEvent(e)):(e=c("error",d),a.onerror&&a.onerror(e),a.dispatchEvent(e))};this.readAsDataURL=function(a){FileAPI.readAsDataURL(a,d)},this.readAsText=function(a){FileAPI.readAsText(a,d)}});
11 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
22 * progress, resize, thumbnail, preview, validation and CORS
33 * @author Danial <danial.farid@gmail.com>
4 * @version 12.0.4
4 * @version 12.2.13
55 */
66
77 if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
2222
2323 var ngFileUpload = angular.module('ngFileUpload', []);
2424
25 ngFileUpload.version = '12.0.4';
25 ngFileUpload.version = '12.2.13';
2626
2727 ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
2828 var upload = this;
8989 function uploadWithAngular() {
9090 $http(config).then(function (r) {
9191 if (resumeSupported && config._chunkSize && !config._finished && config._file) {
92 var fileSize = config._file && config._file.size || 0;
9293 notifyProgress({
93 loaded: config._end,
94 total: config._file && config._file.size,
95 config: config, type: 'progress'
94 loaded: Math.min(config._end, fileSize),
95 total: fileSize,
96 config: config,
97 type: 'progress'
9698 }
9799 );
98100 upload.upload(config, true);
131133 } else if (config.resumeSize) {
132134 config.resumeSize().then(function (size) {
133135 config._start = size;
136 if (config._chunkSize) {
137 config._end = config._start + config._chunkSize;
138 }
134139 uploadWithAngular();
135140 }, function (e) {
136141 throw e;
184189 };
185190
186191 upload.promisesCount++;
187 promise['finally'](function () {
188 upload.promisesCount--;
189 });
192 if (promise['finally'] && promise['finally'] instanceof Function) {
193 promise['finally'](function () {
194 upload.promisesCount--;
195 });
196 }
190197 return promise;
191198 }
192199
370377 var arrayBufferView = new Uint8Array(resp.data);
371378 var type = resp.headers('content-type') || 'image/WebP';
372379 var blob = new window.Blob([arrayBufferView], {type: type});
380 var matches = url.match(/.*\/(.+?)(\?.*)?$/);
381 if (matches.length > 1) {
382 blob.name = matches[1];
383 }
373384 defer.resolve(blob);
374 //var split = type.split('[/;]');
375 //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
376385 }, function (e) {
377386 defer.reject(e);
378387 });
420429 };
421430
422431 upload.shouldUpdateOn = function (type, attr, scope) {
423 var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
432 var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);
424433 if (modelOptions && modelOptions.updateOn) {
425434 return modelOptions.updateOn.split(' ').indexOf(type) > -1;
426435 }
470479 return $q.all(promises);
471480 }
472481
473 function resize(files, attr, scope) {
482 function resizeFile(files, attr, scope, ngModel) {
474483 var resizeVal = upload.attrGetter('ngfResize', attr, scope);
475484 if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
476485 if (resizeVal instanceof Function) {
477486 var defer = $q.defer();
478 resizeVal(files).then(function (p) {
479 resizeWithParams(p, files, attr, scope).then(function (r) {
487 return resizeVal(files).then(function (p) {
488 resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {
480489 defer.resolve(r);
481490 }, function (e) {
482491 defer.reject(e);
485494 defer.reject(e);
486495 });
487496 } else {
488 return resizeWithParams(resizeVal, files, attr, scope);
497 return resizeWithParams(resizeVal, files, attr, scope, ngModel);
489498 }
490499 }
491500
492 function resizeWithParams(param, files, attr, scope) {
501 function resizeWithParams(params, files, attr, scope, ngModel) {
493502 var promises = [upload.emptyPromise()];
494503
495504 function handleFile(f, i) {
496505 if (f.type.indexOf('image') === 0) {
497 if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
498 var promise = upload.resize(f, param.width, param.height, param.quality,
499 param.type, param.ratio, param.centerCrop, function (width, height) {
500 return upload.attrGetter('ngfResizeIf', attr, scope,
501 {$width: width, $height: height, $file: f});
502 }, param.restoreExif !== false);
506 if (params.pattern && !upload.validatePattern(f, params.pattern)) return;
507 params.resizeIf = function (width, height) {
508 return upload.attrGetter('ngfResizeIf', attr, scope,
509 {$width: width, $height: height, $file: f});
510 };
511 var promise = upload.resize(f, params);
503512 promises.push(promise);
504513 promise.then(function (resizedFile) {
505514 files.splice(i, 1, resizedFile);
506515 }, function (e) {
507516 f.$error = 'resize';
517 (f.$errorMessages = (f.$errorMessages || {})).resize = true;
508518 f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
519 ngModel.$ngfValidations.push({name: 'resize', valid: false});
520 upload.applyModelValidation(ngModel, files);
509521 });
510522 }
511523 }
592604 return angular.isArray(v) ? v : [v];
593605 }
594606
595 function separateInvalids() {
596 valids = [];
597 invalids = [];
598 angular.forEach(allNewFiles, function (file) {
599 if (file.$error) {
600 invalids.push(file);
601 } else {
602 valids.push(file);
603 }
604 });
605 }
606
607607 function resizeAndUpdate() {
608608 function updateModel() {
609609 $timeout(function () {
613613 }, options && options.debounce ? options.debounce.change || options.debounce : 0);
614614 }
615615
616 resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () {
616 var resizingFiles = validateAfterResize ? allNewFiles : valids;
617 resizeFile(resizingFiles, attr, scope, ngModel).then(function () {
617618 if (validateAfterResize) {
618 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
619 separateInvalids();
620 updateModel();
621 });
619 upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
620 .then(function (validationResult) {
621 valids = validationResult.validsFiles;
622 invalids = validationResult.invalidsFiles;
623 updateModel();
624 });
622625 } else {
623626 updateModel();
624627 }
625 }, function (e) {
626 throw 'Could not resize files ' + e;
628 }, function () {
629 for (var i = 0; i < resizingFiles.length; i++) {
630 var f = resizingFiles[i];
631 if (f.$error === 'resize') {
632 var index = valids.indexOf(f);
633 if (index > -1) {
634 valids.splice(index, 1);
635 invalids.push(f);
636 }
637 updateModel();
638 }
639 }
627640 });
628641 }
629642
653666
654667 var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
655668
656 var options = upload.attrGetter('ngModelOptions', attr, scope);
657 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
669 var options = upload.attrGetter('ngfModelOptions', attr, scope);
670 upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
671 .then(function (validationResult) {
658672 if (noDelay) {
659673 update(allNewFiles, [], files, dupFiles, isSingleModel);
660674 } else {
661675 if ((!options || !options.allowInvalid) && !validateAfterResize) {
662 separateInvalids();
676 valids = validationResult.validFiles;
677 invalids = validationResult.invalidFiles;
663678 } else {
664679 valids = allNewFiles;
665680 }
696711 /** @namespace attr.ngfSelect */
697712 /** @namespace attr.ngfChange */
698713 /** @namespace attr.ngModel */
699 /** @namespace attr.ngModelOptions */
714 /** @namespace attr.ngfModelOptions */
700715 /** @namespace attr.ngfMultiple */
701716 /** @namespace attr.ngfCapture */
702717 /** @namespace attr.ngfValidate */
716731 function changeFn(evt) {
717732 if (upload.shouldUpdateOn('change', attr, scope)) {
718733 var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
734 /* Handle duplicate call in IE11 */
735 if (!fileList) return;
719736 for (var i = 0; i < fileList.length; i++) {
720737 files.push(fileList[i]);
721738 }
727744 upload.registerModelChangeValidator(ngModel, attr, scope);
728745
729746 var unwatches = [];
730 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
731 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
747 if (attrGetter('ngfMultiple')) {
748 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
749 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
750 }));
751 }
752 if (attrGetter('ngfCapture')) {
753 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
754 fileElem.attr('capture', attrGetter('ngfCapture', scope));
755 }));
756 }
757 if (attrGetter('ngfAccept')) {
758 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
759 fileElem.attr('accept', attrGetter('ngfAccept', scope));
760 }));
761 }
762 unwatches.push(attr.$observe('accept', function () {
763 fileElem.attr('accept', attrGetter('accept'));
732764 }));
733 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
734 fileElem.attr('capture', attrGetter('ngfCapture', scope));
735 }));
736 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
737 fileElem.attr('accept', attrGetter('ngfAccept', scope));
738 }));
739 attr.$observe('accept', function () {
740 fileElem.attr('accept', attrGetter('accept'));
741 });
742 unwatches.push(function () {
743 if (attr.$$observers) delete attr.$$observers.accept;
744 });
745 function bindAttrToFileInput(fileElem) {
746 if (elem !== fileElem) {
747 for (var i = 0; i < elem[0].attributes.length; i++) {
748 var attribute = elem[0].attributes[i];
749 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
750 if (attribute.value == null || attribute.value === '') {
751 if (attribute.name === 'required') attribute.value = 'required';
752 if (attribute.name === 'multiple') attribute.value = 'multiple';
753 }
754 fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
765 function bindAttrToFileInput(fileElem, label) {
766 function updateId(val) {
767 fileElem.attr('id', 'ngf-' + val);
768 label.attr('id', 'ngf-label-' + val);
769 }
770
771 for (var i = 0; i < elem[0].attributes.length; i++) {
772 var attribute = elem[0].attributes[i];
773 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
774 if (attribute.name === 'id') {
775 updateId(attribute.value);
776 unwatches.push(attr.$observe('id', updateId));
777 } else {
778 fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' ||
779 attribute.name === 'multiple')) ? attribute.name : attribute.value);
755780 }
756781 }
757782 }
763788 }
764789
765790 var fileElem = angular.element('<input type="file">');
766
767 bindAttrToFileInput(fileElem);
768791
769792 var label = angular.element('<label>upload</label>');
770793 label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
771794 .css('width', '0px').css('height', '0px').css('border', 'none')
772795 .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
796 bindAttrToFileInput(fileElem, label);
797
773798 generatedElems.push({el: elem, ref: label});
774799
775800 document.body.appendChild(label.append(fileElem)[0]);
776801
777802 return fileElem;
778803 }
779
780 var initialTouchStartY = 0;
781804
782805 function clickHandler(evt) {
783806 if (elem.attr('disabled')) return false;
784807 if (attrGetter('ngfSelectDisabled', scope)) return;
785808
786 var r = handleTouch(evt);
809 var r = detectSwipe(evt);
810 // prevent the click if it is a swipe
787811 if (r != null) return r;
788812
789813 resetModel(evt);
795819 document.body.appendChild(fileElem.parent()[0]);
796820 fileElem.bind('change', changeFn);
797821 }
798 } catch(e){/*ignore*/}
822 } catch (e) {/*ignore*/
823 }
799824
800825 if (isDelayedClickSupported(navigator.userAgent)) {
801826 setTimeout(function () {
808833 return false;
809834 }
810835
811 function handleTouch(evt) {
836
837 var initialTouchStartY = 0;
838 var initialTouchStartX = 0;
839
840 function detectSwipe(evt) {
812841 var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
813 if (evt.type === 'touchstart') {
814 initialTouchStartY = touches ? touches[0].clientY : 0;
815 return true; // don't block event default
816 } else {
817 evt.stopPropagation();
818 evt.preventDefault();
819
820 // prevent scroll from triggering event
821 if (evt.type === 'touchend') {
822 var currentLocation = touches ? touches[0].clientY : 0;
823 if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
842 if (touches) {
843 if (evt.type === 'touchstart') {
844 initialTouchStartX = touches[0].clientX;
845 initialTouchStartY = touches[0].clientY;
846 return true; // don't block event default
847 } else {
848 // prevent scroll from triggering event
849 if (evt.type === 'touchend') {
850 var currentX = touches[0].clientX;
851 var currentY = touches[0].clientY;
852 if ((Math.abs(currentX - initialTouchStartX) > 20) ||
853 (Math.abs(currentY - initialTouchStartY) > 20)) {
854 evt.stopPropagation();
855 evt.preventDefault();
856 return false;
857 }
858 }
859 return true;
824860 }
825861 }
826862 }
10511087 var size = resizeParams;
10521088 if (directiveName === 'ngfThumbnail') {
10531089 if (!size) {
1054 size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
1090 size = {
1091 width: elem[0].naturalWidth || elem[0].clientWidth,
1092 height: elem[0].naturalHeight || elem[0].clientHeight
1093 };
10551094 }
10561095 if (size.width === 0 && window.getComputedStyle) {
10571096 var style = getComputedStyle(elem[0]);
1058 size = {
1059 width: parseInt(style.width.slice(0, -2)),
1060 height: parseInt(style.height.slice(0, -2))
1061 };
1097 if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) {
1098 size = {
1099 width: parseInt(style.width.slice(0, -2)),
1100 height: parseInt(style.height.slice(0, -2))
1101 };
1102 }
10621103 }
10631104 }
10641105
10731114 if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
10741115 (!isBackground || file.type.indexOf('image') === 0)) {
10751116 if (size && Upload.isResizeSupported()) {
1076 Upload.resize(file, size.width, size.height, size.quality).then(
1117 size.resizeIf = function (width, height) {
1118 return Upload.attrGetter('ngfResizeIf', attr, scope,
1119 {$width: width, $height: height, $file: file});
1120 };
1121 Upload.resize(file, size).then(
10771122 function (f) {
10781123 constructDataUrl(f);
10791124 }, function (e) {
11351180 }]);
11361181
11371182 ngFileUpload.config(['$compileProvider', function ($compileProvider) {
1138 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1139 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
1183 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);
1184 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/);
11401185 }]);
11411186
11421187 ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
12281273 if (ngModel) {
12291274 ngModel.$formatters.push(function (files) {
12301275 if (ngModel.$dirty) {
1276 var filesArray = files;
12311277 if (files && !angular.isArray(files)) {
1232 files = [files];
1233 }
1234 upload.validate(files, 0, ngModel, attr, scope).then(function () {
1235 upload.applyModelValidation(ngModel, files);
1278 filesArray = [files];
1279 }
1280 upload.validate(filesArray, 0, ngModel, attr, scope).then(function () {
1281 upload.applyModelValidation(ngModel, filesArray);
12361282 });
12371283 }
1284 return files;
12381285 });
12391286 }
12401287 };
12841331 return upload.attrGetter(name, attr, scope, params);
12851332 };
12861333
1334 var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' ');
1335 var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope);
1336
12871337 if (files == null || files.length === 0) {
1288 return upload.emptyPromise(ngModel);
1338 return upload.emptyPromise({'validFiles': files, 'invalidFiles': []});
12891339 }
12901340
12911341 files = files.length === undefined ? [files] : files.slice(0);
1342 var invalidFiles = [];
12921343
12931344 function validateSync(name, validationName, fn) {
12941345 if (files) {
12991350 var val = upload.getValidationAttr(attr, scope, name, validationName, file);
13001351 if (val != null) {
13011352 if (!fn(file, val, i)) {
1302 file.$error = name;
1303 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1304 file.$errorParam = val;
1305 files.splice(i, 1);
1306 valid = false;
1353 if (ignoredErrors.indexOf(name) === -1) {
1354 file.$error = name;
1355 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1356 file.$errorParam = val;
1357 if (invalidFiles.indexOf(file) === -1) {
1358 invalidFiles.push(file);
1359 }
1360 if (!runAllValidation) {
1361 files.splice(i, 1);
1362 }
1363 valid = false;
1364 } else {
1365 files.splice(i, 1);
1366 }
13071367 }
13081368 }
13091369 }
13141374 }
13151375 }
13161376
1317 validateSync('maxFiles', null, function (file, val, i) {
1318 return prevLength + i < val;
1319 });
13201377 validateSync('pattern', null, upload.validatePattern);
13211378 validateSync('minSize', 'size.min', function (file, val) {
13221379 return file.size + 0.1 >= upload.translateScalars(val);
13391396 });
13401397
13411398 if (!files.length) {
1342 return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
1399 return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles});
13431400 }
13441401
13451402 function validateAsync(name, validationName, type, asyncFn, fn) {
13461403 function resolveResult(defer, file, val) {
1347 if (val != null) {
1348 asyncFn(file, val).then(function (d) {
1349 if (!fn(d, val)) {
1404 function resolveInternal(fn) {
1405 if (fn()) {
1406 if (ignoredErrors.indexOf(name) === -1) {
13501407 file.$error = name;
13511408 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
13521409 file.$errorParam = val;
1353 defer.reject();
1410 if (invalidFiles.indexOf(file) === -1) {
1411 invalidFiles.push(file);
1412 }
1413 if (!runAllValidation) {
1414 var i = files.indexOf(file);
1415 if (i > -1) files.splice(i, 1);
1416 }
1417 defer.resolve(false);
13541418 } else {
1355 defer.resolve();
1419 var j = files.indexOf(file);
1420 if (j > -1) files.splice(j, 1);
1421 defer.resolve(true);
13561422 }
1423 } else {
1424 defer.resolve(true);
1425 }
1426 }
1427
1428 if (val != null) {
1429 asyncFn(file, val).then(function (d) {
1430 resolveInternal(function () {
1431 return !fn(d, val);
1432 });
13571433 }, function () {
1358 if (attrGetter('ngfValidateForce', {$file: file})) {
1359 file.$error = name;
1360 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
1361 file.$errorParam = val;
1362 defer.reject();
1363 } else {
1364 defer.resolve();
1365 }
1434 resolveInternal(function () {
1435 return attrGetter('ngfValidateForce', {$file: file});
1436 });
13661437 });
13671438 } else {
1368 defer.resolve();
1369 }
1370 }
1371
1372 var promises = [upload.emptyPromise()];
1439 defer.resolve(true);
1440 }
1441 }
1442
1443 var promises = [upload.emptyPromise(true)];
13731444 if (files) {
13741445 files = files.length === undefined ? [files] : files;
13751446 angular.forEach(files, function (file) {
13761447 var defer = $q.defer();
13771448 promises.push(defer.promise);
13781449 if (type && (file.type == null || file.type.search(type) !== 0)) {
1379 defer.resolve();
1450 defer.resolve(true);
13801451 return;
13811452 }
13821453 if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {
13841455 resolveResult(defer, file,
13851456 attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));
13861457 }, function () {
1387 defer.reject();
1458 defer.resolve(false);
13881459 });
13891460 } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {
13901461 upload.mediaDuration(file).then(function (d) {
13911462 resolveResult(defer, file,
13921463 attrGetter('ngfDuration', {$file: file, $duration: d}));
13931464 }, function () {
1394 defer.reject();
1465 defer.resolve(false);
13951466 });
13961467 } else {
13971468 resolveResult(defer, file,
13981469 upload.getValidationAttr(attr, scope, name, validationName, file));
13991470 }
14001471 });
1401 return $q.all(promises).then(function () {
1402 ngModel.$ngfValidations.push({name: name, valid: true});
1403 }, function () {
1404 ngModel.$ngfValidations.push({name: name, valid: false});
1405 });
1406 }
1472 }
1473 var deffer = $q.defer();
1474 $q.all(promises).then(function (values) {
1475 var isValid = true;
1476 for (var i = 0; i < values.length; i++) {
1477 if (!values[i]) {
1478 isValid = false;
1479 break;
1480 }
1481 }
1482 ngModel.$ngfValidations.push({name: name, valid: isValid});
1483 deffer.resolve(isValid);
1484 });
1485 return deffer.promise;
14071486 }
14081487
14091488 var deffer = $q.defer();
14101489 var promises = [];
14111490
1412 promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/,
1491 promises.push(validateAsync('maxHeight', 'height.max', /image/,
14131492 this.imageDimensions, function (d, val) {
14141493 return d.height <= val;
1415 })));
1416 promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/,
1494 }));
1495 promises.push(validateAsync('minHeight', 'height.min', /image/,
14171496 this.imageDimensions, function (d, val) {
14181497 return d.height >= val;
1419 })));
1420 promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/,
1498 }));
1499 promises.push(validateAsync('maxWidth', 'width.max', /image/,
14211500 this.imageDimensions, function (d, val) {
14221501 return d.width <= val;
1423 })));
1424 promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/,
1502 }));
1503 promises.push(validateAsync('minWidth', 'width.min', /image/,
14251504 this.imageDimensions, function (d, val) {
14261505 return d.width >= val;
1427 })));
1428 promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/,
1506 }));
1507 promises.push(validateAsync('dimensions', null, /image/,
14291508 function (file, val) {
14301509 return upload.emptyPromise(val);
14311510 }, function (r) {
14321511 return r;
1433 })));
1434 promises.push(upload.happyPromise(validateAsync('ratio', null, /image/,
1512 }));
1513 promises.push(validateAsync('ratio', null, /image/,
14351514 this.imageDimensions, function (d, val) {
14361515 var split = val.toString().split(','), valid = false;
14371516 for (var i = 0; i < split.length; i++) {
1438 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
1517 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) {
14391518 valid = true;
14401519 }
14411520 }
14421521 return valid;
1443 })));
1444 promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/,
1522 }));
1523 promises.push(validateAsync('maxRatio', 'ratio.max', /image/,
14451524 this.imageDimensions, function (d, val) {
14461525 return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
1447 })));
1448 promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/,
1526 }));
1527 promises.push(validateAsync('minRatio', 'ratio.min', /image/,
14491528 this.imageDimensions, function (d, val) {
14501529 return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
1451 })));
1452 promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/,
1530 }));
1531 promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/,
14531532 this.mediaDuration, function (d, val) {
14541533 return d <= upload.translateScalars(val);
1455 })));
1456 promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/,
1534 }));
1535 promises.push(validateAsync('minDuration', 'duration.min', /audio|video/,
14571536 this.mediaDuration, function (d, val) {
14581537 return d >= upload.translateScalars(val);
1459 })));
1460 promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/,
1538 }));
1539 promises.push(validateAsync('duration', null, /audio|video/,
14611540 function (file, val) {
14621541 return upload.emptyPromise(val);
14631542 }, function (r) {
14641543 return r;
1465 })));
1466
1467 promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null,
1544 }));
1545
1546 promises.push(validateAsync('validateAsyncFn', null, null,
14681547 function (file, val) {
14691548 return val;
14701549 }, function (r) {
14711550 return r === true || r === null || r === '';
1472 })));
1473
1474 return $q.all(promises).then(function () {
1475 deffer.resolve(ngModel, ngModel.$ngfValidations);
1551 }));
1552
1553 $q.all(promises).then(function () {
1554
1555 if (runAllValidation) {
1556 for (var i = 0; i < files.length; i++) {
1557 var file = files[i];
1558 if (file.$error) {
1559 files.splice(i--, 1);
1560 }
1561 }
1562 }
1563
1564 runAllValidation = false;
1565 validateSync('maxFiles', null, function (file, val, i) {
1566 return prevLength + i < val;
1567 });
1568
1569 deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles});
14761570 });
1571 return deffer.promise;
14771572 };
14781573
14791574 upload.imageDimensions = function (file) {
14981593 .css('max-width', 'none !important').css('max-height', 'none !important');
14991594
15001595 function success() {
1501 var width = img[0].clientWidth;
1502 var height = img[0].clientHeight;
1596 var width = img[0].naturalWidth || img[0].clientWidth;
1597 var height = img[0].naturalHeight || img[0].clientHeight;
15031598 img.remove();
15041599 file.$ngfWidth = width;
15051600 file.$ngfHeight = height;
15131608
15141609 img.on('load', success);
15151610 img.on('error', error);
1516 var count = 0;
1517
1518 function checkLoadError() {
1611
1612 var secondsCounter = 0;
1613 function checkLoadErrorInCaseOfNoCallback() {
15191614 $timeout(function () {
15201615 if (img[0].parentNode) {
15211616 if (img[0].clientWidth) {
15221617 success();
1523 } else if (count > 10) {
1618 } else if (secondsCounter++ > 10) {
15241619 error();
15251620 } else {
1526 checkLoadError();
1621 checkLoadErrorInCaseOfNoCallback();
15271622 }
15281623 }
15291624 }, 1000);
15301625 }
15311626
1532 checkLoadError();
1627 checkLoadErrorInCaseOfNoCallback();
15331628
15341629 angular.element(document.getElementsByTagName('body')[0]).append(img);
15351630 }, function () {
16401735 var deferred = $q.defer();
16411736 var canvasElement = document.createElement('canvas');
16421737 var imageElement = document.createElement('img');
1738 imageElement.setAttribute('style', 'visibility:hidden;position:fixed;z-index:-100000');
1739 document.body.appendChild(imageElement);
16431740
16441741 imageElement.onload = function () {
1645 if (resizeIf != null && resizeIf(imageElement.width, imageElement.height) === false) {
1742 var imgWidth = imageElement.width, imgHeight = imageElement.height;
1743 imageElement.parentNode.removeChild(imageElement);
1744 if (resizeIf != null && resizeIf(imgWidth, imgHeight) === false) {
16461745 deferred.reject('resizeIf');
16471746 return;
16481747 }
16491748 try {
16501749 if (ratio) {
16511750 var ratioFloat = upload.ratioToFloat(ratio);
1652 var imgRatio = imageElement.width / imageElement.height;
1751 var imgRatio = imgWidth / imgHeight;
16531752 if (imgRatio < ratioFloat) {
1654 width = imageElement.width;
1753 width = imgWidth;
16551754 height = width / ratioFloat;
16561755 } else {
1657 height = imageElement.height;
1756 height = imgHeight;
16581757 width = height * ratioFloat;
16591758 }
16601759 }
16611760 if (!width) {
1662 width = imageElement.width;
1761 width = imgWidth;
16631762 }
16641763 if (!height) {
1665 height = imageElement.height;
1666 }
1667 var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
1764 height = imgHeight;
1765 }
1766 var dimensions = calculateAspectRatioFit(imgWidth, imgHeight, width, height, centerCrop);
16681767 canvasElement.width = Math.min(dimensions.width, width);
16691768 canvasElement.height = Math.min(dimensions.height, height);
16701769 var context = canvasElement.getContext('2d');
16771776 }
16781777 };
16791778 imageElement.onerror = function () {
1779 imageElement.parentNode.removeChild(imageElement);
16801780 deferred.reject();
16811781 };
16821782 imageElement.src = imagen;
17131813 });
17141814 }
17151815
1716 upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) {
1816 upload.resize = function (file, options) {
17171817 if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
17181818
17191819 var deferred = $q.defer();
17201820 upload.dataUrl(file, true).then(function (url) {
1721 resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf)
1821 resize(url, options.width, options.height, options.quality, options.type || file.type,
1822 options.ratio, options.centerCrop, options.resizeIf)
17221823 .then(function (dataUrl) {
1723 if (file.type === 'image/jpeg' && restoreExif) {
1824 if (file.type === 'image/jpeg' && options.restoreExif !== false) {
17241825 try {
17251826 dataUrl = upload.restoreExif(url, dataUrl);
17261827 } catch (e) {
17491850 }]);
17501851
17511852 (function () {
1752 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
1753 function ($parse, $timeout, $location, Upload, $http, $q) {
1853 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$window', 'Upload', '$http', '$q',
1854 function ($parse, $timeout, $window, Upload, $http, $q) {
17541855 return {
17551856 restrict: 'AEC',
17561857 require: '?ngModel',
17571858 link: function (scope, elem, attr, ngModel) {
1758 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
1859 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, Upload, $http, $q);
17591860 }
17601861 };
17611862 }]);
17801881 };
17811882 }]);
17821883
1783 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
1884 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $window, upload, $http, $q) {
17841885 var available = dropAvailable();
17851886
17861887 var attrGetter = function (name, scope, params) {
18561957 if (stopPropagation(scope)) evt.stopPropagation();
18571958 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
18581959 actualDragOverClass = null;
1859 var items = evt.dataTransfer.items;
1860 var html;
1861 try {
1862 html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
1863 } catch (e) {/* Fix IE11 that throw error calling getData */
1864 }
1865
1866 extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
1867 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
1868 if (files.length) {
1869 updateModel(files, evt);
1870 } else {
1871 extractFilesFromHtml('dropUrl', html).then(function (files) {
1872 updateModel(files, evt);
1873 });
1874 }
1875 });
1960 extractFilesAndUpdateModel(evt.dataTransfer, evt, 'dropUrl');
18761961 }, false);
18771962 elem[0].addEventListener('paste', function (evt) {
18781963 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
18801965 evt.preventDefault();
18811966 }
18821967 if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
1883 var files = [];
1884 var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
1885 if (clipboard && clipboard.items) {
1886 for (var k = 0; k < clipboard.items.length; k++) {
1887 if (clipboard.items[k].type.indexOf('image') !== -1) {
1888 files.push(clipboard.items[k].getAsFile());
1889 }
1890 }
1891 }
1892 if (files.length) {
1893 updateModel(files, evt);
1894 } else {
1895 extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
1896 updateModel(files, evt);
1897 });
1898 }
1968 extractFilesAndUpdateModel(evt.clipboardData || evt.originalEvent.clipboardData, evt, 'pasteUrl');
18991969 }, false);
19001970
19011971 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
19081978 });
19091979 }
19101980
1981 function extractFilesAndUpdateModel(source, evt, updateOnType) {
1982 if (!source) return;
1983 // html needs to be calculated on the same process otherwise the data will be wiped
1984 // after promise resolve or setTimeout.
1985 var html;
1986 try {
1987 html = source && source.getData && source.getData('text/html');
1988 } catch (e) {/* Fix IE11 that throw error calling getData */
1989 }
1990 extractFiles(source.items, source.files, attrGetter('ngfAllowDir', scope) !== false,
1991 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
1992 if (files.length) {
1993 updateModel(files, evt);
1994 } else {
1995 extractFilesFromHtml(updateOnType, html).then(function (files) {
1996 updateModel(files, evt);
1997 });
1998 }
1999 });
2000 }
2001
19112002 function updateModel(files, evt) {
19122003 upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
19132004 }
19142005
19152006 function extractFilesFromHtml(updateOn, html) {
1916 if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
2007 if (!upload.shouldUpdateOn(updateOn, attr, scope) || typeof html !== 'string') return upload.rejectPromise([]);
19172008 var urls = [];
19182009 html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
19192010 urls.push(src);
19642055 }
19652056
19662057 function extractFiles(items, fileList, allowDir, multiple) {
1967 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
1968 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
2058 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles');
2059 if (maxFiles == null) {
2060 maxFiles = Number.MAX_VALUE;
2061 }
2062 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize');
2063 if (maxTotalSize == null) {
2064 maxTotalSize = Number.MAX_VALUE;
2065 }
19692066 var includeDir = attrGetter('ngfIncludeDir', scope);
19702067 var files = [], totalSize = 0;
19712068
19762073 var promises = [upload.emptyPromise()];
19772074 if (includeDir) {
19782075 var file = {type: 'directory'};
1979 file.name = file.path = (path || '') + entry.name + entry.name;
2076 file.name = file.path = (path || '') + entry.name;
19802077 files.push(file);
19812078 }
19822079 var dirReader = entry.createReader();
20302127
20312128 var promises = [upload.emptyPromise()];
20322129
2033 if (items && items.length > 0 && $location.protocol() !== 'file') {
2130 if (items && items.length > 0 && $window.location.protocol !== 'file:') {
20342131 for (var i = 0; i < items.length; i++) {
20352132 if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
20362133 var entry = items[i].webkitGetAsEntry();
0 /*! 12.2.13 */
1 !window.XMLHttpRequest||window.FileAPI&&FileAPI.shouldLoad||(window.XMLHttpRequest.prototype.setRequestHeader=function(a){return function(b,c){if("__setXHR_"===b){var d=c(this);d instanceof Function&&d(this)}else a.apply(this,arguments)}}(window.XMLHttpRequest.prototype.setRequestHeader));var ngFileUpload=angular.module("ngFileUpload",[]);ngFileUpload.version="12.2.13",ngFileUpload.service("UploadBase",["$http","$q","$timeout",function(a,b,c){function d(d){function e(a){j.notify&&j.notify(a),k.progressFunc&&c(function(){k.progressFunc(a)})}function h(a){return null!=d._start&&g?{loaded:a.loaded+d._start,total:d._file&&d._file.size||a.total,type:a.type,config:d,lengthComputable:!0,target:a.target}:a}function i(){a(d).then(function(a){if(g&&d._chunkSize&&!d._finished&&d._file){var b=d._file&&d._file.size||0;e({loaded:Math.min(d._end,b),total:b,config:d,type:"progress"}),f.upload(d,!0)}else d._finished&&delete d._finished,j.resolve(a)},function(a){j.reject(a)},function(a){j.notify(a)})}d.method=d.method||"POST",d.headers=d.headers||{};var j=d._deferred=d._deferred||b.defer(),k=j.promise;return d.disableProgress||(d.headers.__setXHR_=function(){return function(a){a&&a.upload&&a.upload.addEventListener&&(d.__XHR=a,d.xhrFn&&d.xhrFn(a),a.upload.addEventListener("progress",function(a){a.config=d,e(h(a))},!1),a.upload.addEventListener("load",function(a){a.lengthComputable&&(a.config=d,e(h(a)))},!1))}}),g?d._chunkSize&&d._end&&!d._finished?(d._start=d._end,d._end+=d._chunkSize,i()):d.resumeSizeUrl?a.get(d.resumeSizeUrl).then(function(a){d._start=d.resumeSizeResponseReader?d.resumeSizeResponseReader(a.data):parseInt((null==a.data.size?a.data:a.data.size).toString()),d._chunkSize&&(d._end=d._start+d._chunkSize),i()},function(a){throw a}):d.resumeSize?d.resumeSize().then(function(a){d._start=a,d._chunkSize&&(d._end=d._start+d._chunkSize),i()},function(a){throw a}):(d._chunkSize&&(d._start=0,d._end=d._start+d._chunkSize),i()):i(),k.success=function(a){return k.then(function(b){a(b.data,b.status,b.headers,d)}),k},k.error=function(a){return k.then(null,function(b){a(b.data,b.status,b.headers,d)}),k},k.progress=function(a){return k.progressFunc=a,k.then(null,null,function(b){a(b)}),k},k.abort=k.pause=function(){return d.__XHR&&c(function(){d.__XHR.abort()}),k},k.xhr=function(a){return d.xhrFn=function(b){return function(){b&&b.apply(k,arguments),a.apply(k,arguments)}}(d.xhrFn),k},f.promisesCount++,k["finally"]&&k["finally"]instanceof Function&&k["finally"](function(){f.promisesCount--}),k}function e(a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}var f=this;f.promisesCount=0,this.isResumeSupported=function(){return window.Blob&&window.Blob.prototype.slice};var g=this.isResumeSupported();this.isUploadInProgress=function(){return f.promisesCount>0},this.rename=function(a,b){return a.ngfName=b,a},this.jsonBlob=function(a){null==a||angular.isString(a)||(a=JSON.stringify(a));var b=new window.Blob([a],{type:"application/json"});return b._ngfBlob=!0,b},this.json=function(a){return angular.toJson(a)},this.isFile=function(a){return null!=a&&(a instanceof window.Blob||a.flashId&&a.name&&a.size)},this.upload=function(a,b){function c(b,c){if(b._ngfBlob)return b;if(a._file=a._file||b,null!=a._start&&g){a._end&&a._end>=b.size&&(a._finished=!0,a._end=b.size);var d=b.slice(a._start,a._end||b.size);return d.name=b.name,d.ngfName=b.ngfName,a._chunkSize&&(c.append("_chunkSize",a._chunkSize),c.append("_currentChunkSize",a._end-a._start),c.append("_chunkNumber",Math.floor(a._start/a._chunkSize)),c.append("_totalSize",a._file.size)),d}return b}function h(b,d,e){if(void 0!==d)if(angular.isDate(d)&&(d=d.toISOString()),angular.isString(d))b.append(e,d);else if(f.isFile(d)){var g=c(d,b),i=e.split(",");i[1]&&(g.ngfName=i[1].replace(/^\s+|\s+$/g,""),e=i[0]),a._fileKey=a._fileKey||e,b.append(e,g,g.ngfName||g.name)}else if(angular.isObject(d)){if(d.$$ngfCircularDetection)throw"ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: "+e;d.$$ngfCircularDetection=!0;try{for(var j in d)if(d.hasOwnProperty(j)&&"$$ngfCircularDetection"!==j){var k=null==a.objectKey?"[i]":a.objectKey;d.length&&parseInt(j)>-1&&(k=null==a.arrayKey?k:a.arrayKey),h(b,d[j],e+k.replace(/[ik]/g,j))}}finally{delete d.$$ngfCircularDetection}}else b.append(e,d)}function i(){a._chunkSize=f.translateScalars(a.resumeChunkSize),a._chunkSize=a._chunkSize?parseInt(a._chunkSize.toString()):null,a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?angular.isArray(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(b){var c,d=new window.FormData;b=b||a.fields||{},a.file&&(b.file=a.file);for(c in b)if(b.hasOwnProperty(c)){var e=b[c];a.formDataAppender?a.formDataAppender(d,c,e):h(d,e,c)}return d})}return b||(a=e(a)),a._isDigested||(a._isDigested=!0,i()),d(a)},this.http=function(b){return b=e(b),b.transformRequest=b.transformRequest||function(b){return window.ArrayBuffer&&b instanceof window.ArrayBuffer||b instanceof window.Blob?b:a.defaults.transformRequest[0].apply(this,arguments)},b._chunkSize=f.translateScalars(b.resumeChunkSize),b._chunkSize=b._chunkSize?parseInt(b._chunkSize.toString()):null,d(b)},this.translateScalars=function(a){if(angular.isString(a)){if(a.search(/kb/i)===a.length-2)return parseFloat(1024*a.substring(0,a.length-2));if(a.search(/mb/i)===a.length-2)return parseFloat(1048576*a.substring(0,a.length-2));if(a.search(/gb/i)===a.length-2)return parseFloat(1073741824*a.substring(0,a.length-2));if(a.search(/b/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/s/i)===a.length-1)return parseFloat(a.substring(0,a.length-1));if(a.search(/m/i)===a.length-1)return parseFloat(60*a.substring(0,a.length-1));if(a.search(/h/i)===a.length-1)return parseFloat(3600*a.substring(0,a.length-1))}return a},this.urlToBlob=function(c){var d=b.defer();return a({url:c,method:"get",responseType:"arraybuffer"}).then(function(a){var b=new Uint8Array(a.data),e=a.headers("content-type")||"image/WebP",f=new window.Blob([b],{type:e}),g=c.match(/.*\/(.+?)(\?.*)?$/);g.length>1&&(f.name=g[1]),d.resolve(f)},function(a){d.reject(a)}),d.promise},this.setDefaults=function(a){this.defaults=a||{}},this.defaults={},this.version=ngFileUpload.version}]),ngFileUpload.service("Upload",["$parse","$timeout","$compile","$q","UploadExif",function(a,b,c,d,e){function f(a,b,c){var e=[i.emptyPromise()];return angular.forEach(a,function(d,f){0===d.type.indexOf("image/jpeg")&&i.attrGetter("ngfFixOrientation",b,c,{$file:d})&&e.push(i.happyPromise(i.applyExifRotation(d),d).then(function(b){a.splice(f,1,b)}))}),d.all(e)}function g(a,b,c,e){var f=i.attrGetter("ngfResize",b,c);if(!f||!i.isResizeSupported()||!a.length)return i.emptyPromise();if(f instanceof Function){var g=d.defer();return f(a).then(function(d){h(d,a,b,c,e).then(function(a){g.resolve(a)},function(a){g.reject(a)})},function(a){g.reject(a)})}return h(f,a,b,c,e)}function h(a,b,c,e,f){function g(d,g){if(0===d.type.indexOf("image")){if(a.pattern&&!i.validatePattern(d,a.pattern))return;a.resizeIf=function(a,b){return i.attrGetter("ngfResizeIf",c,e,{$width:a,$height:b,$file:d})};var j=i.resize(d,a);h.push(j),j.then(function(a){b.splice(g,1,a)},function(a){d.$error="resize",(d.$errorMessages=d.$errorMessages||{}).resize=!0,d.$errorParam=(a?(a.message?a.message:a)+": ":"")+(d&&d.name),f.$ngfValidations.push({name:"resize",valid:!1}),i.applyModelValidation(f,b)})}}for(var h=[i.emptyPromise()],j=0;j<b.length;j++)g(b[j],j);return d.all(h)}var i=e;return i.getAttrWithDefaults=function(a,b){if(null!=a[b])return a[b];var c=i.defaults[b];return null==c?c:angular.isString(c)?c:JSON.stringify(c)},i.attrGetter=function(b,c,d,e){var f=this.getAttrWithDefaults(c,b);if(!d)return f;try{return e?a(f)(d,e):a(f)(d)}catch(g){if(b.search(/min|max|pattern/i))return f;throw g}},i.shouldUpdateOn=function(a,b,c){var d=i.attrGetter("ngfModelOptions",b,c);return d&&d.updateOn?d.updateOn.split(" ").indexOf(a)>-1:!0},i.emptyPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.resolve.apply(a,c)}),a.promise},i.rejectPromise=function(){var a=d.defer(),c=arguments;return b(function(){a.reject.apply(a,c)}),a.promise},i.happyPromise=function(a,c){var e=d.defer();return a.then(function(a){e.resolve(a)},function(a){b(function(){throw a}),e.resolve(c)}),e.promise},i.updateModel=function(c,d,e,h,j,k,l){function m(f,g,j,l,m){d.$$ngfPrevValidFiles=f,d.$$ngfPrevInvalidFiles=g;var n=f&&f.length?f[0]:null,o=g&&g.length?g[0]:null;c&&(i.applyModelValidation(c,f),c.$setViewValue(m?n:f)),h&&a(h)(e,{$files:f,$file:n,$newFiles:j,$duplicateFiles:l,$invalidFiles:g,$invalidFile:o,$event:k});var p=i.attrGetter("ngfModelInvalid",d);p&&b(function(){a(p).assign(e,m?o:g)}),b(function(){})}function n(){function a(a,b){return a.name===b.name&&(a.$ngfOrigSize||a.size)===(b.$ngfOrigSize||b.size)&&a.type===b.type}function b(b){var c;for(c=0;c<r.length;c++)if(a(b,r[c]))return!0;for(c=0;c<s.length;c++)if(a(b,s[c]))return!0;return!1}if(j){q=[],t=[];for(var c=0;c<j.length;c++)b(j[c])?t.push(j[c]):q.push(j[c])}}function o(a){return angular.isArray(a)?a:[a]}function p(){function a(){b(function(){m(w?r.concat(v):v,w?s.concat(u):u,j,t,x)},z&&z.debounce?z.debounce.change||z.debounce:0)}var f=y?q:v;g(f,d,e,c).then(function(){y?i.validate(q,w?r.length:0,c,d,e).then(function(b){v=b.validsFiles,u=b.invalidsFiles,a()}):a()},function(){for(var b=0;b<f.length;b++){var c=f[b];if("resize"===c.$error){var d=v.indexOf(c);d>-1&&(v.splice(d,1),u.push(c)),a()}}})}var q,r,s,t=[],u=[],v=[];r=d.$$ngfPrevValidFiles||[],s=d.$$ngfPrevInvalidFiles||[],c&&c.$modelValue&&(r=o(c.$modelValue));var w=i.attrGetter("ngfKeep",d,e);q=(j||[]).slice(0),("distinct"===w||i.attrGetter("ngfKeepDistinct",d,e)===!0)&&n(d,e);var x=!w&&!i.attrGetter("ngfMultiple",d,e)&&!i.attrGetter("multiple",d);if(!w||q.length){i.attrGetter("ngfBeforeModelChange",d,e,{$files:j,$file:j&&j.length?j[0]:null,$newFiles:q,$duplicateFiles:t,$event:k});var y=i.attrGetter("ngfValidateAfterResize",d,e),z=i.attrGetter("ngfModelOptions",d,e);i.validate(q,w?r.length:0,c,d,e).then(function(a){l?m(q,[],j,t,x):(z&&z.allowInvalid||y?v=q:(v=a.validFiles,u=a.invalidFiles),i.attrGetter("ngfFixOrientation",d,e)&&i.isExifSupported()?f(v,d,e).then(function(){p()}):p())})}},i}]),ngFileUpload.directive("ngfSelect",["$parse","$timeout","$compile","Upload",function(a,b,c,d){function e(a){var b=a.match(/Android[^\d]*(\d+)\.(\d+)/);if(b&&b.length>2){var c=d.defaults.androidFixMinorVersion||4;return parseInt(b[1])<4||parseInt(b[1])===c&&parseInt(b[2])<c}return-1===a.indexOf("Chrome")&&/.*Windows.*Safari.*/.test(a)}function f(a,b,c,d,f,h,i,j){function k(){return"input"===b[0].tagName.toLowerCase()&&c.type&&"file"===c.type.toLowerCase()}function l(){return t("ngfChange")||t("ngfSelect")}function m(b){if(j.shouldUpdateOn("change",c,a)){var e=b.__files_||b.target&&b.target.files,f=[];if(!e)return;for(var g=0;g<e.length;g++)f.push(e[g]);j.updateModel(d,c,a,l(),f.length?f:null,b)}}function n(a,d){function e(b){a.attr("id","ngf-"+b),d.attr("id","ngf-label-"+b)}for(var f=0;f<b[0].attributes.length;f++){var g=b[0].attributes[f];"type"!==g.name&&"class"!==g.name&&"style"!==g.name&&("id"===g.name?(e(g.value),u.push(c.$observe("id",e))):a.attr(g.name,g.value||"required"!==g.name&&"multiple"!==g.name?g.value:g.name))}}function o(){if(k())return b;var a=angular.element('<input type="file">'),c=angular.element("<label>upload</label>");return c.css("visibility","hidden").css("position","absolute").css("overflow","hidden").css("width","0px").css("height","0px").css("border","none").css("margin","0px").css("padding","0px").attr("tabindex","-1"),n(a,c),g.push({el:b,ref:c}),document.body.appendChild(c.append(a)[0]),a}function p(c){if(b.attr("disabled"))return!1;if(!t("ngfSelectDisabled",a)){var d=q(c);if(null!=d)return d;r(c);try{k()||document.body.contains(x[0])||(g.push({el:b,ref:x.parent()}),document.body.appendChild(x.parent()[0]),x.bind("change",m))}catch(f){}return e(navigator.userAgent)?setTimeout(function(){x[0].click()},0):x[0].click(),!1}}function q(a){var b=a.changedTouches||a.originalEvent&&a.originalEvent.changedTouches;if(b){if("touchstart"===a.type)return w=b[0].clientX,v=b[0].clientY,!0;if("touchend"===a.type){var c=b[0].clientX,d=b[0].clientY;if(Math.abs(c-w)>20||Math.abs(d-v)>20)return a.stopPropagation(),a.preventDefault(),!1}return!0}}function r(b){j.shouldUpdateOn("click",c,a)&&x.val()&&(x.val(null),j.updateModel(d,c,a,l(),null,b,!0))}function s(a){if(x&&!x.attr("__ngf_ie10_Fix_")){if(!x[0].parentNode)return void(x=null);a.preventDefault(),a.stopPropagation(),x.unbind("click");var b=x.clone();return x.replaceWith(b),x=b,x.attr("__ngf_ie10_Fix_","true"),x.bind("change",m),x.bind("click",s),x[0].click(),!1}x.removeAttr("__ngf_ie10_Fix_")}var t=function(a,b){return j.attrGetter(a,c,b)};j.registerModelChangeValidator(d,c,a);var u=[];t("ngfMultiple")&&u.push(a.$watch(t("ngfMultiple"),function(){x.attr("multiple",t("ngfMultiple",a))})),t("ngfCapture")&&u.push(a.$watch(t("ngfCapture"),function(){x.attr("capture",t("ngfCapture",a))})),t("ngfAccept")&&u.push(a.$watch(t("ngfAccept"),function(){x.attr("accept",t("ngfAccept",a))})),u.push(c.$observe("accept",function(){x.attr("accept",t("accept"))}));var v=0,w=0,x=b;k()||(x=o()),x.bind("change",m),k()?b.bind("click",r):b.bind("click touchstart touchend",p),-1!==navigator.appVersion.indexOf("MSIE 10")&&x.bind("click",s),d&&d.$formatters.push(function(a){return(null==a||0===a.length)&&x.val()&&x.val(null),a}),a.$on("$destroy",function(){k()||x.parent().remove(),angular.forEach(u,function(a){a()})}),h(function(){for(var a=0;a<g.length;a++){var b=g[a];document.body.contains(b.el[0])||(g.splice(a,1),b.ref.remove())}}),window.FileAPI&&window.FileAPI.ngfFixIE&&window.FileAPI.ngfFixIE(b,x,m)}var g=[];return{restrict:"AEC",require:"?ngModel",link:function(e,g,h,i){f(e,g,h,i,a,b,c,d)}}}]),function(){function a(a){return"img"===a.tagName.toLowerCase()?"image":"audio"===a.tagName.toLowerCase()?"audio":"video"===a.tagName.toLowerCase()?"video":/./}function b(b,c,d,e,f,g,h,i){function j(a){var g=b.attrGetter("ngfNoObjectUrl",f,d);b.dataUrl(a,g)["finally"](function(){c(function(){var b=(g?a.$ngfDataUrl:a.$ngfBlobUrl)||a.$ngfDataUrl;i?e.css("background-image","url('"+(b||"")+"')"):e.attr("src",b),b?e.removeClass("ng-hide"):e.addClass("ng-hide")})})}c(function(){var c=d.$watch(f[g],function(c){var k=h;if("ngfThumbnail"===g&&(k||(k={width:e[0].naturalWidth||e[0].clientWidth,height:e[0].naturalHeight||e[0].clientHeight}),0===k.width&&window.getComputedStyle)){var l=getComputedStyle(e[0]);l.width&&l.width.indexOf("px")>-1&&l.height&&l.height.indexOf("px")>-1&&(k={width:parseInt(l.width.slice(0,-2)),height:parseInt(l.height.slice(0,-2))})}return angular.isString(c)?(e.removeClass("ng-hide"),i?e.css("background-image","url('"+c+"')"):e.attr("src",c)):void(!c||!c.type||0!==c.type.search(a(e[0]))||i&&0!==c.type.indexOf("image")?e.addClass("ng-hide"):k&&b.isResizeSupported()?(k.resizeIf=function(a,e){return b.attrGetter("ngfResizeIf",f,d,{$width:a,$height:e,$file:c})},b.resize(c,k).then(function(a){j(a)},function(a){throw a})):j(c))});d.$on("$destroy",function(){c()})})}ngFileUpload.service("UploadDataUrl",["UploadBase","$timeout","$q",function(a,b,c){var d=a;return d.base64DataUrl=function(a){if(angular.isArray(a)){var b=c.defer(),e=0;return angular.forEach(a,function(c){d.dataUrl(c,!0)["finally"](function(){if(e++,e===a.length){var c=[];angular.forEach(a,function(a){c.push(a.$ngfDataUrl)}),b.resolve(c,a)}})}),b.promise}return d.dataUrl(a,!0)},d.dataUrl=function(a,e){if(!a)return d.emptyPromise(a,a);if(e&&null!=a.$ngfDataUrl||!e&&null!=a.$ngfBlobUrl)return d.emptyPromise(e?a.$ngfDataUrl:a.$ngfBlobUrl,a);var f=e?a.$$ngfDataUrlPromise:a.$$ngfBlobUrlPromise;if(f)return f;var g=c.defer();return b(function(){if(window.FileReader&&a&&(!window.FileAPI||-1===navigator.userAgent.indexOf("MSIE 8")||a.size<2e4)&&(!window.FileAPI||-1===navigator.userAgent.indexOf("MSIE 9")||a.size<4e6)){var c=window.URL||window.webkitURL;if(c&&c.createObjectURL&&!e){var f;try{f=c.createObjectURL(a)}catch(h){return void b(function(){a.$ngfBlobUrl="",g.reject()})}b(function(){if(a.$ngfBlobUrl=f,f){g.resolve(f,a),d.blobUrls=d.blobUrls||[],d.blobUrlsTotalSize=d.blobUrlsTotalSize||0,d.blobUrls.push({url:f,size:a.size}),d.blobUrlsTotalSize+=a.size||0;for(var b=d.defaults.blobUrlsMaxMemory||268435456,e=d.defaults.blobUrlsMaxQueueSize||200;(d.blobUrlsTotalSize>b||d.blobUrls.length>e)&&d.blobUrls.length>1;){var h=d.blobUrls.splice(0,1)[0];c.revokeObjectURL(h.url),d.blobUrlsTotalSize-=h.size}}})}else{var i=new FileReader;i.onload=function(c){b(function(){a.$ngfDataUrl=c.target.result,g.resolve(c.target.result,a),b(function(){delete a.$ngfDataUrl},1e3)})},i.onerror=function(){b(function(){a.$ngfDataUrl="",g.reject()})},i.readAsDataURL(a)}}else b(function(){a[e?"$ngfDataUrl":"$ngfBlobUrl"]="",g.reject()})}),f=e?a.$$ngfDataUrlPromise=g.promise:a.$$ngfBlobUrlPromise=g.promise,f["finally"](function(){delete a[e?"$$ngfDataUrlPromise":"$$ngfBlobUrlPromise"]}),f},d}]),ngFileUpload.directive("ngfSrc",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfSrc",a.attrGetter("ngfResize",f,d),!1)}}}]),ngFileUpload.directive("ngfBackground",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfBackground",a.attrGetter("ngfResize",f,d),!0)}}}]),ngFileUpload.directive("ngfThumbnail",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){var g=a.attrGetter("ngfSize",f,d);b(a,c,d,e,f,"ngfThumbnail",g,a.attrGetter("ngfAsBackground",f,d))}}}]),ngFileUpload.config(["$compileProvider",function(a){a.imgSrcSanitizationWhitelist&&a.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/),a.aHrefSanitizationWhitelist&&a.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/)}]),ngFileUpload.filter("ngfDataUrl",["UploadDataUrl","$sce",function(a,b){return function(c,d,e){if(angular.isString(c))return b.trustAsResourceUrl(c);var f=c&&((d?c.$ngfDataUrl:c.$ngfBlobUrl)||c.$ngfDataUrl);return c&&!f?(!c.$ngfDataUrlFilterInProgress&&angular.isObject(c)&&(c.$ngfDataUrlFilterInProgress=!0,a.dataUrl(c,d)),""):(c&&delete c.$ngfDataUrlFilterInProgress,(c&&f?e?b.trustAsResourceUrl(f):f:c)||"")}}])}(),ngFileUpload.service("UploadValidate",["UploadDataUrl","$q","$timeout",function(a,b,c){function d(a){var b="",c=[];if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])b=a.substring(1,a.length-1);else{var e=a.split(",");if(e.length>1)for(var f=0;f<e.length;f++){var g=d(e[f]);g.regexp?(b+="("+g.regexp+")",f<e.length-1&&(b+="|")):c=c.concat(g.excludes)}else 0===a.indexOf("!")?c.push("^((?!"+d(a.substring(1)).regexp+").)*$"):(0===a.indexOf(".")&&(a="*"+a),b="^"+a.replace(new RegExp("[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]","g"),"\\$&")+"$",b=b.replace(/\\\*/g,".*").replace(/\\\?/g,"."))}return{regexp:b,excludes:c}}function e(a,b){null==b||a.$dirty||(a.$setDirty?a.$setDirty():a.$dirty=!0)}var f=a;return f.validatePattern=function(a,b){if(!b)return!0;var c=d(b),e=!0;if(c.regexp&&c.regexp.length){var f=new RegExp(c.regexp,"i");e=null!=a.type&&f.test(a.type)||null!=a.name&&f.test(a.name)}for(var g=c.excludes.length;g--;){var h=new RegExp(c.excludes[g],"i");e=e&&(null==a.type||h.test(a.type))&&(null==a.name||h.test(a.name))}return e},f.ratioToFloat=function(a){var b=a.toString(),c=b.search(/[x:]/i);return b=c>-1?parseFloat(b.substring(0,c))/parseFloat(b.substring(c+1)):parseFloat(b)},f.registerModelChangeValidator=function(a,b,c){a&&a.$formatters.push(function(d){if(a.$dirty){var e=d;d&&!angular.isArray(d)&&(e=[d]),f.validate(e,0,a,b,c).then(function(){f.applyModelValidation(a,e)})}return d})},f.applyModelValidation=function(a,b){e(a,b),angular.forEach(a.$ngfValidations,function(b){a.$setValidity(b.name,b.valid)})},f.getValidationAttr=function(a,b,c,d,e){var g="ngf"+c[0].toUpperCase()+c.substr(1),h=f.attrGetter(g,a,b,{$file:e});if(null==h&&(h=f.attrGetter("ngfValidate",a,b,{$file:e}))){var i=(d||c).split(".");h=h[i[0]],i.length>1&&(h=h&&h[i[1]])}return h},f.validate=function(a,c,d,e,g){function h(b,c,h){if(a){for(var i=a.length,j=null;i--;){var n=a[i];if(n){var o=f.getValidationAttr(e,g,b,c,n);null!=o&&(h(n,o,i)||(-1===k.indexOf(b)?(n.$error=b,(n.$errorMessages=n.$errorMessages||{})[b]=!0,n.$errorParam=o,-1===m.indexOf(n)&&m.push(n),l||a.splice(i,1),j=!1):a.splice(i,1)))}}null!==j&&d.$ngfValidations.push({name:b,valid:j})}}function i(c,h,i,n,o){function p(b,d,e){function f(f){if(f())if(-1===k.indexOf(c)){if(d.$error=c,(d.$errorMessages=d.$errorMessages||{})[c]=!0,d.$errorParam=e,-1===m.indexOf(d)&&m.push(d),!l){var g=a.indexOf(d);g>-1&&a.splice(g,1)}b.resolve(!1)}else{var h=a.indexOf(d);h>-1&&a.splice(h,1),b.resolve(!0)}else b.resolve(!0)}null!=e?n(d,e).then(function(a){f(function(){return!o(a,e)})},function(){f(function(){return j("ngfValidateForce",{$file:d})})}):b.resolve(!0)}var q=[f.emptyPromise(!0)];a&&(a=void 0===a.length?[a]:a,angular.forEach(a,function(a){var d=b.defer();return q.push(d.promise),!i||null!=a.type&&0===a.type.search(i)?void("dimensions"===c&&null!=f.attrGetter("ngfDimensions",e)?f.imageDimensions(a).then(function(b){p(d,a,j("ngfDimensions",{$file:a,$width:b.width,$height:b.height}))},function(){d.resolve(!1)}):"duration"===c&&null!=f.attrGetter("ngfDuration",e)?f.mediaDuration(a).then(function(b){p(d,a,j("ngfDuration",{$file:a,$duration:b}))},function(){d.resolve(!1)}):p(d,a,f.getValidationAttr(e,g,c,h,a))):void d.resolve(!0)}));var r=b.defer();return b.all(q).then(function(a){for(var b=!0,e=0;e<a.length;e++)if(!a[e]){b=!1;break}d.$ngfValidations.push({name:c,valid:b}),r.resolve(b)}),r.promise}d=d||{},d.$ngfValidations=d.$ngfValidations||[],angular.forEach(d.$ngfValidations,function(a){a.valid=!0});var j=function(a,b){return f.attrGetter(a,e,g,b)},k=(f.attrGetter("ngfIgnoreInvalid",e,g)||"").split(" "),l=f.attrGetter("ngfRunAllValidations",e,g);if(null==a||0===a.length)return f.emptyPromise({validFiles:a,invalidFiles:[]});a=void 0===a.length?[a]:a.slice(0);var m=[];h("pattern",null,f.validatePattern),h("minSize","size.min",function(a,b){return a.size+.1>=f.translateScalars(b)}),h("maxSize","size.max",function(a,b){return a.size-.1<=f.translateScalars(b)});var n=0;if(h("maxTotalSize",null,function(b,c){return n+=b.size,n>f.translateScalars(c)?(a.splice(0,a.length),!1):!0}),h("validateFn",null,function(a,b){return b===!0||null===b||""===b}),!a.length)return f.emptyPromise({validFiles:[],invalidFiles:m});var o=b.defer(),p=[];return p.push(i("maxHeight","height.max",/image/,this.imageDimensions,function(a,b){return a.height<=b})),p.push(i("minHeight","height.min",/image/,this.imageDimensions,function(a,b){return a.height>=b})),p.push(i("maxWidth","width.max",/image/,this.imageDimensions,function(a,b){return a.width<=b})),p.push(i("minWidth","width.min",/image/,this.imageDimensions,function(a,b){return a.width>=b})),p.push(i("dimensions",null,/image/,function(a,b){return f.emptyPromise(b)},function(a){return a})),p.push(i("ratio",null,/image/,this.imageDimensions,function(a,b){for(var c=b.toString().split(","),d=!1,e=0;e<c.length;e++)Math.abs(a.width/a.height-f.ratioToFloat(c[e]))<.01&&(d=!0);return d})),p.push(i("maxRatio","ratio.max",/image/,this.imageDimensions,function(a,b){return a.width/a.height-f.ratioToFloat(b)<1e-4})),p.push(i("minRatio","ratio.min",/image/,this.imageDimensions,function(a,b){return a.width/a.height-f.ratioToFloat(b)>-1e-4})),p.push(i("maxDuration","duration.max",/audio|video/,this.mediaDuration,function(a,b){return a<=f.translateScalars(b)})),p.push(i("minDuration","duration.min",/audio|video/,this.mediaDuration,function(a,b){return a>=f.translateScalars(b)})),p.push(i("duration",null,/audio|video/,function(a,b){return f.emptyPromise(b)},function(a){return a})),p.push(i("validateAsyncFn",null,null,function(a,b){return b},function(a){return a===!0||null===a||""===a})),b.all(p).then(function(){if(l)for(var b=0;b<a.length;b++){var d=a[b];d.$error&&a.splice(b--,1)}l=!1,h("maxFiles",null,function(a,b,d){return b>c+d}),o.resolve({validFiles:a,invalidFiles:m})}),o.promise},f.imageDimensions=function(a){if(a.$ngfWidth&&a.$ngfHeight){var d=b.defer();return c(function(){d.resolve({width:a.$ngfWidth,height:a.$ngfHeight})}),d.promise}if(a.$ngfDimensionPromise)return a.$ngfDimensionPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("image")?void e.reject("not image"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].naturalWidth||h[0].clientWidth,c=h[0].naturalHeight||h[0].clientHeight;h.remove(),a.$ngfWidth=b,a.$ngfHeight=c,e.resolve({width:b,height:c})}function f(){h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].clientWidth?d():i++>10?f():g())},1e3)}var h=angular.element("<img>").attr("src",b).css("visibility","hidden").css("position","fixed").css("max-width","none !important").css("max-height","none !important");h.on("load",d),h.on("error",f);var i=0;g(),angular.element(document.getElementsByTagName("body")[0]).append(h)},function(){e.reject("load error")})}),a.$ngfDimensionPromise=e.promise,a.$ngfDimensionPromise["finally"](function(){delete a.$ngfDimensionPromise}),a.$ngfDimensionPromise},f.mediaDuration=function(a){if(a.$ngfDuration){var d=b.defer();return c(function(){d.resolve(a.$ngfDuration)}),d.promise}if(a.$ngfDurationPromise)return a.$ngfDurationPromise;var e=b.defer();return c(function(){return 0!==a.type.indexOf("audio")&&0!==a.type.indexOf("video")?void e.reject("not media"):void f.dataUrl(a).then(function(b){function d(){var b=h[0].duration;a.$ngfDuration=b,h.remove(),e.resolve(b)}function f(){h.remove(),e.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].duration?d():i>10?f():g())},1e3)}var h=angular.element(0===a.type.indexOf("audio")?"<audio>":"<video>").attr("src",b).css("visibility","none").css("position","fixed");h.on("loadedmetadata",d),h.on("error",f);var i=0;g(),angular.element(document.body).append(h)},function(){e.reject("load error")})}),a.$ngfDurationPromise=e.promise,a.$ngfDurationPromise["finally"](function(){delete a.$ngfDurationPromise}),a.$ngfDurationPromise},f}]),ngFileUpload.service("UploadResize",["UploadValidate","$q",function(a,b){var c=a,d=function(a,b,c,d,e){var f=e?Math.max(c/a,d/b):Math.min(c/a,d/b);return{width:a*f,height:b*f,marginX:a*f-c,marginY:b*f-d}},e=function(a,e,f,g,h,i,j,k){var l=b.defer(),m=document.createElement("canvas"),n=document.createElement("img");return n.setAttribute("style","visibility:hidden;position:fixed;z-index:-100000"),document.body.appendChild(n),n.onload=function(){var a=n.width,b=n.height;if(n.parentNode.removeChild(n),null!=k&&k(a,b)===!1)return void l.reject("resizeIf");try{if(i){var o=c.ratioToFloat(i),p=a/b;o>p?(e=a,f=e/o):(f=b,e=f*o)}e||(e=a),f||(f=b);var q=d(a,b,e,f,j);m.width=Math.min(q.width,e),m.height=Math.min(q.height,f);var r=m.getContext("2d");r.drawImage(n,Math.min(0,-q.marginX/2),Math.min(0,-q.marginY/2),q.width,q.height),l.resolve(m.toDataURL(h||"image/WebP",g||.934))}catch(s){l.reject(s)}},n.onerror=function(){n.parentNode.removeChild(n),l.reject()},n.src=a,l.promise};return c.dataUrltoBlob=function(a,b,c){for(var d=a.split(","),e=d[0].match(/:(.*?);/)[1],f=atob(d[1]),g=f.length,h=new Uint8Array(g);g--;)h[g]=f.charCodeAt(g);var i=new window.Blob([h],{type:e});return i.name=b,i.$ngfOrigSize=c,i},c.isResizeSupported=function(){var a=document.createElement("canvas");return window.atob&&a.getContext&&a.getContext("2d")&&window.Blob},c.isResizeSupported()&&Object.defineProperty(window.Blob.prototype,"name",{get:function(){return this.$ngfName},set:function(a){this.$ngfName=a},configurable:!0}),c.resize=function(a,d){if(0!==a.type.indexOf("image"))return c.emptyPromise(a);var f=b.defer();return c.dataUrl(a,!0).then(function(b){e(b,d.width,d.height,d.quality,d.type||a.type,d.ratio,d.centerCrop,d.resizeIf).then(function(e){if("image/jpeg"===a.type&&d.restoreExif!==!1)try{e=c.restoreExif(b,e)}catch(g){setTimeout(function(){throw g},1)}try{var h=c.dataUrltoBlob(e,a.name,a.size);f.resolve(h)}catch(g){f.reject(g)}},function(b){"resizeIf"===b&&f.resolve(a),f.reject(b)})},function(a){f.reject(a)}),f.promise},c}]),function(){function a(a,c,d,e,f,g,h,i,j,k){function l(){return c.attr("disabled")||s("ngfDropDisabled",a)}function m(b,c,d){if(b){var e;try{e=b&&b.getData&&b.getData("text/html")}catch(f){}q(b.items,b.files,s("ngfAllowDir",a)!==!1,s("multiple")||s("ngfMultiple",a)).then(function(a){a.length?n(a,c):o(d,e).then(function(a){n(a,c)})})}}function n(b,c){i.updateModel(e,d,a,s("ngfChange")||s("ngfDrop"),b,c)}function o(b,c){if(!i.shouldUpdateOn(b,d,a)||"string"!=typeof c)return i.rejectPromise([]);var e=[];c.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi,function(a,b,c){e.push(c)});var f=[],g=[];if(e.length){angular.forEach(e,function(a){f.push(i.urlToBlob(a).then(function(a){g.push(a)}))});var h=k.defer();return k.all(f).then(function(){h.resolve(g)},function(a){h.reject(a)}),h.promise}return i.emptyPromise()}function p(a,b,c,d){var e=s("ngfDragOverClass",a,{$event:c}),f="dragover";if(angular.isString(e))f=e;else if(e&&(e.delay&&(w=e.delay),e.accept||e.reject)){var g=c.dataTransfer.items;if(null!=g&&g.length)for(var h=e.pattern||s("ngfPattern",a,{$event:c}),j=g.length;j--;){if(!i.validatePattern(g[j],h)){f=e.reject;break}f=e.accept}else f=e.accept}d(f)}function q(b,c,e,f){function g(a,b){var c=k.defer();if(null!=a)if(a.isDirectory){var d=[i.emptyPromise()];if(m){var e={type:"directory"};e.name=e.path=(b||"")+a.name,n.push(e)}var f=a.createReader(),h=[],p=function(){f.readEntries(function(e){try{e.length?(h=h.concat(Array.prototype.slice.call(e||[],0)),p()):(angular.forEach(h.slice(0),function(c){n.length<=j&&l>=o&&d.push(g(c,(b?b:"")+a.name+"/"))}),k.all(d).then(function(){c.resolve()},function(a){c.reject(a)}))}catch(f){c.reject(f)}},function(a){c.reject(a)})};p()}else a.file(function(a){try{a.path=(b?b:"")+a.name,m&&(a=i.rename(a,a.path)),n.push(a),o+=a.size,c.resolve()}catch(d){c.reject(d)}},function(a){c.reject(a)});return c.promise}var j=i.getValidationAttr(d,a,"maxFiles");null==j&&(j=Number.MAX_VALUE);var l=i.getValidationAttr(d,a,"maxTotalSize");null==l&&(l=Number.MAX_VALUE);var m=s("ngfIncludeDir",a),n=[],o=0,p=[i.emptyPromise()];if(b&&b.length>0&&"file:"!==h.location.protocol)for(var q=0;q<b.length;q++){if(b[q].webkitGetAsEntry&&b[q].webkitGetAsEntry()&&b[q].webkitGetAsEntry().isDirectory){var r=b[q].webkitGetAsEntry();if(r.isDirectory&&!e)continue;null!=r&&p.push(g(r))}else{var t=b[q].getAsFile();null!=t&&(n.push(t),o+=t.size)}if(n.length>j||o>l||!f&&n.length>0)break}else if(null!=c)for(var u=0;u<c.length;u++){var v=c.item(u);if((v.type||v.size>0)&&(n.push(v),o+=v.size),n.length>j||o>l||!f&&n.length>0)break}var w=k.defer();return k.all(p).then(function(){if(f||m||!n.length)w.resolve(n);else{for(var a=0;n[a]&&"directory"===n[a].type;)a++;w.resolve([n[a]])}},function(a){w.reject(a)}),w.promise}var r=b(),s=function(a,b,c){return i.attrGetter(a,d,b,c)};if(s("dropAvailable")&&g(function(){a[s("dropAvailable")]?a[s("dropAvailable")].value=r:a[s("dropAvailable")]=r}),!r)return void(s("ngfHideOnDropNotAvailable",a)===!0&&c.css("display","none"));null==s("ngfSelect")&&i.registerModelChangeValidator(e,d,a);var t,u=null,v=f(s("ngfStopPropagation")),w=1;c[0].addEventListener("dragover",function(b){if(!l()&&i.shouldUpdateOn("drop",d,a)){if(b.preventDefault(),v(a)&&b.stopPropagation(),navigator.userAgent.indexOf("Chrome")>-1){var e=b.dataTransfer.effectAllowed;b.dataTransfer.dropEffect="move"===e||"linkMove"===e?"move":"copy"}g.cancel(u),t||(t="C",p(a,d,b,function(d){t=d,c.addClass(t),s("ngfDrag",a,{$isDragging:!0,$class:t,$event:b})}))}},!1),c[0].addEventListener("dragenter",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),v(a)&&b.stopPropagation())},!1),c[0].addEventListener("dragleave",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),
2 v(a)&&b.stopPropagation(),u=g(function(){t&&c.removeClass(t),t=null,s("ngfDrag",a,{$isDragging:!1,$event:b})},w||100))},!1),c[0].addEventListener("drop",function(b){!l()&&i.shouldUpdateOn("drop",d,a)&&(b.preventDefault(),v(a)&&b.stopPropagation(),t&&c.removeClass(t),t=null,m(b.dataTransfer,b,"dropUrl"))},!1),c[0].addEventListener("paste",function(b){navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&s("ngfEnableFirefoxPaste",a)&&b.preventDefault(),!l()&&i.shouldUpdateOn("paste",d,a)&&m(b.clipboardData||b.originalEvent.clipboardData,b,"pasteUrl")},!1),navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&s("ngfEnableFirefoxPaste",a)&&(c.attr("contenteditable",!0),c.on("keypress",function(a){a.metaKey||a.ctrlKey||a.preventDefault()}))}function b(){var a=document.createElement("div");return"draggable"in a&&"ondrop"in a&&!/Edge\/12./i.test(navigator.userAgent)}ngFileUpload.directive("ngfDrop",["$parse","$timeout","$window","Upload","$http","$q",function(b,c,d,e,f,g){return{restrict:"AEC",require:"?ngModel",link:function(h,i,j,k){a(h,i,j,k,b,c,d,e,f,g)}}}]),ngFileUpload.directive("ngfNoFileDrop",function(){return function(a,c){b()&&c.css("display","none")}}),ngFileUpload.directive("ngfDropAvailable",["$parse","$timeout","Upload",function(a,c,d){return function(e,f,g){if(b()){var h=a(d.attrGetter("ngfDropAvailable",g));c(function(){h(e),h.assign&&h.assign(e,!0)})}}}])}(),ngFileUpload.service("UploadExif",["UploadResize","$q",function(a,b){function c(a,b,c,d){switch(b){case 2:return a.transform(-1,0,0,1,c,0);case 3:return a.transform(-1,0,0,-1,c,d);case 4:return a.transform(1,0,0,-1,0,d);case 5:return a.transform(0,1,1,0,0,0);case 6:return a.transform(0,1,-1,0,d,0);case 7:return a.transform(0,-1,-1,0,d,c);case 8:return a.transform(0,-1,1,0,0,c)}}function d(a){for(var b="",c=new Uint8Array(a),d=c.byteLength,e=0;d>e;e++)b+=String.fromCharCode(c[e]);return window.btoa(b)}var e=a;return e.isExifSupported=function(){return window.FileReader&&(new FileReader).readAsArrayBuffer&&e.isResizeSupported()},e.readOrientation=function(a){var c=b.defer(),d=new FileReader,e=a.slice?a.slice(0,65536):a;return d.readAsArrayBuffer(e),d.onerror=function(a){return c.reject(a)},d.onload=function(a){var b={orientation:1},d=new DataView(this.result);if(65496!==d.getUint16(0,!1))return c.resolve(b);for(var e=d.byteLength,f=2;e>f;){var g=d.getUint16(f,!1);if(f+=2,65505===g){if(1165519206!==d.getUint32(f+=2,!1))return c.resolve(b);var h=18761===d.getUint16(f+=6,!1);f+=d.getUint32(f+4,h);var i=d.getUint16(f,h);f+=2;for(var j=0;i>j;j++)if(274===d.getUint16(f+12*j,h)){var k=d.getUint16(f+12*j+8,h);return k>=2&&8>=k&&(d.setUint16(f+12*j+8,1,h),b.fixedArrayBuffer=a.target.result),b.orientation=k,c.resolve(b)}}else{if(65280!==(65280&g))break;f+=d.getUint16(f,!1)}}return c.resolve(b)},c.promise},e.applyExifRotation=function(a){if(0!==a.type.indexOf("image/jpeg"))return e.emptyPromise(a);var f=b.defer();return e.readOrientation(a).then(function(b){return b.orientation<2||b.orientation>8?f.resolve(a):void e.dataUrl(a,!0).then(function(g){var h=document.createElement("canvas"),i=document.createElement("img");i.onload=function(){try{h.width=b.orientation>4?i.height:i.width,h.height=b.orientation>4?i.width:i.height;var g=h.getContext("2d");c(g,b.orientation,i.width,i.height),g.drawImage(i,0,0);var j=h.toDataURL(a.type||"image/WebP",.934);j=e.restoreExif(d(b.fixedArrayBuffer),j);var k=e.dataUrltoBlob(j,a.name);f.resolve(k)}catch(l){return f.reject(l)}},i.onerror=function(){f.reject()},i.src=g},function(a){f.reject(a)})},function(a){f.reject(a)}),f.promise},e.restoreExif=function(a,b){var c={};return c.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c.encode64=function(a){var b,c,d,e,f,g="",h="",i="",j=0;do b=a[j++],c=a[j++],h=a[j++],d=b>>2,e=(3&b)<<4|c>>4,f=(15&c)<<2|h>>6,i=63&h,isNaN(c)?f=i=64:isNaN(h)&&(i=64),g=g+this.KEY_STR.charAt(d)+this.KEY_STR.charAt(e)+this.KEY_STR.charAt(f)+this.KEY_STR.charAt(i),b=c=h="",d=e=f=i="";while(j<a.length);return g},c.restore=function(a,b){a.match("data:image/jpeg;base64,")&&(a=a.replace("data:image/jpeg;base64,",""));var c=this.decode64(a),d=this.slice2Segments(c),e=this.exifManipulation(b,d);return"data:image/jpeg;base64,"+this.encode64(e)},c.exifManipulation=function(a,b){var c=this.getExifArray(b),d=this.insertExif(a,c);return new Uint8Array(d)},c.getExifArray=function(a){for(var b,c=0;c<a.length;c++)if(b=a[c],255===b[0]&225===b[1])return b;return[]},c.insertExif=function(a,b){var c=a.replace("data:image/jpeg;base64,",""),d=this.decode64(c),e=d.indexOf(255,3),f=d.slice(0,e),g=d.slice(e),h=f;return h=h.concat(b),h=h.concat(g)},c.slice2Segments=function(a){for(var b=0,c=[];;){if(255===a[b]&218===a[b+1])break;if(255===a[b]&216===a[b+1])b+=2;else{var d=256*a[b+2]+a[b+3],e=b+d+2,f=a.slice(b,e);c.push(f),b=e}if(b>a.length)break}return c},c.decode64=function(a){var b,c,d,e,f,g="",h="",i=0,j=[],k=/[^A-Za-z0-9\+\/\=]/g;k.exec(a)&&console.log("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, NaNExpect errors in decoding."),a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");do d=this.KEY_STR.indexOf(a.charAt(i++)),e=this.KEY_STR.indexOf(a.charAt(i++)),f=this.KEY_STR.indexOf(a.charAt(i++)),h=this.KEY_STR.indexOf(a.charAt(i++)),b=d<<2|e>>4,c=(15&e)<<4|f>>2,g=(3&f)<<6|h,j.push(b),64!==f&&j.push(c),64!==h&&j.push(g),b=c=g="",d=e=f=h="";while(i<a.length);return j},c.restore(a,b)},e}]);
+0
-17
nuget/Package.nuspec less more
0 <?xml version="1.0"?>
1 <package >
2 <metadata>
3 <id>angular-file-upload</id>
4 <title>Angular file upload</title>
5 <version>12.0.4</version>
6 <authors>Danial Farid, Georgios Diamantopoulos (nuget package)</authors>
7 <owners>Danial Farid</owners>
8 <licenseUrl>https://github.com/danialfarid/ng-file-upload/blob/master/LICENSE</licenseUrl>
9 <projectUrl>https://github.com/danialfarid/ng-file-upload</projectUrl>
10 <requireLicenseAcceptance>false</requireLicenseAcceptance>
11 <description>Light-weight HTML5 and cross-browser AngularJS directives for file upload, progress, abort, drag and drop</description>
12 <tags>angularjs upload</tags>
13 <dependencies>
14 </dependencies>
15 </metadata>
16 </package>
+0
-16
nuget/build.bat less more
0 NuGet Update -self
1
2 rmdir /s /q content
3 mkdir content
4 mkdir content\scripts
5 copy ..\dist\* content\scripts
6 del angular-file-upload.*
7
8 NuGet Pack Package.nuspec
9
10 for %%f in (angular-file-upload.*) do (
11 NuGet Push %%f
12 rmdir /s /q content
13 del %%f
14 )
15
+0
-8
nuget/nuget.sh less more
0 #!/bin/sh
1 # add a simple 'nuget' command to Mac OS X under Mono
2 # get NuGet.exe binary from http://nuget.codeplex.com/releases/view/58939
3 # get Microsoft.Build.dll from a Windows .NET 4.0 installation
4 # copy to /usr/local/bin and Robert is your father's brother....
5 #
6 PATH=/usr/local/bin:$PATH
7 mono --runtime=v4.0 /usr/local/bin/NuGet.exe $*
00 {
11 "name": "ng-file-upload",
2 "version": "12.0.4",
2 "version": "12.2.13",
33 "devDependencies": {
44 "grunt": "^0.4.5",
55 "grunt-contrib-concat": "^0.5.1",
1515 "load-grunt-tasks": "^3.1.0"
1616 },
1717 "description": "An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers",
18 "main": "index.js",
18 "files": [
19 "index.js",
20 "dist"
21 ],
1922 "scripts": {
2023 "test": "echo \"Error: no test specified\" && exit 1"
2124 },
+0
-40
release.sh less more
0 echo version: $2
1 echo message: $1
2
3 grunt
4 git add .
5 git add -u .
6 git commit -am "$1"
7 git pull
8 git push
9 cd ../angular-file-upload-shim-bower
10 git add .
11 git add -u .
12 git commit -am "$2"
13 git pull
14 git push
15 cd ../angular-file-upload-bower
16 git add .
17 git add -u .
18 git commit -am "$2"
19 git pull
20 git push
21
22
23 API_JSON=$(printf '{"tag_name": "%s","target_commitish": "master","name": "Version %s","body": "%s","draft": false,"prerelease": false}' $2 $2 "$1")
24
25 echo commit json: $API_JSON
26
27 curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload/releases?access_token=$3
28
29 curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload-shim-bower/releases?access_token=$3
30
31 curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload-bower/releases?access_token=$3
32
33 cd ../ng-file-upload
34 npm publish
35
36 cd ../angular-file-upload-bower
37 meteor publish
38
39 cd ../ng-file-upload
+0
-4313
src/FileAPI.js less more
0 /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git
1 * 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.
2 */
3
4 /*
5 * JavaScript Canvas to Blob 2.0.5
6 * https://github.com/blueimp/JavaScript-Canvas-to-Blob
7 *
8 * Copyright 2012, Sebastian Tschan
9 * https://blueimp.net
10 *
11 * Licensed under the MIT license:
12 * http://www.opensource.org/licenses/MIT
13 *
14 * Based on stackoverflow user Stoive's code snippet:
15 * http://stackoverflow.com/q/4998908
16 */
17
18 /*jslint nomen: true, regexp: true */
19 /*global window, atob, Blob, ArrayBuffer, Uint8Array */
20
21 (function (window) {
22 'use strict';
23 var CanvasPrototype = window.HTMLCanvasElement &&
24 window.HTMLCanvasElement.prototype,
25 hasBlobConstructor = window.Blob && (function () {
26 try {
27 return Boolean(new Blob());
28 } catch (e) {
29 return false;
30 }
31 }()),
32 hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
33 (function () {
34 try {
35 return new Blob([new Uint8Array(100)]).size === 100;
36 } catch (e) {
37 return false;
38 }
39 }()),
40 BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
41 window.MozBlobBuilder || window.MSBlobBuilder,
42 dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
43 window.ArrayBuffer && window.Uint8Array && function (dataURI) {
44 var byteString,
45 arrayBuffer,
46 intArray,
47 i,
48 mimeString,
49 bb;
50 if (dataURI.split(',')[0].indexOf('base64') >= 0) {
51 // Convert base64 to raw binary data held in a string:
52 byteString = atob(dataURI.split(',')[1]);
53 } else {
54 // Convert base64/URLEncoded data component to raw binary data:
55 byteString = decodeURIComponent(dataURI.split(',')[1]);
56 }
57 // Write the bytes of the string to an ArrayBuffer:
58 arrayBuffer = new ArrayBuffer(byteString.length);
59 intArray = new Uint8Array(arrayBuffer);
60 for (i = 0; i < byteString.length; i += 1) {
61 intArray[i] = byteString.charCodeAt(i);
62 }
63 // Separate out the mime component:
64 mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
65 // Write the ArrayBuffer (or ArrayBufferView) to a blob:
66 if (hasBlobConstructor) {
67 return new Blob(
68 [hasArrayBufferViewSupport ? intArray : arrayBuffer],
69 {type: mimeString}
70 );
71 }
72 bb = new BlobBuilder();
73 bb.append(arrayBuffer);
74 return bb.getBlob(mimeString);
75 };
76 if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
77 if (CanvasPrototype.mozGetAsFile) {
78 CanvasPrototype.toBlob = function (callback, type, quality) {
79 if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
80 callback(dataURLtoBlob(this.toDataURL(type, quality)));
81 } else {
82 callback(this.mozGetAsFile('blob', type));
83 }
84 };
85 } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
86 CanvasPrototype.toBlob = function (callback, type, quality) {
87 callback(dataURLtoBlob(this.toDataURL(type, quality)));
88 };
89 }
90 }
91 window.dataURLtoBlob = dataURLtoBlob;
92 })(window);
93
94 /*jslint evil: true */
95 /*global window, URL, webkitURL, ActiveXObject */
96
97 (function (window, undef){
98 'use strict';
99
100 var
101 gid = 1,
102 noop = function (){},
103
104 document = window.document,
105 doctype = document.doctype || {},
106 userAgent = window.navigator.userAgent,
107
108 // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
109 apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
110
111 Blob = window.Blob,
112 File = window.File,
113 FileReader = window.FileReader,
114 FormData = window.FormData,
115
116
117 XMLHttpRequest = window.XMLHttpRequest,
118 jQuery = window.jQuery,
119
120 html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
121 && !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
122
123 cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
124
125 chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
126
127 // https://github.com/blueimp/JavaScript-Canvas-to-Blob
128 dataURLtoBlob = window.dataURLtoBlob,
129
130
131 _rimg = /img/i,
132 _rcanvas = /canvas/i,
133 _rimgcanvas = /img|canvas/i,
134 _rinput = /input/i,
135 _rdata = /^data:[^,]+,/,
136
137 _toString = {}.toString,
138
139
140 Math = window.Math,
141
142 _SIZE_CONST = function (pow){
143 pow = new window.Number(Math.pow(1024, pow));
144 pow.from = function (sz){ return Math.round(sz * this); };
145 return pow;
146 },
147
148 _elEvents = {}, // element event listeners
149 _infoReader = [], // list of file info processors
150
151 _readerEvents = 'abort progress error load loadend',
152 _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
153
154 currentTarget = 'currentTarget', // for minimize
155 preventDefault = 'preventDefault', // and this too
156
157 _isArray = function (ar) {
158 return ar && ('length' in ar);
159 },
160
161 /**
162 * Iterate over a object or array
163 */
164 _each = function (obj, fn, ctx){
165 if( obj ){
166 if( _isArray(obj) ){
167 for( var i = 0, n = obj.length; i < n; i++ ){
168 if( i in obj ){
169 fn.call(ctx, obj[i], i, obj);
170 }
171 }
172 }
173 else {
174 for( var key in obj ){
175 if( obj.hasOwnProperty(key) ){
176 fn.call(ctx, obj[key], key, obj);
177 }
178 }
179 }
180 }
181 },
182
183 /**
184 * Merge the contents of two or more objects together into the first object
185 */
186 _extend = function (dst){
187 var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
188 for( ; i < args.length; i++ ){
189 _each(args[i], _ext);
190 }
191 return dst;
192 },
193
194 /**
195 * Add event listener
196 */
197 _on = function (el, type, fn){
198 if( el ){
199 var uid = api.uid(el);
200
201 if( !_elEvents[uid] ){
202 _elEvents[uid] = {};
203 }
204
205 var isFileReader = (FileReader && el) && (el instanceof FileReader);
206 _each(type.split(/\s+/), function (type){
207 if( jQuery && !isFileReader){
208 jQuery.event.add(el, type, fn);
209 } else {
210 if( !_elEvents[uid][type] ){
211 _elEvents[uid][type] = [];
212 }
213
214 _elEvents[uid][type].push(fn);
215
216 if( el.addEventListener ){ el.addEventListener(type, fn, false); }
217 else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
218 else { el['on'+type] = fn; }
219 }
220 });
221 }
222 },
223
224
225 /**
226 * Remove event listener
227 */
228 _off = function (el, type, fn){
229 if( el ){
230 var uid = api.uid(el), events = _elEvents[uid] || {};
231
232 var isFileReader = (FileReader && el) && (el instanceof FileReader);
233 _each(type.split(/\s+/), function (type){
234 if( jQuery && !isFileReader){
235 jQuery.event.remove(el, type, fn);
236 }
237 else {
238 var fns = events[type] || [], i = fns.length;
239
240 while( i-- ){
241 if( fns[i] === fn ){
242 fns.splice(i, 1);
243 break;
244 }
245 }
246
247 if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
248 else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
249 else { el['on'+type] = null; }
250 }
251 });
252 }
253 },
254
255
256 _one = function(el, type, fn){
257 _on(el, type, function _(evt){
258 _off(el, type, _);
259 fn(evt);
260 });
261 },
262
263
264 _fixEvent = function (evt){
265 if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
266 if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
267 return evt;
268 },
269
270
271 _supportInputAttr = function (attr){
272 var input = document.createElement('input');
273 input.setAttribute('type', "file");
274 return attr in input;
275 },
276
277 /**
278 * FileAPI (core object)
279 */
280 api = {
281 version: '2.0.7',
282
283 cors: false,
284 html5: true,
285 media: false,
286 formData: true,
287 multiPassResize: true,
288
289 debug: false,
290 pingUrl: false,
291 multiFlash: false,
292 flashAbortTimeout: 0,
293 withCredentials: true,
294
295 staticPath: './dist/',
296
297 flashUrl: 0, // @default: './FileAPI.flash.swf'
298 flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
299
300 postNameConcat: function (name, idx){
301 return name + (idx != null ? '['+ idx +']' : '');
302 },
303
304 ext2mime: {
305 jpg: 'image/jpeg'
306 , tif: 'image/tiff'
307 , txt: 'text/plain'
308 },
309
310 // Fallback for flash
311 accept: {
312 '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'
313 , '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'
314 , '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'
315 },
316
317 uploadRetry : 0,
318 networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
319
320 chunkSize : 0,
321 chunkUploadRetry : 0,
322 chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
323
324 KB: _SIZE_CONST(1),
325 MB: _SIZE_CONST(2),
326 GB: _SIZE_CONST(3),
327 TB: _SIZE_CONST(4),
328
329 EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
330
331 expando: 'fileapi' + (new Date).getTime(),
332
333 uid: function (obj){
334 return obj
335 ? (obj[api.expando] = obj[api.expando] || api.uid())
336 : (++gid, api.expando + gid)
337 ;
338 },
339
340 log: function (){
341 // ngf fix for IE8 #1071
342 if( api.debug && api._supportConsoleLog ){
343 if( api._supportConsoleLogApply ){
344 console.log.apply(console, arguments);
345 }
346 else {
347 console.log([].join.call(arguments, ' '));
348 }
349 }
350 },
351
352 /**
353 * Create new image
354 *
355 * @param {String} [src]
356 * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element
357 * @returns {HTMLElement}
358 */
359 newImage: function (src, fn){
360 var img = document.createElement('img');
361 if( fn ){
362 api.event.one(img, 'error load', function (evt){
363 fn(evt.type == 'error', img);
364 img = null;
365 });
366 }
367 img.src = src;
368 return img;
369 },
370
371 /**
372 * Get XHR
373 * @returns {XMLHttpRequest}
374 */
375 getXHR: function (){
376 var xhr;
377
378 if( XMLHttpRequest ){
379 xhr = new XMLHttpRequest;
380 }
381 else if( window.ActiveXObject ){
382 try {
383 xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
384 } catch (e) {
385 xhr = new ActiveXObject('Microsoft.XMLHTTP');
386 }
387 }
388
389 return xhr;
390 },
391
392 isArray: _isArray,
393
394 support: {
395 dnd: cors && ('ondrop' in document.createElement('div')),
396 cors: cors,
397 html5: html5,
398 chunked: chunked,
399 dataURI: true,
400 accept: _supportInputAttr('accept'),
401 multiple: _supportInputAttr('multiple')
402 },
403
404 event: {
405 on: _on
406 , off: _off
407 , one: _one
408 , fix: _fixEvent
409 },
410
411
412 throttle: function(fn, delay) {
413 var id, args;
414
415 return function _throttle(){
416 args = arguments;
417
418 if( !id ){
419 fn.apply(window, args);
420 id = setTimeout(function (){
421 id = 0;
422 fn.apply(window, args);
423 }, delay);
424 }
425 };
426 },
427
428
429 F: function (){},
430
431
432 parseJSON: function (str){
433 var json;
434 if( window.JSON && JSON.parse ){
435 json = JSON.parse(str);
436 }
437 else {
438 json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
439 }
440 return json;
441 },
442
443
444 trim: function (str){
445 str = String(str);
446 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
447 },
448
449 /**
450 * Simple Defer
451 * @return {Object}
452 */
453 defer: function (){
454 var
455 list = []
456 , result
457 , error
458 , defer = {
459 resolve: function (err, res){
460 defer.resolve = noop;
461 error = err || false;
462 result = res;
463
464 while( res = list.shift() ){
465 res(error, result);
466 }
467 },
468
469 then: function (fn){
470 if( error !== undef ){
471 fn(error, result);
472 } else {
473 list.push(fn);
474 }
475 }
476 };
477
478 return defer;
479 },
480
481 queue: function (fn){
482 var
483 _idx = 0
484 , _length = 0
485 , _fail = false
486 , _end = false
487 , queue = {
488 inc: function (){
489 _length++;
490 },
491
492 next: function (){
493 _idx++;
494 setTimeout(queue.check, 0);
495 },
496
497 check: function (){
498 (_idx >= _length) && !_fail && queue.end();
499 },
500
501 isFail: function (){
502 return _fail;
503 },
504
505 fail: function (){
506 !_fail && fn(_fail = true);
507 },
508
509 end: function (){
510 if( !_end ){
511 _end = true;
512 fn();
513 }
514 }
515 }
516 ;
517 return queue;
518 },
519
520
521 /**
522 * For each object
523 *
524 * @param {Object|Array} obj
525 * @param {Function} fn
526 * @param {*} [ctx]
527 */
528 each: _each,
529
530
531 /**
532 * Async for
533 * @param {Array} array
534 * @param {Function} callback
535 */
536 afor: function (array, callback){
537 var i = 0, n = array.length;
538
539 if( _isArray(array) && n-- ){
540 (function _next(){
541 callback(n != i && _next, array[i], i++);
542 })();
543 }
544 else {
545 callback(false);
546 }
547 },
548
549
550 /**
551 * Merge the contents of two or more objects together into the first object
552 *
553 * @param {Object} dst
554 * @return {Object}
555 */
556 extend: _extend,
557
558
559 /**
560 * Is file?
561 * @param {File} file
562 * @return {Boolean}
563 */
564 isFile: function (file){
565 return _toString.call(file) === '[object File]';
566 },
567
568
569 /**
570 * Is blob?
571 * @param {Blob} blob
572 * @returns {Boolean}
573 */
574 isBlob: function (blob) {
575 return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
576 },
577
578
579 /**
580 * Is canvas element
581 *
582 * @param {HTMLElement} el
583 * @return {Boolean}
584 */
585 isCanvas: function (el){
586 return el && _rcanvas.test(el.nodeName);
587 },
588
589
590 getFilesFilter: function (filter){
591 filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
592 return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
593 },
594
595
596
597 /**
598 * Read as DataURL
599 *
600 * @param {File|Element} file
601 * @param {Function} fn
602 */
603 readAsDataURL: function (file, fn){
604 if( api.isCanvas(file) ){
605 _emit(file, fn, 'load', api.toDataURL(file));
606 }
607 else {
608 _readAs(file, fn, 'DataURL');
609 }
610 },
611
612
613 /**
614 * Read as Binary string
615 *
616 * @param {File} file
617 * @param {Function} fn
618 */
619 readAsBinaryString: function (file, fn){
620 if( _hasSupportReadAs('BinaryString') ){
621 _readAs(file, fn, 'BinaryString');
622 } else {
623 // Hello IE10!
624 _readAs(file, function (evt){
625 if( evt.type == 'load' ){
626 try {
627 // dataURL -> binaryString
628 evt.result = api.toBinaryString(evt.result);
629 } catch (e){
630 evt.type = 'error';
631 evt.message = e.toString();
632 }
633 }
634 fn(evt);
635 }, 'DataURL');
636 }
637 },
638
639
640 /**
641 * Read as ArrayBuffer
642 *
643 * @param {File} file
644 * @param {Function} fn
645 */
646 readAsArrayBuffer: function(file, fn){
647 _readAs(file, fn, 'ArrayBuffer');
648 },
649
650
651 /**
652 * Read as text
653 *
654 * @param {File} file
655 * @param {String} encoding
656 * @param {Function} [fn]
657 */
658 readAsText: function(file, encoding, fn){
659 if( !fn ){
660 fn = encoding;
661 encoding = 'utf-8';
662 }
663
664 _readAs(file, fn, 'Text', encoding);
665 },
666
667
668 /**
669 * Convert image or canvas to DataURL
670 *
671 * @param {Element} el Image or Canvas element
672 * @param {String} [type] mime-type
673 * @return {String}
674 */
675 toDataURL: function (el, type){
676 if( typeof el == 'string' ){
677 return el;
678 }
679 else if( el.toDataURL ){
680 return el.toDataURL(type || 'image/png');
681 }
682 },
683
684
685 /**
686 * Canvert string, image or canvas to binary string
687 *
688 * @param {String|Element} val
689 * @return {String}
690 */
691 toBinaryString: function (val){
692 return window.atob(api.toDataURL(val).replace(_rdata, ''));
693 },
694
695
696 /**
697 * Read file or DataURL as ImageElement
698 *
699 * @param {File|String} file
700 * @param {Function} fn
701 * @param {Boolean} [progress]
702 */
703 readAsImage: function (file, fn, progress){
704 if( api.isFile(file) ){
705 if( apiURL ){
706 /** @namespace apiURL.createObjectURL */
707 var data = apiURL.createObjectURL(file);
708 if( data === undef ){
709 _emit(file, fn, 'error');
710 }
711 else {
712 api.readAsImage(data, fn, progress);
713 }
714 }
715 else {
716 api.readAsDataURL(file, function (evt){
717 if( evt.type == 'load' ){
718 api.readAsImage(evt.result, fn, progress);
719 }
720 else if( progress || evt.type == 'error' ){
721 _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
722 }
723 });
724 }
725 }
726 else if( api.isCanvas(file) ){
727 _emit(file, fn, 'load', file);
728 }
729 else if( _rimg.test(file.nodeName) ){
730 if( file.complete ){
731 _emit(file, fn, 'load', file);
732 }
733 else {
734 var events = 'error abort load';
735 _one(file, events, function _fn(evt){
736 if( evt.type == 'load' && apiURL ){
737 /** @namespace apiURL.revokeObjectURL */
738 apiURL.revokeObjectURL(file.src);
739 }
740
741 _off(file, events, _fn);
742 _emit(file, fn, evt, file);
743 });
744 }
745 }
746 else if( file.iframe ){
747 _emit(file, fn, { type: 'error' });
748 }
749 else {
750 // Created image
751 var img = api.newImage(file.dataURL || file);
752 api.readAsImage(img, fn, progress);
753 }
754 },
755
756
757 /**
758 * Make file by name
759 *
760 * @param {String} name
761 * @return {Array}
762 */
763 checkFileObj: function (name){
764 var file = {}, accept = api.accept;
765
766 if( typeof name == 'object' ){
767 file = name;
768 }
769 else {
770 file.name = (name + '').split(/\\|\//g).pop();
771 }
772
773 if( file.type == null ){
774 file.type = file.name.split('.').pop();
775 }
776
777 _each(accept, function (ext, type){
778 ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
779 if( ext.test(file.type) || api.ext2mime[file.type] ){
780 file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
781 }
782 });
783
784 return file;
785 },
786
787
788 /**
789 * Get drop files
790 *
791 * @param {Event} evt
792 * @param {Function} callback
793 */
794 getDropFiles: function (evt, callback){
795 var
796 files = []
797 , dataTransfer = _getDataTransfer(evt)
798 , entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
799 , queue = api.queue(function (){ callback(files); })
800 ;
801
802 _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
803 queue.inc();
804
805 try {
806 if( entrySupport ){
807 _readEntryAsFiles(item, function (err, entryFiles){
808 if( err ){
809 api.log('[err] getDropFiles:', err);
810 } else {
811 files.push.apply(files, entryFiles);
812 }
813 queue.next();
814 });
815 }
816 else {
817 _isRegularFile(item, function (yes){
818 yes && files.push(item);
819 queue.next();
820 });
821 }
822 }
823 catch( err ){
824 queue.next();
825 api.log('[err] getDropFiles: ', err);
826 }
827 });
828
829 queue.check();
830 },
831
832
833 /**
834 * Get file list
835 *
836 * @param {HTMLInputElement|Event} input
837 * @param {String|Function} [filter]
838 * @param {Function} [callback]
839 * @return {Array|Null}
840 */
841 getFiles: function (input, filter, callback){
842 var files = [];
843
844 if( callback ){
845 api.filterFiles(api.getFiles(input), filter, callback);
846 return null;
847 }
848
849 if( input.jquery ){
850 // jQuery object
851 input.each(function (){
852 files = files.concat(api.getFiles(this));
853 });
854 input = files;
855 files = [];
856 }
857
858 if( typeof filter == 'string' ){
859 filter = api.getFilesFilter(filter);
860 }
861
862 if( input.originalEvent ){
863 // jQuery event
864 input = _fixEvent(input.originalEvent);
865 }
866 else if( input.srcElement ){
867 // IE Event
868 input = _fixEvent(input);
869 }
870
871
872 if( input.dataTransfer ){
873 // Drag'n'Drop
874 input = input.dataTransfer;
875 }
876 else if( input.target ){
877 // Event
878 input = input.target;
879 }
880
881 if( input.files ){
882 // Input[type="file"]
883 files = input.files;
884
885 if( !html5 ){
886 // Partial support for file api
887 files[0].blob = input;
888 files[0].iframe = true;
889 }
890 }
891 else if( !html5 && isInputFile(input) ){
892 if( api.trim(input.value) ){
893 files = [api.checkFileObj(input.value)];
894 files[0].blob = input;
895 files[0].iframe = true;
896 }
897 }
898 else if( _isArray(input) ){
899 files = input;
900 }
901
902 return api.filter(files, function (file){ return !filter || filter.test(file.name); });
903 },
904
905
906 /**
907 * Get total file size
908 * @param {Array} files
909 * @return {Number}
910 */
911 getTotalSize: function (files){
912 var size = 0, i = files && files.length;
913 while( i-- ){
914 size += files[i].size;
915 }
916 return size;
917 },
918
919
920 /**
921 * Get image information
922 *
923 * @param {File} file
924 * @param {Function} fn
925 */
926 getInfo: function (file, fn){
927 var info = {}, readers = _infoReader.concat();
928
929 if( api.isFile(file) ){
930 (function _next(){
931 var reader = readers.shift();
932 if( reader ){
933 if( reader.test(file.type) ){
934 reader(file, function (err, res){
935 if( err ){
936 fn(err);
937 }
938 else {
939 _extend(info, res);
940 _next();
941 }
942 });
943 }
944 else {
945 _next();
946 }
947 }
948 else {
949 fn(false, info);
950 }
951 })();
952 }
953 else {
954 fn('not_support_info', info);
955 }
956 },
957
958
959 /**
960 * Add information reader
961 *
962 * @param {RegExp} mime
963 * @param {Function} fn
964 */
965 addInfoReader: function (mime, fn){
966 fn.test = function (type){ return mime.test(type); };
967 _infoReader.push(fn);
968 },
969
970
971 /**
972 * Filter of array
973 *
974 * @param {Array} input
975 * @param {Function} fn
976 * @return {Array}
977 */
978 filter: function (input, fn){
979 var result = [], i = 0, n = input.length, val;
980
981 for( ; i < n; i++ ){
982 if( i in input ){
983 val = input[i];
984 if( fn.call(val, val, i, input) ){
985 result.push(val);
986 }
987 }
988 }
989
990 return result;
991 },
992
993
994 /**
995 * Filter files
996 *
997 * @param {Array} files
998 * @param {Function} eachFn
999 * @param {Function} resultFn
1000 */
1001 filterFiles: function (files, eachFn, resultFn){
1002 if( files.length ){
1003 // HTML5 or Flash
1004 var queue = files.concat(), file, result = [], deleted = [];
1005
1006 (function _next(){
1007 if( queue.length ){
1008 file = queue.shift();
1009 api.getInfo(file, function (err, info){
1010 (eachFn(file, err ? false : info) ? result : deleted).push(file);
1011 _next();
1012 });
1013 }
1014 else {
1015 resultFn(result, deleted);
1016 }
1017 })();
1018 }
1019 else {
1020 resultFn([], files);
1021 }
1022 },
1023
1024
1025 upload: function (options){
1026 options = _extend({
1027 jsonp: 'callback'
1028 , prepare: api.F
1029 , beforeupload: api.F
1030 , upload: api.F
1031 , fileupload: api.F
1032 , fileprogress: api.F
1033 , filecomplete: api.F
1034 , progress: api.F
1035 , complete: api.F
1036 , pause: api.F
1037 , imageOriginal: true
1038 , chunkSize: api.chunkSize
1039 , chunkUploadRetry: api.chunkUploadRetry
1040 , uploadRetry: api.uploadRetry
1041 }, options);
1042
1043
1044 if( options.imageAutoOrientation && !options.imageTransform ){
1045 options.imageTransform = { rotate: 'auto' };
1046 }
1047
1048
1049 var
1050 proxyXHR = new api.XHR(options)
1051 , dataArray = this._getFilesDataArray(options.files)
1052 , _this = this
1053 , _total = 0
1054 , _loaded = 0
1055 , _nextFile
1056 , _complete = false
1057 ;
1058
1059
1060 // calc total size
1061 _each(dataArray, function (data){
1062 _total += data.size;
1063 });
1064
1065 // Array of files
1066 proxyXHR.files = [];
1067 _each(dataArray, function (data){
1068 proxyXHR.files.push(data.file);
1069 });
1070
1071 // Set upload status props
1072 proxyXHR.total = _total;
1073 proxyXHR.loaded = 0;
1074 proxyXHR.filesLeft = dataArray.length;
1075
1076 // emit "beforeupload" event
1077 options.beforeupload(proxyXHR, options);
1078
1079 // Upload by file
1080 _nextFile = function (){
1081 var
1082 data = dataArray.shift()
1083 , _file = data && data.file
1084 , _fileLoaded = false
1085 , _fileOptions = _simpleClone(options)
1086 ;
1087
1088 proxyXHR.filesLeft = dataArray.length;
1089
1090 if( _file && _file.name === api.expando ){
1091 _file = null;
1092 api.log('[warn] FileAPI.upload() — called without files');
1093 }
1094
1095 if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1096 // Mark active job
1097 _complete = false;
1098
1099 // Set current upload file
1100 proxyXHR.currentFile = _file;
1101
1102 // Prepare file options
1103 if (_file && options.prepare(_file, _fileOptions) === false) {
1104 _nextFile.call(_this);
1105 return;
1106 }
1107 _fileOptions.file = _file;
1108
1109 _this._getFormData(_fileOptions, data, function (form){
1110 if( !_loaded ){
1111 // emit "upload" event
1112 options.upload(proxyXHR, options);
1113 }
1114
1115 var xhr = new api.XHR(_extend({}, _fileOptions, {
1116
1117 upload: _file ? function (){
1118 // emit "fileupload" event
1119 options.fileupload(_file, xhr, _fileOptions);
1120 } : noop,
1121
1122 progress: _file ? function (evt){
1123 if( !_fileLoaded ){
1124 // For ignore the double calls.
1125 _fileLoaded = (evt.loaded === evt.total);
1126
1127 // emit "fileprogress" event
1128 options.fileprogress({
1129 type: 'progress'
1130 , total: data.total = evt.total
1131 , loaded: data.loaded = evt.loaded
1132 }, _file, xhr, _fileOptions);
1133
1134 // emit "progress" event
1135 options.progress({
1136 type: 'progress'
1137 , total: _total
1138 , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1139 }, _file, xhr, _fileOptions);
1140 }
1141 } : noop,
1142
1143 complete: function (err){
1144 _each(_xhrPropsExport, function (name){
1145 proxyXHR[name] = xhr[name];
1146 });
1147
1148 if( _file ){
1149 data.total = (data.total || data.size);
1150 data.loaded = data.total;
1151
1152 if( !err ) {
1153 // emulate 100% "progress"
1154 this.progress(data);
1155
1156 // fixed throttle event
1157 _fileLoaded = true;
1158
1159 // bytes loaded
1160 _loaded += data.size; // data.size != data.total, it's desirable fix this
1161 proxyXHR.loaded = _loaded;
1162 }
1163
1164 // emit "filecomplete" event
1165 options.filecomplete(err, xhr, _file, _fileOptions);
1166 }
1167
1168 // upload next file
1169 setTimeout(function () {_nextFile.call(_this);}, 0);
1170 }
1171 })); // xhr
1172
1173
1174 // ...
1175 proxyXHR.abort = function (current){
1176 if (!current) { dataArray.length = 0; }
1177 this.current = current;
1178 xhr.abort();
1179 };
1180
1181 // Start upload
1182 xhr.send(form);
1183 });
1184 }
1185 else {
1186 var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1187 options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1188 // Mark done state
1189 _complete = true;
1190 }
1191 };
1192
1193
1194 // Next tick
1195 setTimeout(_nextFile, 0);
1196
1197
1198 // Append more files to the existing request
1199 // first - add them to the queue head/tail
1200 proxyXHR.append = function (files, first) {
1201 files = api._getFilesDataArray([].concat(files));
1202
1203 _each(files, function (data) {
1204 _total += data.size;
1205 proxyXHR.files.push(data.file);
1206 if (first) {
1207 dataArray.unshift(data);
1208 } else {
1209 dataArray.push(data);
1210 }
1211 });
1212
1213 proxyXHR.statusText = "";
1214
1215 if( _complete ){
1216 _nextFile.call(_this);
1217 }
1218 };
1219
1220
1221 // Removes file from queue by file reference and returns it
1222 proxyXHR.remove = function (file) {
1223 var i = dataArray.length, _file;
1224 while( i-- ){
1225 if( dataArray[i].file == file ){
1226 _file = dataArray.splice(i, 1);
1227 _total -= _file.size;
1228 }
1229 }
1230 return _file;
1231 };
1232
1233 return proxyXHR;
1234 },
1235
1236
1237 _getFilesDataArray: function (data){
1238 var files = [], oFiles = {};
1239
1240 if( isInputFile(data) ){
1241 var tmp = api.getFiles(data);
1242 oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1243 }
1244 else if( _isArray(data) && isInputFile(data[0]) ){
1245 _each(data, function (input){
1246 oFiles[input.name || 'file'] = api.getFiles(input);
1247 });
1248 }
1249 else {
1250 oFiles = data;
1251 }
1252
1253 _each(oFiles, function add(file, name){
1254 if( _isArray(file) ){
1255 _each(file, function (file){
1256 add(file, name);
1257 });
1258 }
1259 else if( file && (file.name || file.image) ){
1260 files.push({
1261 name: name
1262 , file: file
1263 , size: file.size
1264 , total: file.size
1265 , loaded: 0
1266 });
1267 }
1268 });
1269
1270 if( !files.length ){
1271 // Create fake `file` object
1272 files.push({ file: { name: api.expando } });
1273 }
1274
1275 return files;
1276 },
1277
1278
1279 _getFormData: function (options, data, fn){
1280 var
1281 file = data.file
1282 , name = data.name
1283 , filename = file.name
1284 , filetype = file.type
1285 , trans = api.support.transform && options.imageTransform
1286 , Form = new api.Form
1287 , queue = api.queue(function (){ fn(Form); })
1288 , isOrignTrans = trans && _isOriginTransform(trans)
1289 , postNameConcat = api.postNameConcat
1290 ;
1291
1292 // Append data
1293 _each(options.data, function add(val, name){
1294 if( typeof val == 'object' ){
1295 _each(val, function (v, i){
1296 add(v, postNameConcat(name, i));
1297 });
1298 }
1299 else {
1300 Form.append(name, val);
1301 }
1302 });
1303
1304 (function _addFile(file/**Object*/){
1305 if( file.image ){ // This is a FileAPI.Image
1306 queue.inc();
1307
1308 file.toData(function (err, image){
1309 // @todo: error
1310 filename = filename || (new Date).getTime()+'.png';
1311
1312 _addFile(image);
1313 queue.next();
1314 });
1315 }
1316 else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1317 queue.inc();
1318
1319 if( isOrignTrans ){
1320 // Convert to array for transform function
1321 trans = [trans];
1322 }
1323
1324 api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1325 if( isOrignTrans && !err ){
1326 if( !dataURLtoBlob && !api.flashEngine ){
1327 // Canvas.toBlob or Flash not supported, use multipart
1328 Form.multipart = true;
1329 }
1330
1331 Form.append(name, images[0], filename, trans[0].type || filetype);
1332 }
1333 else {
1334 var addOrigin = 0;
1335
1336 if( !err ){
1337 _each(images, function (image, idx){
1338 if( !dataURLtoBlob && !api.flashEngine ){
1339 Form.multipart = true;
1340 }
1341
1342 if( !trans[idx].postName ){
1343 addOrigin = 1;
1344 }
1345
1346 Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1347 });
1348 }
1349
1350 if( err || options.imageOriginal ){
1351 Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1352 }
1353 }
1354
1355 queue.next();
1356 });
1357 }
1358 else if( filename !== api.expando ){
1359 Form.append(name, file, filename);
1360 }
1361 })(file);
1362
1363 queue.check();
1364 },
1365
1366
1367 reset: function (inp, notRemove){
1368 var parent, clone;
1369
1370 if( jQuery ){
1371 clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1372 if( !notRemove ){
1373 jQuery(inp).remove();
1374 }
1375 } else {
1376 parent = inp.parentNode;
1377 clone = parent.insertBefore(inp.cloneNode(true), inp);
1378 clone.value = '';
1379
1380 if( !notRemove ){
1381 parent.removeChild(inp);
1382 }
1383
1384 _each(_elEvents[api.uid(inp)], function (fns, type){
1385 _each(fns, function (fn){
1386 _off(inp, type, fn);
1387 _on(clone, type, fn);
1388 });
1389 });
1390 }
1391
1392 return clone;
1393 },
1394
1395
1396 /**
1397 * Load remote file
1398 *
1399 * @param {String} url
1400 * @param {Function} fn
1401 * @return {XMLHttpRequest}
1402 */
1403 load: function (url, fn){
1404 var xhr = api.getXHR();
1405 if( xhr ){
1406 xhr.open('GET', url, true);
1407
1408 if( xhr.overrideMimeType ){
1409 xhr.overrideMimeType('text/plain; charset=x-user-defined');
1410 }
1411
1412 _on(xhr, 'progress', function (/**Event*/evt){
1413 /** @namespace evt.lengthComputable */
1414 if( evt.lengthComputable ){
1415 fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1416 }
1417 });
1418
1419 xhr.onreadystatechange = function(){
1420 if( xhr.readyState == 4 ){
1421 xhr.onreadystatechange = null;
1422 if( xhr.status == 200 ){
1423 url = url.split('/');
1424 /** @namespace xhr.responseBody */
1425 var file = {
1426 name: url[url.length-1]
1427 , size: xhr.getResponseHeader('Content-Length')
1428 , type: xhr.getResponseHeader('Content-Type')
1429 };
1430 file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1431 fn({ type: 'load', result: file }, xhr);
1432 }
1433 else {
1434 fn({ type: 'error' }, xhr);
1435 }
1436 }
1437 };
1438 xhr.send(null);
1439 } else {
1440 fn({ type: 'error' });
1441 }
1442
1443 return xhr;
1444 },
1445
1446 encode64: function (str){
1447 var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1448
1449 if( typeof str !== 'string' ){
1450 str = String(str);
1451 }
1452
1453 while( i < str.length ){
1454 //all three "& 0xff" added below are there to fix a known bug
1455 //with bytes returned by xhr.responseText
1456 var
1457 byte1 = str.charCodeAt(i++) & 0xff
1458 , byte2 = str.charCodeAt(i++) & 0xff
1459 , byte3 = str.charCodeAt(i++) & 0xff
1460 , enc1 = byte1 >> 2
1461 , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1462 , enc3, enc4
1463 ;
1464
1465 if( isNaN(byte2) ){
1466 enc3 = enc4 = 64;
1467 } else {
1468 enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1469 enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1470 }
1471
1472 outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1473 }
1474
1475 return outStr;
1476 }
1477
1478 } // api
1479 ;
1480
1481
1482 function _emit(target, fn, name, res, ext){
1483 var evt = {
1484 type: name.type || name
1485 , target: target
1486 , result: res
1487 };
1488 _extend(evt, ext);
1489 fn(evt);
1490 }
1491
1492
1493 function _hasSupportReadAs(as){
1494 return FileReader && !!FileReader.prototype['readAs'+as];
1495 }
1496
1497
1498 function _readAs(file, fn, as, encoding){
1499 if( api.isBlob(file) && _hasSupportReadAs(as) ){
1500 var Reader = new FileReader;
1501
1502 // Add event listener
1503 _on(Reader, _readerEvents, function _fn(evt){
1504 var type = evt.type;
1505 if( type == 'progress' ){
1506 _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1507 }
1508 else if( type == 'loadend' ){
1509 _off(Reader, _readerEvents, _fn);
1510 Reader = null;
1511 }
1512 else {
1513 _emit(file, fn, evt, evt.target.result);
1514 }
1515 });
1516
1517
1518 try {
1519 // ReadAs ...
1520 if( encoding ){
1521 Reader['readAs'+as](file, encoding);
1522 }
1523 else {
1524 Reader['readAs'+as](file);
1525 }
1526 }
1527 catch (err){
1528 _emit(file, fn, 'error', undef, { error: err.toString() });
1529 }
1530 }
1531 else {
1532 _emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });
1533 }
1534 }
1535
1536
1537 function _isRegularFile(file, callback){
1538 // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1539 if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1540 if( FileReader ){
1541 try {
1542 var Reader = new FileReader();
1543
1544 _one(Reader, _readerEvents, function (evt){
1545 var isFile = evt.type != 'error';
1546 callback(isFile);
1547 if( isFile ){
1548 Reader.abort();
1549 }
1550 });
1551
1552 Reader.readAsDataURL(file);
1553 } catch( err ){
1554 callback(false);
1555 }
1556 }
1557 else {
1558 callback(null);
1559 }
1560 }
1561 else {
1562 callback(true);
1563 }
1564 }
1565
1566
1567 function _getAsEntry(item){
1568 var entry;
1569 if( item.getAsEntry ){ entry = item.getAsEntry(); }
1570 else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1571 return entry;
1572 }
1573
1574
1575 function _readEntryAsFiles(entry, callback){
1576 if( !entry ){
1577 // error
1578 callback('invalid entry');
1579 }
1580 else if( entry.isFile ){
1581 // Read as file
1582 entry.file(function(file){
1583 // success
1584 file.fullPath = entry.fullPath;
1585 callback(false, [file]);
1586 }, function (err){
1587 // error
1588 callback('FileError.code: '+err.code);
1589 });
1590 }
1591 else if( entry.isDirectory ){
1592 var reader = entry.createReader(), result = [];
1593
1594 reader.readEntries(function(entries){
1595 // success
1596 api.afor(entries, function (next, entry){
1597 _readEntryAsFiles(entry, function (err, files){
1598 if( err ){
1599 api.log(err);
1600 }
1601 else {
1602 result = result.concat(files);
1603 }
1604
1605 if( next ){
1606 next();
1607 }
1608 else {
1609 callback(false, result);
1610 }
1611 });
1612 });
1613 }, function (err){
1614 // error
1615 callback('directory_reader: ' + err);
1616 });
1617 }
1618 else {
1619 _readEntryAsFiles(_getAsEntry(entry), callback);
1620 }
1621 }
1622
1623
1624 function _simpleClone(obj){
1625 var copy = {};
1626 _each(obj, function (val, key){
1627 if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1628 val = _extend({}, val);
1629 }
1630 copy[key] = val;
1631 });
1632 return copy;
1633 }
1634
1635
1636 function isInputFile(el){
1637 return _rinput.test(el && el.tagName);
1638 }
1639
1640
1641 function _getDataTransfer(evt){
1642 return (evt.originalEvent || evt || '').dataTransfer || {};
1643 }
1644
1645
1646 function _isOriginTransform(trans){
1647 var key;
1648 for( key in trans ){
1649 if( trans.hasOwnProperty(key) ){
1650 if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1651 return true;
1652 }
1653 }
1654 }
1655 return false;
1656 }
1657
1658
1659 // Add default image info reader
1660 api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1661 if( !file.__dimensions ){
1662 var defer = file.__dimensions = api.defer();
1663
1664 api.readAsImage(file, function (evt){
1665 var img = evt.target;
1666 defer.resolve(evt.type == 'load' ? false : 'error', {
1667 width: img.width
1668 , height: img.height
1669 });
1670 img.src = api.EMPTY_PNG;
1671 img = null;
1672 });
1673 }
1674
1675 file.__dimensions.then(callback);
1676 });
1677
1678
1679 /**
1680 * Drag'n'Drop special event
1681 *
1682 * @param {HTMLElement} el
1683 * @param {Function} onHover
1684 * @param {Function} onDrop
1685 */
1686 api.event.dnd = function (el, onHover, onDrop){
1687 var _id, _type;
1688
1689 if( !onDrop ){
1690 onDrop = onHover;
1691 onHover = api.F;
1692 }
1693
1694 if( FileReader ){
1695 // Hover
1696 _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1697 var
1698 types = _getDataTransfer(evt).types
1699 , i = types && types.length
1700 , debounceTrigger = false
1701 ;
1702
1703 while( i-- ){
1704 if( ~types[i].indexOf('File') ){
1705 evt[preventDefault]();
1706
1707 if( _type !== evt.type ){
1708 _type = evt.type; // Store current type of event
1709
1710 if( _type != 'dragleave' ){
1711 onHover.call(evt[currentTarget], true, evt);
1712 }
1713
1714 debounceTrigger = true;
1715 }
1716
1717 break; // exit from "while"
1718 }
1719 }
1720
1721 if( debounceTrigger ){
1722 clearTimeout(_id);
1723 _id = setTimeout(function (){
1724 onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1725 }, 50);
1726 }
1727 });
1728
1729
1730 // Drop
1731 _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1732 evt[preventDefault]();
1733
1734 _type = 0;
1735 onHover.call(evt[currentTarget], false, evt);
1736
1737 api.getDropFiles(evt, function (files){
1738 onDrop.call(evt[currentTarget], files, evt);
1739 });
1740 });
1741 }
1742 else {
1743 api.log("Drag'n'Drop -- not supported");
1744 }
1745 };
1746
1747
1748 /**
1749 * Remove drag'n'drop
1750 * @param {HTMLElement} el
1751 * @param {Function} onHover
1752 * @param {Function} onDrop
1753 */
1754 api.event.dnd.off = function (el, onHover, onDrop){
1755 _off(el, 'dragenter dragleave dragover', onHover.ff);
1756 _off(el, 'drop', onDrop.ff);
1757 };
1758
1759
1760 // Support jQuery
1761 if( jQuery && !jQuery.fn.dnd ){
1762 jQuery.fn.dnd = function (onHover, onDrop){
1763 return this.each(function (){
1764 api.event.dnd(this, onHover, onDrop);
1765 });
1766 };
1767
1768 jQuery.fn.offdnd = function (onHover, onDrop){
1769 return this.each(function (){
1770 api.event.dnd.off(this, onHover, onDrop);
1771 });
1772 };
1773 }
1774
1775 // @export
1776 window.FileAPI = _extend(api, window.FileAPI);
1777
1778
1779 // Debug info
1780 api.log('FileAPI: ' + api.version);
1781 api.log('protocol: ' + window.location.protocol);
1782 api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1783
1784
1785 // @detect 'x-ua-compatible'
1786 _each(document.getElementsByTagName('meta'), function (meta){
1787 if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1788 api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1789 }
1790 });
1791
1792
1793 // configuration
1794 try {
1795 api._supportConsoleLog = !!console.log;
1796 api._supportConsoleLogApply = !!console.log.apply;
1797 } catch (err) {}
1798
1799 if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1800 if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1801 if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1802 })(window, void 0);
1803
1804 /*global window, FileAPI, document */
1805
1806 (function (api, document, undef) {
1807 'use strict';
1808
1809 var
1810 min = Math.min,
1811 round = Math.round,
1812 getCanvas = function () { return document.createElement('canvas'); },
1813 support = false,
1814 exifOrientation = {
1815 8: 270
1816 , 3: 180
1817 , 6: 90
1818 , 7: 270
1819 , 4: 180
1820 , 5: 90
1821 }
1822 ;
1823
1824 try {
1825 support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1826 }
1827 catch (e){}
1828
1829
1830 function Image(file){
1831 if( file instanceof Image ){
1832 var img = new Image(file.file);
1833 api.extend(img.matrix, file.matrix);
1834 return img;
1835 }
1836 else if( !(this instanceof Image) ){
1837 return new Image(file);
1838 }
1839
1840 this.file = file;
1841 this.size = file.size || 100;
1842
1843 this.matrix = {
1844 sx: 0,
1845 sy: 0,
1846 sw: 0,
1847 sh: 0,
1848 dx: 0,
1849 dy: 0,
1850 dw: 0,
1851 dh: 0,
1852 resize: 0, // min, max OR preview
1853 deg: 0,
1854 quality: 1, // jpeg quality
1855 filter: 0
1856 };
1857 }
1858
1859
1860 Image.prototype = {
1861 image: true,
1862 constructor: Image,
1863
1864 set: function (attrs){
1865 api.extend(this.matrix, attrs);
1866 return this;
1867 },
1868
1869 crop: function (x, y, w, h){
1870 if( w === undef ){
1871 w = x;
1872 h = y;
1873 x = y = 0;
1874 }
1875 return this.set({ sx: x, sy: y, sw: w, sh: h || w });
1876 },
1877
1878 resize: function (w, h, strategy){
1879 if( /min|max/.test(h) ){
1880 strategy = h;
1881 h = w;
1882 }
1883
1884 return this.set({ dw: w, dh: h || w, resize: strategy });
1885 },
1886
1887 preview: function (w, h){
1888 return this.resize(w, h || w, 'preview');
1889 },
1890
1891 rotate: function (deg){
1892 return this.set({ deg: deg });
1893 },
1894
1895 filter: function (filter){
1896 return this.set({ filter: filter });
1897 },
1898
1899 overlay: function (images){
1900 return this.set({ overlay: images });
1901 },
1902
1903 clone: function (){
1904 return new Image(this);
1905 },
1906
1907 _load: function (image, fn){
1908 var self = this;
1909
1910 if( /img|video/i.test(image.nodeName) ){
1911 fn.call(self, null, image);
1912 }
1913 else {
1914 api.readAsImage(image, function (evt){
1915 fn.call(self, evt.type != 'load', evt.result);
1916 });
1917 }
1918 },
1919
1920 _apply: function (image, fn){
1921 var
1922 canvas = getCanvas()
1923 , m = this.getMatrix(image)
1924 , ctx = canvas.getContext('2d')
1925 , width = image.videoWidth || image.width
1926 , height = image.videoHeight || image.height
1927 , deg = m.deg
1928 , dw = m.dw
1929 , dh = m.dh
1930 , w = width
1931 , h = height
1932 , filter = m.filter
1933 , copy // canvas copy
1934 , buffer = image
1935 , overlay = m.overlay
1936 , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1937 , renderImageToCanvas = api.renderImageToCanvas
1938 ;
1939
1940 // Normalize angle
1941 deg = deg - Math.floor(deg/360)*360;
1942
1943 // For `renderImageToCanvas`
1944 image._type = this.file.type;
1945
1946 while(m.multipass && min(w/dw, h/dh) > 2 ){
1947 w = (w/2 + 0.5)|0;
1948 h = (h/2 + 0.5)|0;
1949
1950 copy = getCanvas();
1951 copy.width = w;
1952 copy.height = h;
1953
1954 if( buffer !== image ){
1955 renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1956 buffer = copy;
1957 }
1958 else {
1959 buffer = copy;
1960 renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1961 m.sx = m.sy = m.sw = m.sh = 0;
1962 }
1963 }
1964
1965
1966 canvas.width = (deg % 180) ? dh : dw;
1967 canvas.height = (deg % 180) ? dw : dh;
1968
1969 canvas.type = m.type;
1970 canvas.quality = m.quality;
1971
1972 ctx.rotate(deg * Math.PI / 180);
1973 renderImageToCanvas(ctx.canvas, buffer
1974 , m.sx, m.sy
1975 , m.sw || buffer.width
1976 , m.sh || buffer.height
1977 , (deg == 180 || deg == 270 ? -dw : 0)
1978 , (deg == 90 || deg == 180 ? -dh : 0)
1979 , dw, dh
1980 );
1981 dw = canvas.width;
1982 dh = canvas.height;
1983
1984 // Apply overlay
1985 overlay && api.each([].concat(overlay), function (over){
1986 queue.inc();
1987 // preload
1988 var img = new window.Image, fn = function (){
1989 var
1990 x = over.x|0
1991 , y = over.y|0
1992 , w = over.w || img.width
1993 , h = over.h || img.height
1994 , rel = over.rel
1995 ;
1996
1997 // center | right | left
1998 x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
1999
2000 // center | bottom | top
2001 y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
2002
2003 api.event.off(img, 'error load abort', fn);
2004
2005 try {
2006 ctx.globalAlpha = over.opacity || 1;
2007 ctx.drawImage(img, x, y, w, h);
2008 }
2009 catch (er){}
2010
2011 queue.next();
2012 };
2013
2014 api.event.on(img, 'error load abort', fn);
2015 img.src = over.src;
2016
2017 if( img.complete ){
2018 fn();
2019 }
2020 });
2021
2022 if( filter ){
2023 queue.inc();
2024 Image.applyFilter(canvas, filter, queue.next);
2025 }
2026
2027 queue.check();
2028 },
2029
2030 getMatrix: function (image){
2031 var
2032 m = api.extend({}, this.matrix)
2033 , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
2034 , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2035 , dw = m.dw = m.dw || sw
2036 , dh = m.dh = m.dh || sh
2037 , sf = sw/sh, df = dw/dh
2038 , strategy = m.resize
2039 ;
2040
2041 if( strategy == 'preview' ){
2042 if( dw != sw || dh != sh ){
2043 // Make preview
2044 var w, h;
2045
2046 if( df >= sf ){
2047 w = sw;
2048 h = w / df;
2049 } else {
2050 h = sh;
2051 w = h * df;
2052 }
2053
2054 if( w != sw || h != sh ){
2055 m.sx = ~~((sw - w)/2);
2056 m.sy = ~~((sh - h)/2);
2057 sw = w;
2058 sh = h;
2059 }
2060 }
2061 }
2062 else if( strategy ){
2063 if( !(sw > dw || sh > dh) ){
2064 dw = sw;
2065 dh = sh;
2066 }
2067 else if( strategy == 'min' ){
2068 dw = round(sf < df ? min(sw, dw) : dh*sf);
2069 dh = round(sf < df ? dw/sf : min(sh, dh));
2070 }
2071 else {
2072 dw = round(sf >= df ? min(sw, dw) : dh*sf);
2073 dh = round(sf >= df ? dw/sf : min(sh, dh));
2074 }
2075 }
2076
2077 m.sw = sw;
2078 m.sh = sh;
2079 m.dw = dw;
2080 m.dh = dh;
2081 m.multipass = api.multiPassResize;
2082 return m;
2083 },
2084
2085 _trans: function (fn){
2086 this._load(this.file, function (err, image){
2087 if( err ){
2088 fn(err);
2089 }
2090 else {
2091 try {
2092 this._apply(image, fn);
2093 } catch (err){
2094 api.log('[err] FileAPI.Image.fn._apply:', err);
2095 fn(err);
2096 }
2097 }
2098 });
2099 },
2100
2101
2102 get: function (fn){
2103 if( api.support.transform ){
2104 var _this = this, matrix = _this.matrix;
2105
2106 if( matrix.deg == 'auto' ){
2107 api.getInfo(_this.file, function (err, info){
2108 // rotate by exif orientation
2109 matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2110 _this._trans(fn);
2111 });
2112 }
2113 else {
2114 _this._trans(fn);
2115 }
2116 }
2117 else {
2118 fn('not_support_transform');
2119 }
2120
2121 return this;
2122 },
2123
2124
2125 toData: function (fn){
2126 return this.get(fn);
2127 }
2128
2129 };
2130
2131
2132 Image.exifOrientation = exifOrientation;
2133
2134
2135 Image.transform = function (file, transform, autoOrientation, fn){
2136 function _transform(err, img){
2137 // img -- info object
2138 var
2139 images = {}
2140 , queue = api.queue(function (err){
2141 fn(err, images);
2142 })
2143 ;
2144
2145 if( !err ){
2146 api.each(transform, function (params, name){
2147 if( !queue.isFail() ){
2148 var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2149
2150 if( isFn ){
2151 params(img, ImgTrans);
2152 }
2153 else if( params.width ){
2154 ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2155 }
2156 else {
2157 if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2158 ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2159 }
2160 }
2161
2162 if( params.crop ){
2163 var crop = params.crop;
2164 ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2165 }
2166
2167 if( params.rotate === undef && autoOrientation ){
2168 params.rotate = 'auto';
2169 }
2170
2171 ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2172
2173 if( !isFn ){
2174 ImgTrans.set({
2175 deg: params.rotate
2176 , overlay: params.overlay
2177 , filter: params.filter
2178 , quality: params.quality || 1
2179 });
2180 }
2181
2182 queue.inc();
2183 ImgTrans.toData(function (err, image){
2184 if( err ){
2185 queue.fail();
2186 }
2187 else {
2188 images[name] = image;
2189 queue.next();
2190 }
2191 });
2192 }
2193 });
2194 }
2195 else {
2196 queue.fail();
2197 }
2198 }
2199
2200
2201 // @todo: Оло-ло, нужно рефакторить это место
2202 if( file.width ){
2203 _transform(false, file);
2204 } else {
2205 api.getInfo(file, _transform);
2206 }
2207 };
2208
2209
2210 // @const
2211 api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2212 api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2213 Image[x+'_'+y] = i*3 + j;
2214 Image[y+'_'+x] = i*3 + j;
2215 });
2216 });
2217
2218
2219 /**
2220 * Trabsform element to canvas
2221 *
2222 * @param {Image|HTMLVideoElement} el
2223 * @returns {Canvas}
2224 */
2225 Image.toCanvas = function(el){
2226 var canvas = document.createElement('canvas');
2227 canvas.width = el.videoWidth || el.width;
2228 canvas.height = el.videoHeight || el.height;
2229 canvas.getContext('2d').drawImage(el, 0, 0);
2230 return canvas;
2231 };
2232
2233
2234 /**
2235 * Create image from DataURL
2236 * @param {String} dataURL
2237 * @param {Object} size
2238 * @param {Function} callback
2239 */
2240 Image.fromDataURL = function (dataURL, size, callback){
2241 var img = api.newImage(dataURL);
2242 api.extend(img, size);
2243 callback(img);
2244 };
2245
2246
2247 /**
2248 * Apply filter (caman.js)
2249 *
2250 * @param {Canvas|Image} canvas
2251 * @param {String|Function} filter
2252 * @param {Function} doneFn
2253 */
2254 Image.applyFilter = function (canvas, filter, doneFn){
2255 if( typeof filter == 'function' ){
2256 filter(canvas, doneFn);
2257 }
2258 else if( window.Caman ){
2259 // http://camanjs.com/guides/
2260 window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2261 if( typeof filter == 'string' ){
2262 this[filter]();
2263 }
2264 else {
2265 api.each(filter, function (val, method){
2266 this[method](val);
2267 }, this);
2268 }
2269 this.render(doneFn);
2270 });
2271 }
2272 };
2273
2274
2275 /**
2276 * For load-image-ios.js
2277 */
2278 api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2279 try {
2280 return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2281 } catch (ex) {
2282 api.log('renderImageToCanvas failed');
2283 throw ex;
2284 }
2285 };
2286
2287
2288 // @export
2289 api.support.canvas = api.support.transform = support;
2290 api.Image = Image;
2291 })(FileAPI, document);
2292
2293 /*
2294 * JavaScript Load Image iOS scaling fixes 1.0.3
2295 * https://github.com/blueimp/JavaScript-Load-Image
2296 *
2297 * Copyright 2013, Sebastian Tschan
2298 * https://blueimp.net
2299 *
2300 * iOS image scaling fixes based on
2301 * https://github.com/stomita/ios-imagefile-megapixel
2302 *
2303 * Licensed under the MIT license:
2304 * http://www.opensource.org/licenses/MIT
2305 */
2306
2307 /*jslint nomen: true, bitwise: true */
2308 /*global FileAPI, window, document */
2309
2310 (function (factory) {
2311 'use strict';
2312 factory(FileAPI);
2313 }(function (loadImage) {
2314 'use strict';
2315
2316 // Only apply fixes on the iOS platform:
2317 if (!window.navigator || !window.navigator.platform ||
2318 !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2319 return;
2320 }
2321
2322 var originalRenderMethod = loadImage.renderImageToCanvas;
2323
2324 // Detects subsampling in JPEG images:
2325 loadImage.detectSubsampling = function (img) {
2326 var canvas,
2327 context;
2328 if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2329 canvas = document.createElement('canvas');
2330 canvas.width = canvas.height = 1;
2331 context = canvas.getContext('2d');
2332 context.drawImage(img, -img.width + 1, 0);
2333 // subsampled image becomes half smaller in rendering size.
2334 // check alpha channel value to confirm image is covering edge pixel or not.
2335 // if alpha value is 0 image is not covering, hence subsampled.
2336 return context.getImageData(0, 0, 1, 1).data[3] === 0;
2337 }
2338 return false;
2339 };
2340
2341 // Detects vertical squash in JPEG images:
2342 loadImage.detectVerticalSquash = function (img, subsampled) {
2343 var naturalHeight = img.naturalHeight || img.height,
2344 canvas = document.createElement('canvas'),
2345 context = canvas.getContext('2d'),
2346 data,
2347 sy,
2348 ey,
2349 py,
2350 alpha;
2351 if (subsampled) {
2352 naturalHeight /= 2;
2353 }
2354 canvas.width = 1;
2355 canvas.height = naturalHeight;
2356 context.drawImage(img, 0, 0);
2357 data = context.getImageData(0, 0, 1, naturalHeight).data;
2358 // search image edge pixel position in case it is squashed vertically:
2359 sy = 0;
2360 ey = naturalHeight;
2361 py = naturalHeight;
2362 while (py > sy) {
2363 alpha = data[(py - 1) * 4 + 3];
2364 if (alpha === 0) {
2365 ey = py;
2366 } else {
2367 sy = py;
2368 }
2369 py = (ey + sy) >> 1;
2370 }
2371 return (py / naturalHeight) || 1;
2372 };
2373
2374 // Renders image to canvas while working around iOS image scaling bugs:
2375 // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2376 loadImage.renderImageToCanvas = function (
2377 canvas,
2378 img,
2379 sourceX,
2380 sourceY,
2381 sourceWidth,
2382 sourceHeight,
2383 destX,
2384 destY,
2385 destWidth,
2386 destHeight
2387 ) {
2388 if (img._type === 'image/jpeg') {
2389 var context = canvas.getContext('2d'),
2390 tmpCanvas = document.createElement('canvas'),
2391 tileSize = 1024,
2392 tmpContext = tmpCanvas.getContext('2d'),
2393 subsampled,
2394 vertSquashRatio,
2395 tileX,
2396 tileY;
2397 tmpCanvas.width = tileSize;
2398 tmpCanvas.height = tileSize;
2399 context.save();
2400 subsampled = loadImage.detectSubsampling(img);
2401 if (subsampled) {
2402 sourceX /= 2;
2403 sourceY /= 2;
2404 sourceWidth /= 2;
2405 sourceHeight /= 2;
2406 }
2407 vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2408 if (subsampled || vertSquashRatio !== 1) {
2409 sourceY *= vertSquashRatio;
2410 destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2411 destHeight = Math.ceil(
2412 tileSize * destHeight / sourceHeight / vertSquashRatio
2413 );
2414 destY = 0;
2415 tileY = 0;
2416 while (tileY < sourceHeight) {
2417 destX = 0;
2418 tileX = 0;
2419 while (tileX < sourceWidth) {
2420 tmpContext.clearRect(0, 0, tileSize, tileSize);
2421 tmpContext.drawImage(
2422 img,
2423 sourceX,
2424 sourceY,
2425 sourceWidth,
2426 sourceHeight,
2427 -tileX,
2428 -tileY,
2429 sourceWidth,
2430 sourceHeight
2431 );
2432 context.drawImage(
2433 tmpCanvas,
2434 0,
2435 0,
2436 tileSize,
2437 tileSize,
2438 destX,
2439 destY,
2440 destWidth,
2441 destHeight
2442 );
2443 tileX += tileSize;
2444 destX += destWidth;
2445 }
2446 tileY += tileSize;
2447 destY += destHeight;
2448 }
2449 context.restore();
2450 return canvas;
2451 }
2452 }
2453 return originalRenderMethod(
2454 canvas,
2455 img,
2456 sourceX,
2457 sourceY,
2458 sourceWidth,
2459 sourceHeight,
2460 destX,
2461 destY,
2462 destWidth,
2463 destHeight
2464 );
2465 };
2466
2467 }));
2468
2469 /*global window, FileAPI */
2470
2471 (function (api, window){
2472 "use strict";
2473
2474 var
2475 document = window.document
2476 , FormData = window.FormData
2477 , Form = function (){ this.items = []; }
2478 , encodeURIComponent = window.encodeURIComponent
2479 ;
2480
2481
2482 Form.prototype = {
2483
2484 append: function (name, blob, file, type){
2485 this.items.push({
2486 name: name
2487 , blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2488 , file: blob && (file || blob.name)
2489 , type: blob && (type || blob.type)
2490 });
2491 },
2492
2493 each: function (fn){
2494 var i = 0, n = this.items.length;
2495 for( ; i < n; i++ ){
2496 fn.call(this, this.items[i]);
2497 }
2498 },
2499
2500 toData: function (fn, options){
2501 // allow chunked transfer if we have only one file to send
2502 // flag is used below and in XHR._send
2503 options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2504
2505 if( !api.support.html5 ){
2506 api.log('FileAPI.Form.toHtmlData');
2507 this.toHtmlData(fn);
2508 }
2509 else if( !api.formData || this.multipart || !FormData ){
2510 api.log('FileAPI.Form.toMultipartData');
2511 this.toMultipartData(fn);
2512 }
2513 else if( options._chunked ){
2514 api.log('FileAPI.Form.toPlainData');
2515 this.toPlainData(fn);
2516 }
2517 else {
2518 api.log('FileAPI.Form.toFormData');
2519 this.toFormData(fn);
2520 }
2521 },
2522
2523 _to: function (data, complete, next, arg){
2524 var queue = api.queue(function (){
2525 complete(data);
2526 });
2527
2528 this.each(function (file){
2529 next(file, data, queue, arg);
2530 });
2531
2532 queue.check();
2533 },
2534
2535
2536 toHtmlData: function (fn){
2537 this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2538 var blob = file.blob, hidden;
2539
2540 if( file.file ){
2541 api.reset(blob, true);
2542 // set new name
2543 blob.name = file.name;
2544 blob.disabled = false;
2545 data.appendChild(blob);
2546 }
2547 else {
2548 hidden = document.createElement('input');
2549 hidden.name = file.name;
2550 hidden.type = 'hidden';
2551 hidden.value = blob;
2552 data.appendChild(hidden);
2553 }
2554 });
2555 },
2556
2557 toPlainData: function (fn){
2558 this._to({}, fn, function (file, data, queue){
2559 if( file.file ){
2560 data.type = file.file;
2561 }
2562
2563 if( file.blob.toBlob ){
2564 // canvas
2565 queue.inc();
2566 _convertFile(file, function (file, blob){
2567 data.name = file.name;
2568 data.file = blob;
2569 data.size = blob.length;
2570 data.type = file.type;
2571 queue.next();
2572 });
2573 }
2574 else if( file.file ){
2575 // file
2576 data.name = file.blob.name;
2577 data.file = file.blob;
2578 data.size = file.blob.size;
2579 data.type = file.type;
2580 }
2581 else {
2582 // additional data
2583 if( !data.params ){
2584 data.params = [];
2585 }
2586 data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2587 }
2588
2589 data.start = -1;
2590 data.end = data.file && data.file.FileAPIReadPosition || -1;
2591 data.retry = 0;
2592 });
2593 },
2594
2595 toFormData: function (fn){
2596 this._to(new FormData, fn, function (file, data, queue){
2597 if( file.blob && file.blob.toBlob ){
2598 queue.inc();
2599 _convertFile(file, function (file, blob){
2600 data.append(file.name, blob, file.file);
2601 queue.next();
2602 });
2603 }
2604 else if( file.file ){
2605 data.append(file.name, file.blob, file.file);
2606 }
2607 else {
2608 data.append(file.name, file.blob);
2609 }
2610
2611 if( file.file ){
2612 data.append('_'+file.name, file.file);
2613 }
2614 });
2615 },
2616
2617
2618 toMultipartData: function (fn){
2619 this._to([], fn, function (file, data, queue, boundary){
2620 queue.inc();
2621 _convertFile(file, function (file, blob){
2622 data.push(
2623 '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2624 + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2625 + '\r\n'
2626 + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2627 + '\r\n')
2628 );
2629 queue.next();
2630 }, true);
2631 }, api.expando);
2632 }
2633 };
2634
2635
2636 function _convertFile(file, fn, useBinaryString){
2637 var blob = file.blob, filename = file.file;
2638
2639 if( filename ){
2640 if( !blob.toDataURL ){
2641 // The Blob is not an image.
2642 api.readAsBinaryString(blob, function (evt){
2643 if( evt.type == 'load' ){
2644 fn(file, evt.result);
2645 }
2646 });
2647 return;
2648 }
2649
2650 var
2651 mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2652 , type = mime[file.type] ? file.type : 'image/png'
2653 , ext = mime[type] || '.png'
2654 , quality = blob.quality || 1
2655 ;
2656
2657 if( !filename.match(new RegExp(ext+'$', 'i')) ){
2658 // Does not change the current extension, but add a new one.
2659 filename += ext.replace('?', '');
2660 }
2661
2662 file.file = filename;
2663 file.type = type;
2664
2665 if( !useBinaryString && blob.toBlob ){
2666 blob.toBlob(function (blob){
2667 fn(file, blob);
2668 }, type, quality);
2669 }
2670 else {
2671 fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2672 }
2673 }
2674 else {
2675 fn(file, blob);
2676 }
2677 }
2678
2679
2680 // @export
2681 api.Form = Form;
2682 })(FileAPI, window);
2683
2684 /*global window, FileAPI, Uint8Array */
2685
2686 (function (window, api){
2687 "use strict";
2688
2689 var
2690 noop = function (){}
2691 , document = window.document
2692
2693 , XHR = function (options){
2694 this.uid = api.uid();
2695 this.xhr = {
2696 abort: noop
2697 , getResponseHeader: noop
2698 , getAllResponseHeaders: noop
2699 };
2700 this.options = options;
2701 },
2702
2703 _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2704 ;
2705
2706
2707 XHR.prototype = {
2708 status: 0,
2709 statusText: '',
2710 constructor: XHR,
2711
2712 getResponseHeader: function (name){
2713 return this.xhr.getResponseHeader(name);
2714 },
2715
2716 getAllResponseHeaders: function (){
2717 return this.xhr.getAllResponseHeaders() || {};
2718 },
2719
2720 end: function (status, statusText){
2721 var _this = this, options = _this.options;
2722
2723 _this.end =
2724 _this.abort = noop;
2725 _this.status = status;
2726
2727 if( statusText ){
2728 _this.statusText = statusText;
2729 }
2730
2731 api.log('xhr.end:', status, statusText);
2732 options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2733
2734 if( _this.xhr && _this.xhr.node ){
2735 setTimeout(function (){
2736 var node = _this.xhr.node;
2737 try { node.parentNode.removeChild(node); } catch (e){}
2738 try { delete window[_this.uid]; } catch (e){}
2739 window[_this.uid] = _this.xhr.node = null;
2740 }, 9);
2741 }
2742 },
2743
2744 abort: function (){
2745 this.end(0, 'abort');
2746
2747 if( this.xhr ){
2748 this.xhr.aborted = true;
2749 this.xhr.abort();
2750 }
2751 },
2752
2753 send: function (FormData){
2754 var _this = this, options = this.options;
2755
2756 FormData.toData(function (data){
2757 // Start uploading
2758 options.upload(options, _this);
2759 _this._send.call(_this, options, data);
2760 }, options);
2761 },
2762
2763 _send: function (options, data){
2764 var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2765
2766 api.log('XHR._send:', data);
2767
2768 if( !options.cache ){
2769 // No cache
2770 url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2771 }
2772
2773 if( data.nodeName ){
2774 var jsonp = options.jsonp;
2775
2776 // prepare callback in GET
2777 url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2778
2779 // legacy
2780 options.upload(options, _this);
2781
2782 var
2783 onPostMessage = function (evt){
2784 if( ~url.indexOf(evt.origin) ){
2785 try {
2786 var result = api.parseJSON(evt.data);
2787 if( result.id == uid ){
2788 complete(result.status, result.statusText, result.response);
2789 }
2790 } catch( err ){
2791 complete(0, err.message);
2792 }
2793 }
2794 },
2795
2796 // jsonp-callack
2797 complete = window[uid] = function (status, statusText, response){
2798 _this.readyState = 4;
2799 _this.responseText = response;
2800 _this.end(status, statusText);
2801
2802 api.event.off(window, 'message', onPostMessage);
2803 window[uid] = xhr = transport = window[onloadFuncName] = null;
2804 }
2805 ;
2806
2807 _this.xhr.abort = function (){
2808 try {
2809 if( transport.stop ){ transport.stop(); }
2810 else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2811 else { transport.contentWindow.document.execCommand('Stop'); }
2812 }
2813 catch (er) {}
2814 complete(0, "abort");
2815 };
2816
2817 api.event.on(window, 'message', onPostMessage);
2818
2819 window[onloadFuncName] = function (){
2820 try {
2821 var
2822 win = transport.contentWindow
2823 , doc = win.document
2824 , result = win.result || api.parseJSON(doc.body.innerHTML)
2825 ;
2826 complete(result.status, result.statusText, result.response);
2827 } catch (e){
2828 api.log('[transport.onload]', e);
2829 }
2830 };
2831
2832 xhr = document.createElement('div');
2833 xhr.innerHTML = '<form target="'+ uid +'" action="'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2834 + '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2835 + (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2836 + '</form>'
2837 ;
2838
2839 // get form-data & transport
2840 var
2841 form = xhr.getElementsByTagName('form')[0]
2842 , transport = xhr.getElementsByTagName('iframe')[0]
2843 ;
2844
2845 form.appendChild(data);
2846
2847 api.log(form.parentNode.innerHTML);
2848
2849 // append to DOM
2850 document.body.appendChild(xhr);
2851
2852 // keep a reference to node-transport
2853 _this.xhr.node = xhr;
2854
2855 // send
2856 _this.readyState = 2; // loaded
2857 form.submit();
2858 form = null;
2859 }
2860 else {
2861 // Clean url
2862 url = url.replace(/([a-z]+)=(\?)&?/i, '');
2863
2864 // html5
2865 if (this.xhr && this.xhr.aborted) {
2866 api.log("Error: already aborted");
2867 return;
2868 }
2869 xhr = _this.xhr = api.getXHR();
2870
2871 if (data.params) {
2872 url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2873 }
2874
2875 xhr.open('POST', url, true);
2876
2877 if( api.withCredentials ){
2878 xhr.withCredentials = "true";
2879 }
2880
2881 if( !options.headers || !options.headers['X-Requested-With'] ){
2882 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2883 }
2884
2885 api.each(options.headers, function (val, key){
2886 xhr.setRequestHeader(key, val);
2887 });
2888
2889
2890 if ( options._chunked ) {
2891 // chunked upload
2892 if( xhr.upload ){
2893 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2894 if (!data.retry) {
2895 // show progress only for correct chunk uploads
2896 options.progress({
2897 type: evt.type
2898 , total: data.size
2899 , loaded: data.start + evt.loaded
2900 , totalSize: data.size
2901 }, _this, options);
2902 }
2903 }, 100), false);
2904 }
2905
2906 xhr.onreadystatechange = function (){
2907 var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2908
2909 _this.status = xhr.status;
2910 _this.statusText = xhr.statusText;
2911 _this.readyState = xhr.readyState;
2912
2913 if( xhr.readyState == 4 ){
2914 try {
2915 for( var k in _xhrResponsePostfix ){
2916 _this['response'+k] = xhr['response'+k];
2917 }
2918 }catch(_){}
2919 xhr.onreadystatechange = null;
2920
2921 if (!xhr.status || xhr.status - 201 > 0) {
2922 api.log("Error: " + xhr.status);
2923 // some kind of error
2924 // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2925 // up - server error
2926 if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2927 // let's try again the same chunk
2928 // only applicable for recoverable error codes 500 && 416
2929 var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2930
2931 // inform about recoverable problems
2932 options.pause(data.file, options);
2933
2934 // smart restart if server reports about the last known byte
2935 api.log("X-Last-Known-Byte: " + lkb);
2936 if (lkb) {
2937 data.end = lkb;
2938 } else {
2939 data.end = data.start - 1;
2940 if (416 == xhr.status) {
2941 data.end = data.end - options.chunkSize;
2942 }
2943 }
2944
2945 setTimeout(function () {
2946 _this._send(options, data);
2947 }, delay);
2948 } else {
2949 // no mo retries
2950 _this.end(xhr.status);
2951 }
2952 } else {
2953 // success
2954 data.retry = 0;
2955
2956 if (data.end == data.size - 1) {
2957 // finished
2958 _this.end(xhr.status);
2959 } else {
2960 // next chunk
2961
2962 // shift position if server reports about the last known byte
2963 api.log("X-Last-Known-Byte: " + lkb);
2964 if (lkb) {
2965 data.end = lkb;
2966 }
2967 data.file.FileAPIReadPosition = data.end;
2968
2969 setTimeout(function () {
2970 _this._send(options, data);
2971 }, 0);
2972 }
2973 }
2974
2975 xhr = null;
2976 }
2977 };
2978
2979 data.start = data.end + 1;
2980 data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2981
2982 // Retrieve a slice of file
2983 var
2984 file = data.file
2985 , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2986 ;
2987
2988 if( data.size && !slice.size ){
2989 setTimeout(function (){
2990 _this.end(-1);
2991 });
2992 } else {
2993 xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2994 xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2995 xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2996
2997 xhr.send(slice);
2998 }
2999
3000 file = slice = null;
3001 } else {
3002 // single piece upload
3003 if( xhr.upload ){
3004 // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3005 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3006 options.progress(evt, _this, options);
3007 }, 100), false);
3008 }
3009
3010 xhr.onreadystatechange = function (){
3011 _this.status = xhr.status;
3012 _this.statusText = xhr.statusText;
3013 _this.readyState = xhr.readyState;
3014
3015 if( xhr.readyState == 4 ){
3016 for( var k in _xhrResponsePostfix ){
3017 _this['response'+k] = xhr['response'+k];
3018 }
3019 xhr.onreadystatechange = null;
3020
3021 if (!xhr.status || xhr.status > 201) {
3022 api.log("Error: " + xhr.status);
3023 if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3024 options.retry = (options.retry || 0) + 1;
3025 var delay = api.networkDownRetryTimeout;
3026
3027 // inform about recoverable problems
3028 options.pause(options.file, options);
3029
3030 setTimeout(function () {
3031 _this._send(options, data);
3032 }, delay);
3033 } else {
3034 //success
3035 _this.end(xhr.status);
3036 }
3037 } else {
3038 //success
3039 _this.end(xhr.status);
3040 }
3041
3042 xhr = null;
3043 }
3044 };
3045
3046 if( api.isArray(data) ){
3047 // multipart
3048 xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3049 var rawData = data.join('') +'--_'+ api.expando +'--';
3050
3051 /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3052 if( xhr.sendAsBinary ){
3053 xhr.sendAsBinary(rawData);
3054 }
3055 else {
3056 var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3057 xhr.send(new Uint8Array(bytes).buffer);
3058
3059 }
3060 } else {
3061 // FormData
3062 xhr.send(data);
3063 }
3064 }
3065 }
3066 }
3067 };
3068
3069
3070 // @export
3071 api.XHR = XHR;
3072 })(window, FileAPI);
3073
3074 /**
3075 * @class FileAPI.Camera
3076 * @author RubaXa <trash@rubaxa.org>
3077 * @support Chrome 21+, FF 18+, Opera 12+
3078 */
3079
3080 /*global window, FileAPI, jQuery */
3081 /** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3082 (function (window, api){
3083 "use strict";
3084
3085 var
3086 URL = window.URL || window.webkitURL,
3087
3088 document = window.document,
3089 navigator = window.navigator,
3090
3091 getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3092
3093 html5 = !!getMedia
3094 ;
3095
3096
3097 // Support "media"
3098 api.support.media = html5;
3099
3100
3101 var Camera = function (video){
3102 this.video = video;
3103 };
3104
3105
3106 Camera.prototype = {
3107 isActive: function (){
3108 return !!this._active;
3109 },
3110
3111
3112 /**
3113 * Start camera streaming
3114 * @param {Function} callback
3115 */
3116 start: function (callback){
3117 var
3118 _this = this
3119 , video = _this.video
3120 , _successId
3121 , _failId
3122 , _complete = function (err){
3123 _this._active = !err;
3124 clearTimeout(_failId);
3125 clearTimeout(_successId);
3126 // api.event.off(video, 'loadedmetadata', _complete);
3127 callback && callback(err, _this);
3128 }
3129 ;
3130
3131 getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3132 // Success
3133 _this.stream = stream;
3134
3135 // api.event.on(video, 'loadedmetadata', function (){
3136 // _complete(null);
3137 // });
3138
3139 // Set camera stream
3140 video.src = URL.createObjectURL(stream);
3141
3142 // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3143 // See crbug.com/110938.
3144 _successId = setInterval(function (){
3145 if( _detectVideoSignal(video) ){
3146 _complete(null);
3147 }
3148 }, 1000);
3149
3150 _failId = setTimeout(function (){
3151 _complete('timeout');
3152 }, 5000);
3153
3154 // Go-go-go!
3155 video.play();
3156 }, _complete/*error*/);
3157 },
3158
3159
3160 /**
3161 * Stop camera streaming
3162 */
3163 stop: function (){
3164 try {
3165 this._active = false;
3166 this.video.pause();
3167 this.stream.stop();
3168 } catch( err ){ }
3169 },
3170
3171
3172 /**
3173 * Create screenshot
3174 * @return {FileAPI.Camera.Shot}
3175 */
3176 shot: function (){
3177 return new Shot(this.video);
3178 }
3179 };
3180
3181
3182 /**
3183 * Get camera element from container
3184 *
3185 * @static
3186 * @param {HTMLElement} el
3187 * @return {Camera}
3188 */
3189 Camera.get = function (el){
3190 return new Camera(el.firstChild);
3191 };
3192
3193
3194 /**
3195 * Publish camera element into container
3196 *
3197 * @static
3198 * @param {HTMLElement} el
3199 * @param {Object} options
3200 * @param {Function} [callback]
3201 */
3202 Camera.publish = function (el, options, callback){
3203 if( typeof options == 'function' ){
3204 callback = options;
3205 options = {};
3206 }
3207
3208 // Dimensions of "camera"
3209 options = api.extend({}, {
3210 width: '100%'
3211 , height: '100%'
3212 , start: true
3213 }, options);
3214
3215
3216 if( el.jquery ){
3217 // Extract first element, from jQuery collection
3218 el = el[0];
3219 }
3220
3221
3222 var doneFn = function (err){
3223 if( err ){
3224 callback(err);
3225 }
3226 else {
3227 // Get camera
3228 var cam = Camera.get(el);
3229 if( options.start ){
3230 cam.start(callback);
3231 }
3232 else {
3233 callback(null, cam);
3234 }
3235 }
3236 };
3237
3238
3239 el.style.width = _px(options.width);
3240 el.style.height = _px(options.height);
3241
3242
3243 if( api.html5 && html5 ){
3244 // Create video element
3245 var video = document.createElement('video');
3246
3247 // Set dimensions
3248 video.style.width = _px(options.width);
3249 video.style.height = _px(options.height);
3250
3251 // Clean container
3252 if( window.jQuery ){
3253 jQuery(el).empty();
3254 } else {
3255 el.innerHTML = '';
3256 }
3257
3258 // Add "camera" to container
3259 el.appendChild(video);
3260
3261 // end
3262 doneFn();
3263 }
3264 else {
3265 Camera.fallback(el, options, doneFn);
3266 }
3267 };
3268
3269
3270 Camera.fallback = function (el, options, callback){
3271 callback('not_support_camera');
3272 };
3273
3274
3275 /**
3276 * @class FileAPI.Camera.Shot
3277 */
3278 var Shot = function (video){
3279 var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
3280 var shot = api.Image(canvas);
3281 shot.type = 'image/png';
3282 shot.width = canvas.width;
3283 shot.height = canvas.height;
3284 shot.size = canvas.width * canvas.height * 4;
3285 return shot;
3286 };
3287
3288
3289 /**
3290 * Add "px" postfix, if value is a number
3291 *
3292 * @private
3293 * @param {*} val
3294 * @return {String}
3295 */
3296 function _px(val){
3297 return val >= 0 ? val + 'px' : val;
3298 }
3299
3300
3301 /**
3302 * @private
3303 * @param {HTMLVideoElement} video
3304 * @return {Boolean}
3305 */
3306 function _detectVideoSignal(video){
3307 var canvas = document.createElement('canvas'), ctx, res = false;
3308 try {
3309 ctx = canvas.getContext('2d');
3310 ctx.drawImage(video, 0, 0, 1, 1);
3311 res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3312 }
3313 catch( e ){}
3314 return res;
3315 }
3316
3317
3318 // @export
3319 Camera.Shot = Shot;
3320 api.Camera = Camera;
3321 })(window, FileAPI);
3322
3323 /**
3324 * FileAPI fallback to Flash
3325 *
3326 * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
3327 */
3328
3329 /*global window, ActiveXObject, FileAPI */
3330 (function (window, jQuery, api) {
3331 "use strict";
3332
3333 var
3334 document = window.document
3335 , location = window.location
3336 , navigator = window.navigator
3337 , _each = api.each
3338 ;
3339
3340
3341 api.support.flash = (function (){
3342 var mime = navigator.mimeTypes, has = false;
3343
3344 if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3345 has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3346 }
3347 else {
3348 try {
3349 has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3350 }
3351 catch(er){
3352 api.log('Flash -- does not supported.');
3353 }
3354 }
3355
3356 if( has && /^file:/i.test(location) ){
3357 api.log('[warn] Flash does not work on `file:` protocol.');
3358 }
3359
3360 return has;
3361 })();
3362
3363
3364 api.support.flash
3365 && (0
3366 || !api.html5 || !api.support.html5
3367 || (api.cors && !api.support.cors)
3368 || (api.media && !api.support.media)
3369 )
3370 && (function (){
3371 var
3372 _attr = api.uid()
3373 , _retry = 0
3374 , _files = {}
3375 , _rhttp = /^https?:/i
3376
3377 , flash = {
3378 _fn: {},
3379
3380
3381 /**
3382 * Publish flash-object
3383 *
3384 * @param {HTMLElement} el
3385 * @param {String} id
3386 * @param {Object} [opts]
3387 */
3388 publish: function (el, id, opts){
3389 opts = opts || {};
3390 el.innerHTML = _makeFlashHTML({
3391 id: id
3392 , src: _getUrl(api.flashUrl, 'r=' + api.version)
3393 // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3394 , wmode: opts.camera ? '' : 'transparent'
3395 , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3396 + '&flashId='+ id
3397 + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3398 + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3399 + '&timeout='+api.flashAbortTimeout
3400 + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3401 + '&debug='+(api.debug?"1":"")
3402 }, opts);
3403 },
3404
3405
3406 /**
3407 * Initialization & preload flash object
3408 */
3409 init: function (){
3410 var child = document.body && document.body.firstChild;
3411
3412 if( child ){
3413 do {
3414 if( child.nodeType == 1 ){
3415 api.log('FlashAPI.state: awaiting');
3416
3417 var dummy = document.createElement('div');
3418
3419 dummy.id = '_' + _attr;
3420
3421 _css(dummy, {
3422 top: 1
3423 , right: 1
3424 , width: 5
3425 , height: 5
3426 , position: 'absolute'
3427 , zIndex: 1e6+'' // set max zIndex
3428 });
3429
3430 child.parentNode.insertBefore(dummy, child);
3431 flash.publish(dummy, _attr);
3432
3433 return;
3434 }
3435 }
3436 while( child = child.nextSibling );
3437 }
3438
3439 if( _retry < 10 ){
3440 setTimeout(flash.init, ++_retry*50);
3441 }
3442 },
3443
3444
3445 ready: function (){
3446 api.log('FlashAPI.state: ready');
3447
3448 flash.ready = api.F;
3449 flash.isReady = true;
3450 flash.patch();
3451 flash.patchCamera && flash.patchCamera();
3452 api.event.on(document, 'mouseover', flash.mouseover);
3453 api.event.on(document, 'click', function (evt){
3454 if( flash.mouseover(evt) ){
3455 evt.preventDefault
3456 ? evt.preventDefault()
3457 : (evt.returnValue = true)
3458 ;
3459 }
3460 });
3461 },
3462
3463
3464 getEl: function (){
3465 return document.getElementById('_'+_attr);
3466 },
3467
3468
3469 getWrapper: function (node){
3470 do {
3471 if( /js-fileapi-wrapper/.test(node.className) ){
3472 return node;
3473 }
3474 }
3475 while( (node = node.parentNode) && (node !== document.body) );
3476 },
3477
3478 disableMouseover: false,
3479
3480 mouseover: function (evt){
3481 if (!flash.disableMouseover) {
3482 var target = api.event.fix(evt).target;
3483
3484 if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3485 var
3486 state = target.getAttribute(_attr)
3487 , wrapper = flash.getWrapper(target)
3488 ;
3489
3490 if( api.multiFlash ){
3491 // check state:
3492 // i — published
3493 // i — initialization
3494 // r — ready
3495 if( state == 'i' || state == 'r' ){
3496 // publish fail
3497 return false;
3498 }
3499 else if( state != 'p' ){
3500 // set "init" state
3501 target.setAttribute(_attr, 'i');
3502
3503 var dummy = document.createElement('div');
3504
3505 if( !wrapper ){
3506 api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3507 return;
3508 }
3509
3510 _css(dummy, {
3511 top: 0
3512 , left: 0
3513 , width: target.offsetWidth
3514 , height: target.offsetHeight
3515 , zIndex: 1e6+'' // set max zIndex
3516 , position: 'absolute'
3517 });
3518
3519 wrapper.appendChild(dummy);
3520 flash.publish(dummy, api.uid());
3521
3522 // set "publish" state
3523 target.setAttribute(_attr, 'p');
3524 }
3525
3526 return true;
3527 }
3528 else if( wrapper ){
3529 // Use one flash element
3530 var box = _getDimensions(wrapper);
3531 _css(flash.getEl(), box);
3532
3533 // Set current input
3534 flash.curInp = target;
3535 }
3536 }
3537 else if( !/object|embed/i.test(target.nodeName) ){
3538 _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3539 }
3540 }
3541 },
3542
3543 onEvent: function (evt){
3544 var type = evt.type;
3545
3546 if( type == 'ready' ){
3547 try {
3548 // set "ready" state
3549 flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3550 } catch (e){
3551 }
3552
3553 flash.ready();
3554 setTimeout(function (){ flash.mouseenter(evt); }, 50);
3555 return true;
3556 }
3557 else if( type === 'ping' ){
3558 api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3559 }
3560 else if( type === 'log' ){
3561 api.log('(flash -> js).log:', evt.target);
3562 }
3563 else if( type in flash ){
3564 setTimeout(function (){
3565 api.log('FlashAPI.event.'+evt.type+':', evt);
3566 flash[type](evt);
3567 }, 1);
3568 }
3569 },
3570 mouseDown: function(evt) {
3571 flash.disableMouseover = true;
3572 },
3573 cancel: function(evt) {
3574 flash.disableMouseover = false;
3575 },
3576 mouseenter: function (evt){
3577 var node = flash.getInput(evt.flashId);
3578
3579 if( node ){
3580 // Set multiple mode
3581 flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3582
3583
3584 // Set files filter
3585 var accept = [], exts = {};
3586
3587 _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3588 api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3589 exts[ext] = 1;
3590 });
3591 });
3592
3593 _each(exts, function (i, ext){
3594 accept.push( ext );
3595 });
3596
3597 flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3598 }
3599 },
3600
3601
3602 get: function (id){
3603 return document[id] || window[id] || document.embeds[id];
3604 },
3605
3606
3607 getInput: function (id){
3608 if( api.multiFlash ){
3609 try {
3610 var node = flash.getWrapper(flash.get(id));
3611 if( node ){
3612 return node.getElementsByTagName('input')[0];
3613 }
3614 } catch (e){
3615 api.log('[err] Can not find "input" by flashId:', id, e);
3616 }
3617 } else {
3618 return flash.curInp;
3619 }
3620 },
3621
3622
3623 select: function (evt){
3624 try {
3625 var
3626 inp = flash.getInput(evt.flashId)
3627 , uid = api.uid(inp)
3628 , files = evt.target.files
3629 , event
3630 ;
3631 _each(files, function (file){
3632 api.checkFileObj(file);
3633 });
3634
3635 _files[uid] = files;
3636
3637 if( document.createEvent ){
3638 event = document.createEvent('Event');
3639 event.files = files;
3640 event.initEvent('change', true, true);
3641 inp.dispatchEvent(event);
3642 }
3643 else if( jQuery ){
3644 jQuery(inp).trigger({ type: 'change', files: files });
3645 }
3646 else {
3647 event = document.createEventObject();
3648 event.files = files;
3649 inp.fireEvent('onchange', event);
3650 }
3651 } finally {
3652 flash.disableMouseover = false;
3653 }
3654 },
3655
3656 interval: null,
3657 cmd: function (id, name, data, last) {
3658 if (flash.uploadInProgress && flash.readInProgress) {
3659 setTimeout(function() {
3660 flash.cmd(id, name, data, last);
3661 }, 100);
3662 } else {
3663 this.cmdFn(id, name, data, last);
3664 }
3665 },
3666
3667 cmdFn: function(id, name, data, last) {
3668 try {
3669 api.log('(js -> flash).'+name+':', data);
3670 return flash.get(id.flashId || id).cmd(name, data);
3671 } catch (e){
3672 api.log('(js -> flash).onError:', e);
3673 if( !last ){
3674 // try again
3675 setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3676 }
3677 }
3678 },
3679
3680 patch: function (){
3681 api.flashEngine = true;
3682
3683 // FileAPI
3684 _inherit(api, {
3685 readAsDataURL: function (file, callback){
3686 if( _isHtmlFile(file) ){
3687 this.parent.apply(this, arguments);
3688 }
3689 else {
3690 api.log('FlashAPI.readAsBase64');
3691 flash.readInProgress = true;
3692 flash.cmd(file, 'readAsBase64', {
3693 id: file.id,
3694 callback: _wrap(function _(err, base64){
3695 flash.readInProgress = false;
3696 _unwrap(_);
3697
3698 api.log('FlashAPI.readAsBase64:', err);
3699
3700 callback({
3701 type: err ? 'error' : 'load'
3702 , error: err
3703 , result: 'data:'+ file.type +';base64,'+ base64
3704 });
3705 })
3706 });
3707 }
3708 },
3709
3710 readAsText: function (file, encoding, callback){
3711 if( callback ){
3712 api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3713 } else {
3714 callback = encoding;
3715 }
3716
3717 api.readAsDataURL(file, function (evt){
3718 if( evt.type == 'load' ){
3719 try {
3720 evt.result = window.atob(evt.result.split(';base64,')[1]);
3721 } catch( err ){
3722 evt.type = 'error';
3723 evt.error = err.toString();
3724 }
3725 }
3726 callback(evt);
3727 });
3728 },
3729
3730 getFiles: function (input, filter, callback){
3731 if( callback ){
3732 api.filterFiles(api.getFiles(input), filter, callback);
3733 return null;
3734 }
3735
3736 var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3737
3738
3739 if( !files ){
3740 // Файлов нету, вызываем родительский метод
3741 return this.parent.apply(this, arguments);
3742 }
3743
3744
3745 if( filter ){
3746 filter = api.getFilesFilter(filter);
3747 files = api.filter(files, function (file){ return filter.test(file.name); });
3748 }
3749
3750 return files;
3751 },
3752
3753
3754 getInfo: function (file, fn){
3755 if( _isHtmlFile(file) ){
3756 this.parent.apply(this, arguments);
3757 }
3758 else if( file.isShot ){
3759 fn(null, file.info = {
3760 width: file.width,
3761 height: file.height
3762 });
3763 }
3764 else {
3765 if( !file.__info ){
3766 var defer = file.__info = api.defer();
3767
3768 // flash.cmd(file, 'getFileInfo', {
3769 // id: file.id
3770 // , callback: _wrap(function _(err, info){
3771 // _unwrap(_);
3772 // defer.resolve(err, file.info = info);
3773 // })
3774 // });
3775 defer.resolve(null, file.info = null);
3776
3777 }
3778
3779 file.__info.then(fn);
3780 }
3781 }
3782 });
3783
3784
3785 // FileAPI.Image
3786 api.support.transform = true;
3787 api.Image && _inherit(api.Image.prototype, {
3788 get: function (fn, scaleMode){
3789 this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3790 return this.parent(fn);
3791 },
3792
3793 _load: function (file, fn){
3794 api.log('FlashAPI.Image._load:', file);
3795
3796 if( _isHtmlFile(file) ){
3797 this.parent.apply(this, arguments);
3798 }
3799 else {
3800 var _this = this;
3801 api.getInfo(file, function (err){
3802 fn.call(_this, err, file);
3803 });
3804 }
3805 },
3806
3807 _apply: function (file, fn){
3808 api.log('FlashAPI.Image._apply:', file);
3809
3810 if( _isHtmlFile(file) ){
3811 this.parent.apply(this, arguments);
3812 }
3813 else {
3814 var m = this.getMatrix(file.info), doneFn = fn;
3815
3816 flash.cmd(file, 'imageTransform', {
3817 id: file.id
3818 , matrix: m
3819 , callback: _wrap(function _(err, base64){
3820 api.log('FlashAPI.Image._apply.callback:', err);
3821 _unwrap(_);
3822
3823 if( err ){
3824 doneFn(err);
3825 }
3826 else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3827 _makeFlashImage({
3828 width: (m.deg % 180) ? m.dh : m.dw
3829 , height: (m.deg % 180) ? m.dw : m.dh
3830 , scale: m.scaleMode
3831 }, base64, doneFn);
3832 }
3833 else {
3834 if( m.filter ){
3835 doneFn = function (err, img){
3836 if( err ){
3837 fn(err);
3838 }
3839 else {
3840 api.Image.applyFilter(img, m.filter, function (){
3841 fn(err, this.canvas);
3842 });
3843 }
3844 };
3845 }
3846
3847 api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3848 }
3849 })
3850 });
3851 }
3852 },
3853
3854 toData: function (fn){
3855 var
3856 file = this.file
3857 , info = file.info
3858 , matrix = this.getMatrix(info)
3859 ;
3860 api.log('FlashAPI.Image.toData');
3861
3862 if( _isHtmlFile(file) ){
3863 this.parent.apply(this, arguments);
3864 }
3865 else {
3866 if( matrix.deg == 'auto' ){
3867 matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3868 }
3869
3870 fn.call(this, !file.info, {
3871 id: file.id
3872 , flashId: file.flashId
3873 , name: file.name
3874 , type: file.type
3875 , matrix: matrix
3876 });
3877 }
3878 }
3879 });
3880
3881
3882 api.Image && _inherit(api.Image, {
3883 fromDataURL: function (dataURL, size, callback){
3884 if( !api.support.dataURI || dataURL.length > 3e4 ){
3885 _makeFlashImage(
3886 api.extend({ scale: 'exactFit' }, size)
3887 , dataURL.replace(/^data:[^,]+,/, '')
3888 , function (err, el){ callback(el); }
3889 );
3890 }
3891 else {
3892 this.parent(dataURL, size, callback);
3893 }
3894 }
3895 });
3896
3897 // FileAPI.Form
3898 _inherit(api.Form.prototype, {
3899 toData: function (fn){
3900 var items = this.items, i = items.length;
3901
3902 for( ; i--; ){
3903 if( items[i].file && _isHtmlFile(items[i].blob) ){
3904 return this.parent.apply(this, arguments);
3905 }
3906 }
3907
3908 api.log('FlashAPI.Form.toData');
3909 fn(items);
3910 }
3911 });
3912
3913
3914 // FileAPI.XHR
3915 _inherit(api.XHR.prototype, {
3916 _send: function (options, formData){
3917 if(
3918 formData.nodeName
3919 || formData.append && api.support.html5
3920 || api.isArray(formData) && (typeof formData[0] === 'string')
3921 ){
3922 // HTML5, Multipart or IFrame
3923 return this.parent.apply(this, arguments);
3924 }
3925
3926
3927 var
3928 data = {}
3929 , files = {}
3930 , _this = this
3931 , flashId
3932 , fileId
3933 ;
3934
3935 _each(formData, function (item){
3936 if( item.file ){
3937 files[item.name] = item = _getFileDescr(item.blob);
3938 fileId = item.id;
3939 flashId = item.flashId;
3940 }
3941 else {
3942 data[item.name] = item.blob;
3943 }
3944 });
3945
3946 if( !fileId ){
3947 flashId = _attr;
3948 }
3949
3950 if( !flashId ){
3951 api.log('[err] FlashAPI._send: flashId -- undefined');
3952 return this.parent.apply(this, arguments);
3953 }
3954 else {
3955 api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3956 }
3957
3958 _this.xhr = {
3959 headers: {},
3960 abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3961 getResponseHeader: function (name){ return this.headers[name]; },
3962 getAllResponseHeaders: function (){ return this.headers; }
3963 };
3964
3965 var queue = api.queue(function (){
3966 flash.uploadInProgress = true;
3967 flash.cmd(flashId, 'upload', {
3968 url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3969 , data: data
3970 , files: fileId ? files : null
3971 , headers: options.headers || {}
3972 , callback: _wrap(function upload(evt){
3973 var type = evt.type, result = evt.result;
3974
3975 api.log('FlashAPI.upload.'+type);
3976
3977 if( type == 'progress' ){
3978 evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3979 evt.lengthComputable = true;
3980 options.progress(evt);
3981 }
3982 else if( type == 'complete' ){
3983 flash.uploadInProgress = false;
3984 _unwrap(upload);
3985
3986 if( typeof result == 'string' ){
3987 _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3988 }
3989
3990 _this.end(evt.status || 200);
3991 }
3992 else if( type == 'abort' || type == 'error' ){
3993 flash.uploadInProgress = false;
3994 _this.end(evt.status || 0, evt.message);
3995 _unwrap(upload);
3996 }
3997 })
3998 });
3999 });
4000
4001
4002 // #2174: FileReference.load() call while FileReference.upload() or vice versa
4003 _each(files, function (file){
4004 queue.inc();
4005 api.getInfo(file, queue.next);
4006 });
4007
4008 queue.check();
4009 }
4010 });
4011 }
4012 }
4013 ;
4014
4015
4016 function _makeFlashHTML(opts){
4017 return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4018 + '<param name="movie" value="#src#" />'
4019 + '<param name="flashvars" value="#flashvars#" />'
4020 + '<param name="swliveconnect" value="true" />'
4021 + '<param name="allowscriptaccess" value="always" />'
4022 + '<param name="allownetworking" value="all" />'
4023 + '<param name="menu" value="false" />'
4024 + '<param name="wmode" value="#wmode#" />'
4025 + '<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>'
4026 + '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4027 ;
4028 }
4029
4030
4031 function _css(el, css){
4032 if( el && el.style ){
4033 var key, val;
4034 for( key in css ){
4035 val = css[key];
4036 if( typeof val == 'number' ){
4037 val += 'px';
4038 }
4039 try { el.style[key] = val; } catch (e) {}
4040 }
4041
4042 }
4043 }
4044
4045
4046 function _inherit(obj, methods){
4047 _each(methods, function (fn, name){
4048 var prev = obj[name];
4049 obj[name] = function (){
4050 this.parent = prev;
4051 return fn.apply(this, arguments);
4052 };
4053 });
4054 }
4055
4056 function _isHtmlFile(file){
4057 return file && !file.flashId;
4058 }
4059
4060 function _wrap(fn){
4061 var id = fn.wid = api.uid();
4062 flash._fn[id] = fn;
4063 return 'FileAPI.Flash._fn.'+id;
4064 }
4065
4066
4067 function _unwrap(fn){
4068 try {
4069 flash._fn[fn.wid] = null;
4070 delete flash._fn[fn.wid];
4071 }
4072 catch(e){}
4073 }
4074
4075
4076 function _getUrl(url, params){
4077 if( !_rhttp.test(url) ){
4078 if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4079 var path = location.pathname;
4080 path = path.substr(0, path.lastIndexOf('/'));
4081 url = (path +'/'+ url).replace('/./', '/');
4082 }
4083
4084 if( '//' != url.substr(0, 2) ){
4085 url = '//' + location.host + url;
4086 }
4087
4088 if( !_rhttp.test(url) ){
4089 url = location.protocol + url;
4090 }
4091 }
4092
4093 if( params ){
4094 url += (/\?/.test(url) ? '&' : '?') + params;
4095 }
4096
4097 return url;
4098 }
4099
4100
4101 function _makeFlashImage(opts, base64, fn){
4102 var
4103 key
4104 , flashId = api.uid()
4105 , el = document.createElement('div')
4106 , attempts = 10
4107 ;
4108
4109 for( key in opts ){
4110 el.setAttribute(key, opts[key]);
4111 el[key] = opts[key];
4112 }
4113
4114 _css(el, opts);
4115
4116 opts.width = '100%';
4117 opts.height = '100%';
4118
4119 el.innerHTML = _makeFlashHTML(api.extend({
4120 id: flashId
4121 , src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4122 , wmode: 'opaque'
4123 , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4124 _unwrap(_);
4125 if( --attempts > 0 ){
4126 _setImage();
4127 }
4128 return true;
4129 })
4130 }, opts));
4131
4132 function _setImage(){
4133 try {
4134 // Get flash-object by id
4135 var img = flash.get(flashId);
4136 img.setImage(base64);
4137 } catch (e){
4138 api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4139 }
4140 }
4141
4142 fn(false, el);
4143 el = null;
4144 }
4145
4146
4147 function _getFileDescr(file){
4148 return {
4149 id: file.id
4150 , name: file.name
4151 , matrix: file.matrix
4152 , flashId: file.flashId
4153 };
4154 }
4155
4156
4157 function _getDimensions(el){
4158 var
4159 box = el.getBoundingClientRect()
4160 , body = document.body
4161 , docEl = (el && el.ownerDocument).documentElement
4162 ;
4163
4164 function getOffset(obj) {
4165 var left, top;
4166 left = top = 0;
4167 if (obj.offsetParent) {
4168 do {
4169 left += obj.offsetLeft;
4170 top += obj.offsetTop;
4171 } while (obj = obj.offsetParent);
4172 }
4173 return {
4174 left : left,
4175 top : top
4176 };
4177 };
4178
4179 return {
4180 top: getOffset(el).top
4181 , left: getOffset(el).left
4182 , width: el.offsetWidth
4183 , height: el.offsetHeight
4184 };
4185 }
4186
4187 // @export
4188 api.Flash = flash;
4189
4190
4191 // Check dataURI support
4192 api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4193 api.support.dataURI = !(img.width != 1 || img.height != 1);
4194 flash.init();
4195 });
4196 })();
4197 })(window, window.jQuery, FileAPI);
4198
4199 /**
4200 * FileAPI fallback to Flash
4201 *
4202 * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
4203 */
4204
4205 /*global window, FileAPI */
4206 (function (window, jQuery, api) {
4207 "use strict";
4208
4209 var _each = api.each,
4210 _cameraQueue = [];
4211
4212
4213 if (api.support.flash && (api.media && !api.support.media)) {
4214 (function () {
4215
4216 function _wrap(fn) {
4217 var id = fn.wid = api.uid();
4218 api.Flash._fn[id] = fn;
4219 return 'FileAPI.Flash._fn.' + id;
4220 }
4221
4222
4223 function _unwrap(fn) {
4224 try {
4225 api.Flash._fn[fn.wid] = null;
4226 delete api.Flash._fn[fn.wid];
4227 } catch (e) {
4228 }
4229 }
4230
4231 var flash = api.Flash;
4232 api.extend(api.Flash, {
4233
4234 patchCamera: function () {
4235 api.Camera.fallback = function (el, options, callback) {
4236 var camId = api.uid();
4237 api.log('FlashAPI.Camera.publish: ' + camId);
4238 flash.publish(el, camId, api.extend(options, {
4239 camera: true,
4240 onEvent: _wrap(function _(evt) {
4241 if (evt.type === 'camera') {
4242 _unwrap(_);
4243
4244 if (evt.error) {
4245 api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4246 callback(evt.error);
4247 } else {
4248 api.log('FlashAPI.Camera.publish.success: ' + camId);
4249 callback(null);
4250 }
4251 }
4252 })
4253 }));
4254 };
4255 // Run
4256 _each(_cameraQueue, function (args) {
4257 api.Camera.fallback.apply(api.Camera, args);
4258 });
4259 _cameraQueue = [];
4260
4261
4262 // FileAPI.Camera:proto
4263 api.extend(api.Camera.prototype, {
4264 _id: function () {
4265 return this.video.id;
4266 },
4267
4268 start: function (callback) {
4269 var _this = this;
4270 flash.cmd(this._id(), 'camera.on', {
4271 callback: _wrap(function _(evt) {
4272 _unwrap(_);
4273
4274 if (evt.error) {
4275 api.log('FlashAPI.camera.on.error: ' + evt.error);
4276 callback(evt.error, _this);
4277 } else {
4278 api.log('FlashAPI.camera.on.success: ' + _this._id());
4279 _this._active = true;
4280 callback(null, _this);
4281 }
4282 })
4283 });
4284 },
4285
4286 stop: function () {
4287 this._active = false;
4288 flash.cmd(this._id(), 'camera.off');
4289 },
4290
4291 shot: function () {
4292 api.log('FlashAPI.Camera.shot:', this._id());
4293
4294 var shot = api.Flash.cmd(this._id(), 'shot', {});
4295 shot.type = 'image/png';
4296 shot.flashId = this._id();
4297 shot.isShot = true;
4298
4299 return new api.Camera.Shot(shot);
4300 }
4301 });
4302 }
4303 });
4304
4305 api.Camera.fallback = function () {
4306 _cameraQueue.push(arguments);
4307 };
4308
4309 }());
4310 }
4311 }(window, window.jQuery, FileAPI));
4312 if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
+0
-247
src/data-url.js less more
0 (function () {
1
2 ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) {
3 var upload = UploadBase;
4 upload.base64DataUrl = function (file) {
5 if (angular.isArray(file)) {
6 var d = $q.defer(), count = 0;
7 angular.forEach(file, function (f) {
8 upload.dataUrl(f, true)['finally'](function () {
9 count++;
10 if (count === file.length) {
11 var urls = [];
12 angular.forEach(file, function (ff) {
13 urls.push(ff.$ngfDataUrl);
14 });
15 d.resolve(urls, file);
16 }
17 });
18 });
19 return d.promise;
20 } else {
21 return upload.dataUrl(file, true);
22 }
23 };
24 upload.dataUrl = function (file, disallowObjectUrl) {
25 if (!file) return upload.emptyPromise(file, file);
26 if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) {
27 return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file);
28 }
29 var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise;
30 if (p) return p;
31
32 var deferred = $q.defer();
33 $timeout(function () {
34 if (window.FileReader && file &&
35 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) &&
36 (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) {
37 //prefer URL.createObjectURL for handling refrences to files of all sizes
38 //since it doesn´t build a large string in memory
39 var URL = window.URL || window.webkitURL;
40 if (URL && URL.createObjectURL && !disallowObjectUrl) {
41 var url;
42 try {
43 url = URL.createObjectURL(file);
44 } catch (e) {
45 $timeout(function () {
46 file.$ngfBlobUrl = '';
47 deferred.reject();
48 });
49 return;
50 }
51 $timeout(function () {
52 file.$ngfBlobUrl = url;
53 if (url) {
54 deferred.resolve(url, file);
55 upload.blobUrls = upload.blobUrls || [];
56 upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0;
57 upload.blobUrls.push({url: url, size: file.size});
58 upload.blobUrlsTotalSize += file.size || 0;
59 var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456;
60 var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200;
61 while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) {
62 var obj = upload.blobUrls.splice(0, 1)[0];
63 URL.revokeObjectURL(obj.url);
64 upload.blobUrlsTotalSize -= obj.size;
65 }
66 }
67 });
68 } else {
69 var fileReader = new FileReader();
70 fileReader.onload = function (e) {
71 $timeout(function () {
72 file.$ngfDataUrl = e.target.result;
73 deferred.resolve(e.target.result, file);
74 $timeout(function () {
75 delete file.$ngfDataUrl;
76 }, 1000);
77 });
78 };
79 fileReader.onerror = function () {
80 $timeout(function () {
81 file.$ngfDataUrl = '';
82 deferred.reject();
83 });
84 };
85 fileReader.readAsDataURL(file);
86 }
87 } else {
88 $timeout(function () {
89 file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = '';
90 deferred.reject();
91 });
92 }
93 });
94
95 if (disallowObjectUrl) {
96 p = file.$$ngfDataUrlPromise = deferred.promise;
97 } else {
98 p = file.$$ngfBlobUrlPromise = deferred.promise;
99 }
100 p['finally'](function () {
101 delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise'];
102 });
103 return p;
104 };
105 return upload;
106 }]);
107
108 function getTagType(el) {
109 if (el.tagName.toLowerCase() === 'img') return 'image';
110 if (el.tagName.toLowerCase() === 'audio') return 'audio';
111 if (el.tagName.toLowerCase() === 'video') return 'video';
112 return /./;
113 }
114
115 function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) {
116 function constructDataUrl(file) {
117 var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope);
118 Upload.dataUrl(file, disallowObjectUrl)['finally'](function () {
119 $timeout(function () {
120 var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl;
121 if (isBackground) {
122 elem.css('background-image', 'url(\'' + (src || '') + '\')');
123 } else {
124 elem.attr('src', src);
125 }
126 if (src) {
127 elem.removeClass('ng-hide');
128 } else {
129 elem.addClass('ng-hide');
130 }
131 });
132 });
133 }
134
135 $timeout(function () {
136 var unwatch = scope.$watch(attr[directiveName], function (file) {
137 var size = resizeParams;
138 if (directiveName === 'ngfThumbnail') {
139 if (!size) {
140 size = {width: elem[0].clientWidth, height: elem[0].clientHeight};
141 }
142 if (size.width === 0 && window.getComputedStyle) {
143 var style = getComputedStyle(elem[0]);
144 size = {
145 width: parseInt(style.width.slice(0, -2)),
146 height: parseInt(style.height.slice(0, -2))
147 };
148 }
149 }
150
151 if (angular.isString(file)) {
152 elem.removeClass('ng-hide');
153 if (isBackground) {
154 return elem.css('background-image', 'url(\'' + file + '\')');
155 } else {
156 return elem.attr('src', file);
157 }
158 }
159 if (file && file.type && file.type.search(getTagType(elem[0])) === 0 &&
160 (!isBackground || file.type.indexOf('image') === 0)) {
161 if (size && Upload.isResizeSupported()) {
162 Upload.resize(file, size.width, size.height, size.quality).then(
163 function (f) {
164 constructDataUrl(f);
165 }, function (e) {
166 throw e;
167 }
168 );
169 } else {
170 constructDataUrl(file);
171 }
172 } else {
173 elem.addClass('ng-hide');
174 }
175 });
176
177 scope.$on('$destroy', function () {
178 unwatch();
179 });
180 });
181 }
182
183
184 /** @namespace attr.ngfSrc */
185 /** @namespace attr.ngfNoObjectUrl */
186 ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) {
187 return {
188 restrict: 'AE',
189 link: function (scope, elem, attr) {
190 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc',
191 Upload.attrGetter('ngfResize', attr, scope), false);
192 }
193 };
194 }]);
195
196 /** @namespace attr.ngfBackground */
197 /** @namespace attr.ngfNoObjectUrl */
198 ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) {
199 return {
200 restrict: 'AE',
201 link: function (scope, elem, attr) {
202 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground',
203 Upload.attrGetter('ngfResize', attr, scope), true);
204 }
205 };
206 }]);
207
208 /** @namespace attr.ngfThumbnail */
209 /** @namespace attr.ngfAsBackground */
210 /** @namespace attr.ngfSize */
211 /** @namespace attr.ngfNoObjectUrl */
212 ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) {
213 return {
214 restrict: 'AE',
215 link: function (scope, elem, attr) {
216 var size = Upload.attrGetter('ngfSize', attr, scope);
217 linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size,
218 Upload.attrGetter('ngfAsBackground', attr, scope));
219 }
220 };
221 }]);
222
223 ngFileUpload.config(['$compileProvider', function ($compileProvider) {
224 if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
225 if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/);
226 }]);
227
228 ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) {
229 return function (file, disallowObjectUrl, trustedUrl) {
230 if (angular.isString(file)) {
231 return $sce.trustAsResourceUrl(file);
232 }
233 var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl);
234 if (file && !src) {
235 if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) {
236 file.$ngfDataUrlFilterInProgress = true;
237 UploadDataUrl.dataUrl(file, disallowObjectUrl);
238 }
239 return '';
240 }
241 if (file) delete file.$ngfDataUrlFilterInProgress;
242 return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || '';
243 };
244 }]);
245
246 })();
+0
-339
src/drop.js less more
0 (function () {
1 ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
2 function ($parse, $timeout, $location, Upload, $http, $q) {
3 return {
4 restrict: 'AEC',
5 require: '?ngModel',
6 link: function (scope, elem, attr, ngModel) {
7 linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
8 }
9 };
10 }]);
11
12 ngFileUpload.directive('ngfNoFileDrop', function () {
13 return function (scope, elem) {
14 if (dropAvailable()) elem.css('display', 'none');
15 };
16 });
17
18 ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {
19 return function (scope, elem, attr) {
20 if (dropAvailable()) {
21 var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));
22 $timeout(function () {
23 model(scope);
24 if (model.assign) {
25 model.assign(scope, true);
26 }
27 });
28 }
29 };
30 }]);
31
32 function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
33 var available = dropAvailable();
34
35 var attrGetter = function (name, scope, params) {
36 return upload.attrGetter(name, attr, scope, params);
37 };
38
39 if (attrGetter('dropAvailable')) {
40 $timeout(function () {
41 if (scope[attrGetter('dropAvailable')]) {
42 scope[attrGetter('dropAvailable')].value = available;
43 } else {
44 scope[attrGetter('dropAvailable')] = available;
45 }
46 });
47 }
48 if (!available) {
49 if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {
50 elem.css('display', 'none');
51 }
52 return;
53 }
54
55 function isDisabled() {
56 return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);
57 }
58
59 if (attrGetter('ngfSelect') == null) {
60 upload.registerModelChangeValidator(ngModel, attr, scope);
61 }
62
63 var leaveTimeout = null;
64 var stopPropagation = $parse(attrGetter('ngfStopPropagation'));
65 var dragOverDelay = 1;
66 var actualDragOverClass;
67
68 elem[0].addEventListener('dragover', function (evt) {
69 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
70 evt.preventDefault();
71 if (stopPropagation(scope)) evt.stopPropagation();
72 // handling dragover events from the Chrome download bar
73 if (navigator.userAgent.indexOf('Chrome') > -1) {
74 var b = evt.dataTransfer.effectAllowed;
75 evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';
76 }
77 $timeout.cancel(leaveTimeout);
78 if (!actualDragOverClass) {
79 actualDragOverClass = 'C';
80 calculateDragOverClass(scope, attr, evt, function (clazz) {
81 actualDragOverClass = clazz;
82 elem.addClass(actualDragOverClass);
83 attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});
84 });
85 }
86 }, false);
87 elem[0].addEventListener('dragenter', function (evt) {
88 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
89 evt.preventDefault();
90 if (stopPropagation(scope)) evt.stopPropagation();
91 }, false);
92 elem[0].addEventListener('dragleave', function (evt) {
93 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
94 evt.preventDefault();
95 if (stopPropagation(scope)) evt.stopPropagation();
96 leaveTimeout = $timeout(function () {
97 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
98 actualDragOverClass = null;
99 attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});
100 }, dragOverDelay || 100);
101 }, false);
102 elem[0].addEventListener('drop', function (evt) {
103 if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
104 evt.preventDefault();
105 if (stopPropagation(scope)) evt.stopPropagation();
106 if (actualDragOverClass) elem.removeClass(actualDragOverClass);
107 actualDragOverClass = null;
108 var items = evt.dataTransfer.items;
109 var html;
110 try {
111 html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
112 } catch (e) {/* Fix IE11 that throw error calling getData */
113 }
114
115 extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
116 attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
117 if (files.length) {
118 updateModel(files, evt);
119 } else {
120 extractFilesFromHtml('dropUrl', html).then(function (files) {
121 updateModel(files, evt);
122 });
123 }
124 });
125 }, false);
126 elem[0].addEventListener('paste', function (evt) {
127 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
128 attrGetter('ngfEnableFirefoxPaste', scope)) {
129 evt.preventDefault();
130 }
131 if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
132 var files = [];
133 var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
134 if (clipboard && clipboard.items) {
135 for (var k = 0; k < clipboard.items.length; k++) {
136 if (clipboard.items[k].type.indexOf('image') !== -1) {
137 files.push(clipboard.items[k].getAsFile());
138 }
139 }
140 }
141 if (files.length) {
142 updateModel(files, evt);
143 } else {
144 extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
145 updateModel(files, evt);
146 });
147 }
148 }, false);
149
150 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
151 attrGetter('ngfEnableFirefoxPaste', scope)) {
152 elem.attr('contenteditable', true);
153 elem.on('keypress', function (e) {
154 if (!e.metaKey && !e.ctrlKey) {
155 e.preventDefault();
156 }
157 });
158 }
159
160 function updateModel(files, evt) {
161 upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
162 }
163
164 function extractFilesFromHtml(updateOn, html) {
165 if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
166 var urls = [];
167 html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
168 urls.push(src);
169 });
170 var promises = [], files = [];
171 if (urls.length) {
172 angular.forEach(urls, function (url) {
173 promises.push(upload.urlToBlob(url).then(function (blob) {
174 files.push(blob);
175 }));
176 });
177 var defer = $q.defer();
178 $q.all(promises).then(function () {
179 defer.resolve(files);
180 }, function (e) {
181 defer.reject(e);
182 });
183 return defer.promise;
184 }
185 return upload.emptyPromise();
186 }
187
188 function calculateDragOverClass(scope, attr, evt, callback) {
189 var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';
190 if (angular.isString(obj)) {
191 dClass = obj;
192 } else if (obj) {
193 if (obj.delay) dragOverDelay = obj.delay;
194 if (obj.accept || obj.reject) {
195 var items = evt.dataTransfer.items;
196 if (items == null || !items.length) {
197 dClass = obj.accept;
198 } else {
199 var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
200 var len = items.length;
201 while (len--) {
202 if (!upload.validatePattern(items[len], pattern)) {
203 dClass = obj.reject;
204 break;
205 } else {
206 dClass = obj.accept;
207 }
208 }
209 }
210 }
211 }
212 callback(dClass);
213 }
214
215 function extractFiles(items, fileList, allowDir, multiple) {
216 var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
217 var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
218 var includeDir = attrGetter('ngfIncludeDir', scope);
219 var files = [], totalSize = 0;
220
221 function traverseFileTree(entry, path) {
222 var defer = $q.defer();
223 if (entry != null) {
224 if (entry.isDirectory) {
225 var promises = [upload.emptyPromise()];
226 if (includeDir) {
227 var file = {type: 'directory'};
228 file.name = file.path = (path || '') + entry.name;
229 files.push(file);
230 }
231 var dirReader = entry.createReader();
232 var entries = [];
233 var readEntries = function () {
234 dirReader.readEntries(function (results) {
235 try {
236 if (!results.length) {
237 angular.forEach(entries.slice(0), function (e) {
238 if (files.length <= maxFiles && totalSize <= maxTotalSize) {
239 promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));
240 }
241 });
242 $q.all(promises).then(function () {
243 defer.resolve();
244 }, function (e) {
245 defer.reject(e);
246 });
247 } else {
248 entries = entries.concat(Array.prototype.slice.call(results || [], 0));
249 readEntries();
250 }
251 } catch (e) {
252 defer.reject(e);
253 }
254 }, function (e) {
255 defer.reject(e);
256 });
257 };
258 readEntries();
259 } else {
260 entry.file(function (file) {
261 try {
262 file.path = (path ? path : '') + file.name;
263 if (includeDir) {
264 file = upload.rename(file, file.path);
265 }
266 files.push(file);
267 totalSize += file.size;
268 defer.resolve();
269 } catch (e) {
270 defer.reject(e);
271 }
272 }, function (e) {
273 defer.reject(e);
274 });
275 }
276 }
277 return defer.promise;
278 }
279
280 var promises = [upload.emptyPromise()];
281
282 if (items && items.length > 0 && $location.protocol() !== 'file') {
283 for (var i = 0; i < items.length; i++) {
284 if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
285 var entry = items[i].webkitGetAsEntry();
286 if (entry.isDirectory && !allowDir) {
287 continue;
288 }
289 if (entry != null) {
290 promises.push(traverseFileTree(entry));
291 }
292 } else {
293 var f = items[i].getAsFile();
294 if (f != null) {
295 files.push(f);
296 totalSize += f.size;
297 }
298 }
299 if (files.length > maxFiles || totalSize > maxTotalSize ||
300 (!multiple && files.length > 0)) break;
301 }
302 } else {
303 if (fileList != null) {
304 for (var j = 0; j < fileList.length; j++) {
305 var file = fileList.item(j);
306 if (file.type || file.size > 0) {
307 files.push(file);
308 totalSize += file.size;
309 }
310 if (files.length > maxFiles || totalSize > maxTotalSize ||
311 (!multiple && files.length > 0)) break;
312 }
313 }
314 }
315
316 var defer = $q.defer();
317 $q.all(promises).then(function () {
318 if (!multiple && !includeDir && files.length) {
319 var i = 0;
320 while (files[i] && files[i].type === 'directory') i++;
321 defer.resolve([files[i]]);
322 } else {
323 defer.resolve(files);
324 }
325 }, function (e) {
326 defer.reject(e);
327 });
328
329 return defer.promise;
330 }
331 }
332
333 function dropAvailable() {
334 var div = document.createElement('div');
335 return ('draggable' in div) && ('ondrop' in div) && !/Edge\/12./i.test(navigator.userAgent);
336 }
337
338 })();
+0
-288
src/exif.js less more
0 // customized version of https://github.com/exif-js/exif-js
1 ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) {
2 var upload = UploadResize;
3
4 upload.isExifSupported = function () {
5 return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported();
6 };
7
8 function applyTransform(ctx, orientation, width, height) {
9 switch (orientation) {
10 case 2:
11 return ctx.transform(-1, 0, 0, 1, width, 0);
12 case 3:
13 return ctx.transform(-1, 0, 0, -1, width, height);
14 case 4:
15 return ctx.transform(1, 0, 0, -1, 0, height);
16 case 5:
17 return ctx.transform(0, 1, 1, 0, 0, 0);
18 case 6:
19 return ctx.transform(0, 1, -1, 0, height, 0);
20 case 7:
21 return ctx.transform(0, -1, -1, 0, height, width);
22 case 8:
23 return ctx.transform(0, -1, 1, 0, 0, width);
24 }
25 }
26
27 upload.readOrientation = function (file) {
28 var defer = $q.defer();
29 var reader = new FileReader();
30 var slicedFile = file.slice ? file.slice(0, 64 * 1024) : file;
31 reader.readAsArrayBuffer(slicedFile);
32 reader.onerror = function (e) {
33 return defer.reject(e);
34 };
35 reader.onload = function (e) {
36 var result = {orientation: 1};
37 var view = new DataView(this.result);
38 if (view.getUint16(0, false) !== 0xFFD8) return defer.resolve(result);
39
40 var length = view.byteLength,
41 offset = 2;
42 while (offset < length) {
43 var marker = view.getUint16(offset, false);
44 offset += 2;
45 if (marker === 0xFFE1) {
46 if (view.getUint32(offset += 2, false) !== 0x45786966) return defer.resolve(result);
47
48 var little = view.getUint16(offset += 6, false) === 0x4949;
49 offset += view.getUint32(offset + 4, little);
50 var tags = view.getUint16(offset, little);
51 offset += 2;
52 for (var i = 0; i < tags; i++)
53 if (view.getUint16(offset + (i * 12), little) === 0x0112) {
54 var orientation = view.getUint16(offset + (i * 12) + 8, little);
55 if (orientation >= 2 && orientation <= 8) {
56 view.setUint16(offset + (i * 12) + 8, 1, little);
57 result.fixedArrayBuffer = e.target.result;
58 }
59 result.orientation = orientation;
60 return defer.resolve(result);
61 }
62 } else if ((marker & 0xFF00) !== 0xFF00) break;
63 else offset += view.getUint16(offset, false);
64 }
65 return defer.resolve(result);
66 };
67 return defer.promise;
68 };
69
70 function arrayBufferToBase64(buffer) {
71 var binary = '';
72 var bytes = new Uint8Array(buffer);
73 var len = bytes.byteLength;
74 for (var i = 0; i < len; i++) {
75 binary += String.fromCharCode(bytes[i]);
76 }
77 return window.btoa(binary);
78 }
79
80 upload.applyExifRotation = function (file) {
81 if (file.type.indexOf('image/jpeg') !== 0) {
82 return upload.emptyPromise(file);
83 }
84
85 var deferred = $q.defer();
86 upload.readOrientation(file).then(function (result) {
87 if (result.orientation < 2 || result.orientation > 8) {
88 return deferred.resolve(file);
89 }
90 upload.dataUrl(file, true).then(function (url) {
91 var canvas = document.createElement('canvas');
92 var img = document.createElement('img');
93
94 img.onload = function () {
95 try {
96 canvas.width = result.orientation > 4 ? img.height : img.width;
97 canvas.height = result.orientation > 4 ? img.width : img.height;
98 var ctx = canvas.getContext('2d');
99 applyTransform(ctx, result.orientation, img.width, img.height);
100 ctx.drawImage(img, 0, 0);
101 var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 0.934);
102 dataUrl = upload.restoreExif(arrayBufferToBase64(result.fixedArrayBuffer), dataUrl);
103 var blob = upload.dataUrltoBlob(dataUrl, file.name);
104 deferred.resolve(blob);
105 } catch (e) {
106 return deferred.reject(e);
107 }
108 };
109 img.onerror = function () {
110 deferred.reject();
111 };
112 img.src = url;
113 }, function (e) {
114 deferred.reject(e);
115 });
116 }, function (e) {
117 deferred.reject(e);
118 });
119 return deferred.promise;
120 };
121
122 upload.restoreExif = function (orig, resized) {
123 var ExifRestorer = {};
124
125 ExifRestorer.KEY_STR = 'ABCDEFGHIJKLMNOP' +
126 'QRSTUVWXYZabcdef' +
127 'ghijklmnopqrstuv' +
128 'wxyz0123456789+/' +
129 '=';
130
131 ExifRestorer.encode64 = function (input) {
132 var output = '',
133 chr1, chr2, chr3 = '',
134 enc1, enc2, enc3, enc4 = '',
135 i = 0;
136
137 do {
138 chr1 = input[i++];
139 chr2 = input[i++];
140 chr3 = input[i++];
141
142 enc1 = chr1 >> 2;
143 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
144 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
145 enc4 = chr3 & 63;
146
147 if (isNaN(chr2)) {
148 enc3 = enc4 = 64;
149 } else if (isNaN(chr3)) {
150 enc4 = 64;
151 }
152
153 output = output +
154 this.KEY_STR.charAt(enc1) +
155 this.KEY_STR.charAt(enc2) +
156 this.KEY_STR.charAt(enc3) +
157 this.KEY_STR.charAt(enc4);
158 chr1 = chr2 = chr3 = '';
159 enc1 = enc2 = enc3 = enc4 = '';
160 } while (i < input.length);
161
162 return output;
163 };
164
165 ExifRestorer.restore = function (origFileBase64, resizedFileBase64) {
166 if (origFileBase64.match('data:image/jpeg;base64,')) {
167 origFileBase64 = origFileBase64.replace('data:image/jpeg;base64,', '');
168 }
169
170 var rawImage = this.decode64(origFileBase64);
171 var segments = this.slice2Segments(rawImage);
172
173 var image = this.exifManipulation(resizedFileBase64, segments);
174
175 return 'data:image/jpeg;base64,' + this.encode64(image);
176 };
177
178
179 ExifRestorer.exifManipulation = function (resizedFileBase64, segments) {
180 var exifArray = this.getExifArray(segments),
181 newImageArray = this.insertExif(resizedFileBase64, exifArray);
182 return new Uint8Array(newImageArray);
183 };
184
185
186 ExifRestorer.getExifArray = function (segments) {
187 var seg;
188 for (var x = 0; x < segments.length; x++) {
189 seg = segments[x];
190 if (seg[0] === 255 & seg[1] === 225) //(ff e1)
191 {
192 return seg;
193 }
194 }
195 return [];
196 };
197
198
199 ExifRestorer.insertExif = function (resizedFileBase64, exifArray) {
200 var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''),
201 buf = this.decode64(imageData),
202 separatePoint = buf.indexOf(255, 3),
203 mae = buf.slice(0, separatePoint),
204 ato = buf.slice(separatePoint),
205 array = mae;
206
207 array = array.concat(exifArray);
208 array = array.concat(ato);
209 return array;
210 };
211
212
213 ExifRestorer.slice2Segments = function (rawImageArray) {
214 var head = 0,
215 segments = [];
216
217 while (1) {
218 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {
219 break;
220 }
221 if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {
222 head += 2;
223 }
224 else {
225 var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],
226 endPoint = head + length + 2,
227 seg = rawImageArray.slice(head, endPoint);
228 segments.push(seg);
229 head = endPoint;
230 }
231 if (head > rawImageArray.length) {
232 break;
233 }
234 }
235
236 return segments;
237 };
238
239
240 ExifRestorer.decode64 = function (input) {
241 var chr1, chr2, chr3 = '',
242 enc1, enc2, enc3, enc4 = '',
243 i = 0,
244 buf = [];
245
246 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
247 var base64test = /[^A-Za-z0-9\+\/\=]/g;
248 if (base64test.exec(input)) {
249 console.log('There were invalid base64 characters in the input text.\n' +
250 'Valid base64 characters are A-Z, a-z, 0-9, ' + ', ' / ',and "="\n' +
251 'Expect errors in decoding.');
252 }
253 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
254
255 do {
256 enc1 = this.KEY_STR.indexOf(input.charAt(i++));
257 enc2 = this.KEY_STR.indexOf(input.charAt(i++));
258 enc3 = this.KEY_STR.indexOf(input.charAt(i++));
259 enc4 = this.KEY_STR.indexOf(input.charAt(i++));
260
261 chr1 = (enc1 << 2) | (enc2 >> 4);
262 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
263 chr3 = ((enc3 & 3) << 6) | enc4;
264
265 buf.push(chr1);
266
267 if (enc3 !== 64) {
268 buf.push(chr2);
269 }
270 if (enc4 !== 64) {
271 buf.push(chr3);
272 }
273
274 chr1 = chr2 = chr3 = '';
275 enc1 = enc2 = enc3 = enc4 = '';
276
277 } while (i < input.length);
278
279 return buf;
280 };
281
282 return ExifRestorer.restore(orig, resized); //<= EXIF
283 };
284
285 return upload;
286 }]);
287
+0
-287
src/model.js less more
0 ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {
1 var upload = UploadExif;
2 upload.getAttrWithDefaults = function (attr, name) {
3 if (attr[name] != null) return attr[name];
4 var def = upload.defaults[name];
5 return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));
6 };
7
8 upload.attrGetter = function (name, attr, scope, params) {
9 var attrVal = this.getAttrWithDefaults(attr, name);
10 if (scope) {
11 try {
12 if (params) {
13 return $parse(attrVal)(scope, params);
14 } else {
15 return $parse(attrVal)(scope);
16 }
17 } catch (e) {
18 // hangle string value without single qoute
19 if (name.search(/min|max|pattern/i)) {
20 return attrVal;
21 } else {
22 throw e;
23 }
24 }
25 } else {
26 return attrVal;
27 }
28 };
29
30 upload.shouldUpdateOn = function (type, attr, scope) {
31 var modelOptions = upload.attrGetter('ngModelOptions', attr, scope);
32 if (modelOptions && modelOptions.updateOn) {
33 return modelOptions.updateOn.split(' ').indexOf(type) > -1;
34 }
35 return true;
36 };
37
38 upload.emptyPromise = function () {
39 var d = $q.defer();
40 var args = arguments;
41 $timeout(function () {
42 d.resolve.apply(d, args);
43 });
44 return d.promise;
45 };
46
47 upload.rejectPromise = function () {
48 var d = $q.defer();
49 var args = arguments;
50 $timeout(function () {
51 d.reject.apply(d, args);
52 });
53 return d.promise;
54 };
55
56 upload.happyPromise = function (promise, data) {
57 var d = $q.defer();
58 promise.then(function (result) {
59 d.resolve(result);
60 }, function (error) {
61 $timeout(function () {
62 throw error;
63 });
64 d.resolve(data);
65 });
66 return d.promise;
67 };
68
69 function applyExifRotations(files, attr, scope) {
70 var promises = [upload.emptyPromise()];
71 angular.forEach(files, function (f, i) {
72 if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {
73 promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {
74 files.splice(i, 1, fixedFile);
75 }));
76 }
77 });
78 return $q.all(promises);
79 }
80
81 function resize(files, attr, scope) {
82 var resizeVal = upload.attrGetter('ngfResize', attr, scope);
83 if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
84 if (resizeVal instanceof Function) {
85 var defer = $q.defer();
86 resizeVal(files).then(function (p) {
87 resizeWithParams(p, files, attr, scope).then(function (r) {
88 defer.resolve(r);
89 }, function (e) {
90 defer.reject(e);
91 });
92 }, function (e) {
93 defer.reject(e);
94 });
95 } else {
96 return resizeWithParams(resizeVal, files, attr, scope);
97 }
98 }
99
100 function resizeWithParams(param, files, attr, scope) {
101 var promises = [upload.emptyPromise()];
102
103 function handleFile(f, i) {
104 if (f.type.indexOf('image') === 0) {
105 if (param.pattern && !upload.validatePattern(f, param.pattern)) return;
106 var promise = upload.resize(f, param.width, param.height, param.quality,
107 param.type, param.ratio, param.centerCrop, function (width, height) {
108 return upload.attrGetter('ngfResizeIf', attr, scope,
109 {$width: width, $height: height, $file: f});
110 }, param.restoreExif !== false);
111 promises.push(promise);
112 promise.then(function (resizedFile) {
113 files.splice(i, 1, resizedFile);
114 }, function (e) {
115 f.$error = 'resize';
116 f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
117 });
118 }
119 }
120
121 for (var i = 0; i < files.length; i++) {
122 handleFile(files[i], i);
123 }
124 return $q.all(promises);
125 }
126
127 upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
128 function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {
129 attr.$$ngfPrevValidFiles = files;
130 attr.$$ngfPrevInvalidFiles = invalidFiles;
131 var file = files && files.length ? files[0] : null;
132 var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;
133
134 if (ngModel) {
135 upload.applyModelValidation(ngModel, files);
136 ngModel.$setViewValue(isSingleModel ? file : files);
137 }
138
139 if (fileChange) {
140 $parse(fileChange)(scope, {
141 $files: files,
142 $file: file,
143 $newFiles: newFiles,
144 $duplicateFiles: dupFiles,
145 $invalidFiles: invalidFiles,
146 $invalidFile: invalidFile,
147 $event: evt
148 });
149 }
150
151 var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
152 if (invalidModel) {
153 $timeout(function () {
154 $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);
155 });
156 }
157 $timeout(function () {
158 // scope apply changes
159 });
160 }
161
162 var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,
163 invalids = [], valids = [];
164
165 function removeDuplicates() {
166 function equals(f1, f2) {
167 return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&
168 f1.type === f2.type;
169 }
170
171 function isInPrevFiles(f) {
172 var j;
173 for (j = 0; j < prevValidFiles.length; j++) {
174 if (equals(f, prevValidFiles[j])) {
175 return true;
176 }
177 }
178 for (j = 0; j < prevInvalidFiles.length; j++) {
179 if (equals(f, prevInvalidFiles[j])) {
180 return true;
181 }
182 }
183 return false;
184 }
185
186 if (files) {
187 allNewFiles = [];
188 dupFiles = [];
189 for (var i = 0; i < files.length; i++) {
190 if (isInPrevFiles(files[i])) {
191 dupFiles.push(files[i]);
192 } else {
193 allNewFiles.push(files[i]);
194 }
195 }
196 }
197 }
198
199 function toArray(v) {
200 return angular.isArray(v) ? v : [v];
201 }
202
203 function separateInvalids() {
204 valids = [];
205 invalids = [];
206 angular.forEach(allNewFiles, function (file) {
207 if (file.$error) {
208 invalids.push(file);
209 } else {
210 valids.push(file);
211 }
212 });
213 }
214
215 function resizeAndUpdate() {
216 function updateModel() {
217 $timeout(function () {
218 update(keep ? prevValidFiles.concat(valids) : valids,
219 keep ? prevInvalidFiles.concat(invalids) : invalids,
220 files, dupFiles, isSingleModel);
221 }, options && options.debounce ? options.debounce.change || options.debounce : 0);
222 }
223
224 resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () {
225 if (validateAfterResize) {
226 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
227 separateInvalids();
228 updateModel();
229 });
230 } else {
231 updateModel();
232 }
233 }, function (e) {
234 throw 'Could not resize files ' + e;
235 });
236 }
237
238 prevValidFiles = attr.$$ngfPrevValidFiles || [];
239 prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];
240 if (ngModel && ngModel.$modelValue) {
241 prevValidFiles = toArray(ngModel.$modelValue);
242 }
243
244 var keep = upload.attrGetter('ngfKeep', attr, scope);
245 allNewFiles = (files || []).slice(0);
246 if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {
247 removeDuplicates(attr, scope);
248 }
249
250 var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);
251
252 if (keep && !allNewFiles.length) return;
253
254 upload.attrGetter('ngfBeforeModelChange', attr, scope, {
255 $files: files,
256 $file: files && files.length ? files[0] : null,
257 $newFiles: allNewFiles,
258 $duplicateFiles: dupFiles,
259 $event: evt
260 });
261
262 var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
263
264 var options = upload.attrGetter('ngModelOptions', attr, scope);
265 upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () {
266 if (noDelay) {
267 update(allNewFiles, [], files, dupFiles, isSingleModel);
268 } else {
269 if ((!options || !options.allowInvalid) && !validateAfterResize) {
270 separateInvalids();
271 } else {
272 valids = allNewFiles;
273 }
274 if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {
275 applyExifRotations(valids, attr, scope).then(function () {
276 resizeAndUpdate();
277 });
278 } else {
279 resizeAndUpdate();
280 }
281 }
282 });
283 };
284
285 return upload;
286 }]);
+0
-135
src/resize.js less more
0 ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) {
1 var upload = UploadValidate;
2
3 /**
4 * Conserve aspect ratio of the original region. Useful when shrinking/enlarging
5 * images to fit into a certain area.
6 * Source: http://stackoverflow.com/a/14731922
7 *
8 * @param {Number} srcWidth Source area width
9 * @param {Number} srcHeight Source area height
10 * @param {Number} maxWidth Nestable area maximum available width
11 * @param {Number} maxHeight Nestable area maximum available height
12 * @return {Object} { width, height }
13 */
14 var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight, centerCrop) {
15 var ratio = centerCrop ? Math.max(maxWidth / srcWidth, maxHeight / srcHeight) :
16 Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
17 return {
18 width: srcWidth * ratio, height: srcHeight * ratio,
19 marginX: srcWidth * ratio - maxWidth, marginY: srcHeight * ratio - maxHeight
20 };
21 };
22
23 // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89
24 var resize = function (imagen, width, height, quality, type, ratio, centerCrop, resizeIf) {
25 var deferred = $q.defer();
26 var canvasElement = document.createElement('canvas');
27 var imageElement = document.createElement('img');
28
29 imageElement.onload = function () {
30 if (resizeIf != null && resizeIf(imageElement.width, imageElement.height) === false) {
31 deferred.reject('resizeIf');
32 return;
33 }
34 try {
35 if (ratio) {
36 var ratioFloat = upload.ratioToFloat(ratio);
37 var imgRatio = imageElement.width / imageElement.height;
38 if (imgRatio < ratioFloat) {
39 width = imageElement.width;
40 height = width / ratioFloat;
41 } else {
42 height = imageElement.height;
43 width = height * ratioFloat;
44 }
45 }
46 if (!width) {
47 width = imageElement.width;
48 }
49 if (!height) {
50 height = imageElement.height;
51 }
52 var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height, centerCrop);
53 canvasElement.width = Math.min(dimensions.width, width);
54 canvasElement.height = Math.min(dimensions.height, height);
55 var context = canvasElement.getContext('2d');
56 context.drawImage(imageElement,
57 Math.min(0, -dimensions.marginX / 2), Math.min(0, -dimensions.marginY / 2),
58 dimensions.width, dimensions.height);
59 deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 0.934));
60 } catch (e) {
61 deferred.reject(e);
62 }
63 };
64 imageElement.onerror = function () {
65 deferred.reject();
66 };
67 imageElement.src = imagen;
68 return deferred.promise;
69 };
70
71 upload.dataUrltoBlob = function (dataurl, name, origSize) {
72 var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
73 bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
74 while (n--) {
75 u8arr[n] = bstr.charCodeAt(n);
76 }
77 var blob = new window.Blob([u8arr], {type: mime});
78 blob.name = name;
79 blob.$ngfOrigSize = origSize;
80 return blob;
81 };
82
83 upload.isResizeSupported = function () {
84 var elem = document.createElement('canvas');
85 return window.atob && elem.getContext && elem.getContext('2d') && window.Blob;
86 };
87
88 if (upload.isResizeSupported()) {
89 // add name getter to the blob constructor prototype
90 Object.defineProperty(window.Blob.prototype, 'name', {
91 get: function () {
92 return this.$ngfName;
93 },
94 set: function (v) {
95 this.$ngfName = v;
96 },
97 configurable: true
98 });
99 }
100
101 upload.resize = function (file, width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif) {
102 if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file);
103
104 var deferred = $q.defer();
105 upload.dataUrl(file, true).then(function (url) {
106 resize(url, width, height, quality, type || file.type, ratio, centerCrop, resizeIf)
107 .then(function (dataUrl) {
108 if (file.type === 'image/jpeg' && restoreExif) {
109 try {
110 dataUrl = upload.restoreExif(url, dataUrl);
111 } catch (e) {
112 setTimeout(function () {throw e;}, 1);
113 }
114 }
115 try {
116 var blob = upload.dataUrltoBlob(dataUrl, file.name, file.size);
117 deferred.resolve(blob);
118 } catch (e) {
119 deferred.reject(e);
120 }
121 }, function (r) {
122 if (r === 'resizeIf') {
123 deferred.resolve(file);
124 }
125 deferred.reject(r);
126 });
127 }, function (e) {
128 deferred.reject(e);
129 });
130 return deferred.promise;
131 };
132
133 return upload;
134 }]);
+0
-233
src/select.js less more
0 ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) {
1 var generatedElems = [];
2
3 function isDelayedClickSupported(ua) {
4 // fix for android native browser < 4.4 and safari windows
5 var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/);
6 if (m && m.length > 2) {
7 var v = Upload.defaults.androidFixMinorVersion || 4;
8 return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v);
9 }
10
11 // safari on windows
12 return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua);
13 }
14
15 function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) {
16 /** @namespace attr.ngfSelect */
17 /** @namespace attr.ngfChange */
18 /** @namespace attr.ngModel */
19 /** @namespace attr.ngModelOptions */
20 /** @namespace attr.ngfMultiple */
21 /** @namespace attr.ngfCapture */
22 /** @namespace attr.ngfValidate */
23 /** @namespace attr.ngfKeep */
24 var attrGetter = function (name, scope) {
25 return upload.attrGetter(name, attr, scope);
26 };
27
28 function isInputTypeFile() {
29 return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file';
30 }
31
32 function fileChangeAttr() {
33 return attrGetter('ngfChange') || attrGetter('ngfSelect');
34 }
35
36 function changeFn(evt) {
37 if (upload.shouldUpdateOn('change', attr, scope)) {
38 var fileList = evt.__files_ || (evt.target && evt.target.files), files = [];
39 for (var i = 0; i < fileList.length; i++) {
40 files.push(fileList[i]);
41 }
42 upload.updateModel(ngModel, attr, scope, fileChangeAttr(),
43 files.length ? files : null, evt);
44 }
45 }
46
47 upload.registerModelChangeValidator(ngModel, attr, scope);
48
49 var unwatches = [];
50 unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () {
51 fileElem.attr('multiple', attrGetter('ngfMultiple', scope));
52 }));
53 unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () {
54 fileElem.attr('capture', attrGetter('ngfCapture', scope));
55 }));
56 unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () {
57 fileElem.attr('accept', attrGetter('ngfAccept', scope));
58 }));
59 attr.$observe('accept', function () {
60 fileElem.attr('accept', attrGetter('accept'));
61 });
62 unwatches.push(function () {
63 if (attr.$$observers) delete attr.$$observers.accept;
64 });
65 function bindAttrToFileInput(fileElem) {
66 if (elem !== fileElem) {
67 for (var i = 0; i < elem[0].attributes.length; i++) {
68 var attribute = elem[0].attributes[i];
69 if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') {
70 if (attribute.value == null || attribute.value === '') {
71 if (attribute.name === 'required') attribute.value = 'required';
72 if (attribute.name === 'multiple') attribute.value = 'multiple';
73 }
74 fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value);
75 }
76 }
77 }
78 }
79
80 function createFileInput() {
81 if (isInputTypeFile()) {
82 return elem;
83 }
84
85 var fileElem = angular.element('<input type="file">');
86
87 bindAttrToFileInput(fileElem);
88
89 var label = angular.element('<label>upload</label>');
90 label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden')
91 .css('width', '0px').css('height', '0px').css('border', 'none')
92 .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1');
93 generatedElems.push({el: elem, ref: label});
94
95 document.body.appendChild(label.append(fileElem)[0]);
96
97 return fileElem;
98 }
99
100 var initialTouchStartY = 0;
101
102 function clickHandler(evt) {
103 if (elem.attr('disabled')) return false;
104 if (attrGetter('ngfSelectDisabled', scope)) return;
105
106 var r = handleTouch(evt);
107 if (r != null) return r;
108
109 resetModel(evt);
110
111 // fix for md when the element is removed from the DOM and added back #460
112 try {
113 if (!isInputTypeFile() && !document.body.contains(fileElem[0])) {
114 generatedElems.push({el: elem, ref: fileElem.parent()});
115 document.body.appendChild(fileElem.parent()[0]);
116 fileElem.bind('change', changeFn);
117 }
118 } catch(e){/*ignore*/}
119
120 if (isDelayedClickSupported(navigator.userAgent)) {
121 setTimeout(function () {
122 fileElem[0].click();
123 }, 0);
124 } else {
125 fileElem[0].click();
126 }
127
128 return false;
129 }
130
131 function handleTouch(evt) {
132 var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches);
133 if (evt.type === 'touchstart') {
134 initialTouchStartY = touches ? touches[0].clientY : 0;
135 return true; // don't block event default
136 } else {
137 evt.stopPropagation();
138 evt.preventDefault();
139
140 // prevent scroll from triggering event
141 if (evt.type === 'touchend') {
142 var currentLocation = touches ? touches[0].clientY : 0;
143 if (Math.abs(currentLocation - initialTouchStartY) > 20) return false;
144 }
145 }
146 }
147
148 var fileElem = elem;
149
150 function resetModel(evt) {
151 if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) {
152 fileElem.val(null);
153 upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true);
154 }
155 }
156
157 if (!isInputTypeFile()) {
158 fileElem = createFileInput();
159 }
160 fileElem.bind('change', changeFn);
161
162 if (!isInputTypeFile()) {
163 elem.bind('click touchstart touchend', clickHandler);
164 } else {
165 elem.bind('click', resetModel);
166 }
167
168 function ie10SameFileSelectFix(evt) {
169 if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) {
170 if (!fileElem[0].parentNode) {
171 fileElem = null;
172 return;
173 }
174 evt.preventDefault();
175 evt.stopPropagation();
176 fileElem.unbind('click');
177 var clone = fileElem.clone();
178 fileElem.replaceWith(clone);
179 fileElem = clone;
180 fileElem.attr('__ngf_ie10_Fix_', 'true');
181 fileElem.bind('change', changeFn);
182 fileElem.bind('click', ie10SameFileSelectFix);
183 fileElem[0].click();
184 return false;
185 } else {
186 fileElem.removeAttr('__ngf_ie10_Fix_');
187 }
188 }
189
190 if (navigator.appVersion.indexOf('MSIE 10') !== -1) {
191 fileElem.bind('click', ie10SameFileSelectFix);
192 }
193
194 if (ngModel) ngModel.$formatters.push(function (val) {
195 if (val == null || val.length === 0) {
196 if (fileElem.val()) {
197 fileElem.val(null);
198 }
199 }
200 return val;
201 });
202
203 scope.$on('$destroy', function () {
204 if (!isInputTypeFile()) fileElem.parent().remove();
205 angular.forEach(unwatches, function (unwatch) {
206 unwatch();
207 });
208 });
209
210 $timeout(function () {
211 for (var i = 0; i < generatedElems.length; i++) {
212 var g = generatedElems[i];
213 if (!document.body.contains(g.el[0])) {
214 generatedElems.splice(i, 1);
215 g.ref.remove();
216 }
217 }
218 });
219
220 if (window.FileAPI && window.FileAPI.ngfFixIE) {
221 window.FileAPI.ngfFixIE(elem, fileElem, changeFn);
222 }
223 }
224
225 return {
226 restrict: 'AEC',
227 require: '?ngModel',
228 link: function (scope, elem, attr, ngModel) {
229 linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload);
230 }
231 };
232 }]);
+0
-134
src/shim-elem.js less more
0 (function () {
1 /** @namespace FileAPI.forceLoad */
2 /** @namespace window.FileAPI.jsUrl */
3 /** @namespace window.FileAPI.jsPath */
4
5 function isInputTypeFile(elem) {
6 return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
7 }
8
9 function hasFlash() {
10 try {
11 var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
12 if (fo) return true;
13 } catch (e) {
14 if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
15 }
16 return false;
17 }
18
19 function getOffset(obj) {
20 var left = 0, top = 0;
21
22 if (window.jQuery) {
23 return jQuery(obj).offset();
24 }
25
26 if (obj.offsetParent) {
27 do {
28 left += (obj.offsetLeft - obj.scrollLeft);
29 top += (obj.offsetTop - obj.scrollTop);
30 obj = obj.offsetParent;
31 } while (obj);
32 }
33 return {
34 left: left,
35 top: top
36 };
37 }
38
39 if (FileAPI.shouldLoad) {
40 FileAPI.hasFlash = hasFlash();
41
42 //load FileAPI
43 if (FileAPI.forceLoad) {
44 FileAPI.html5 = false;
45 }
46
47 if (!FileAPI.upload) {
48 var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
49 if (window.FileAPI.jsUrl) {
50 jsUrl = window.FileAPI.jsUrl;
51 } else if (window.FileAPI.jsPath) {
52 basePath = window.FileAPI.jsPath;
53 } else {
54 for (i = 0; i < allScripts.length; i++) {
55 src = allScripts[i].src;
56 index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
57 if (index > -1) {
58 basePath = src.substring(0, index + 1);
59 break;
60 }
61 }
62 }
63
64 if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
65 script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
66 document.getElementsByTagName('head')[0].appendChild(script);
67 }
68
69 FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
70 if (!hasFlash()) {
71 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
72 }
73 var fixInputStyle = function () {
74 var label = fileElem.parent();
75 if (elem.attr('disabled')) {
76 if (label) label.removeClass('js-fileapi-wrapper');
77 } else {
78 if (!fileElem.attr('__ngf_flash_')) {
79 fileElem.unbind('change');
80 fileElem.unbind('click');
81 fileElem.bind('change', function (evt) {
82 fileApiChangeFn.apply(this, [evt]);
83 changeFn.apply(this, [evt]);
84 });
85 fileElem.attr('__ngf_flash_', 'true');
86 }
87 label.addClass('js-fileapi-wrapper');
88 if (!isInputTypeFile(elem)) {
89 label.css('position', 'absolute')
90 .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
91 .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
92 .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
93 .css('overflow', 'hidden').css('z-index', '900000')
94 .css('visibility', 'visible');
95 fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
96 .css('position', 'absolute').css('top', '0px').css('left', '0px');
97 }
98 }
99 };
100
101 elem.bind('mouseenter', fixInputStyle);
102
103 var fileApiChangeFn = function (evt) {
104 var files = FileAPI.getFiles(evt);
105 //just a double check for #233
106 for (var i = 0; i < files.length; i++) {
107 if (files[i].size === undefined) files[i].size = 0;
108 if (files[i].name === undefined) files[i].name = 'file';
109 if (files[i].type === undefined) files[i].type = 'undefined';
110 }
111 if (!evt.target) {
112 evt.target = {};
113 }
114 evt.target.files = files;
115 // if evt.target.files is not writable use helper field
116 if (evt.target.files !== files) {
117 evt.__files_ = files;
118 }
119 (evt.__files_ || evt.target.files).item = function (i) {
120 return (evt.__files_ || evt.target.files)[i] || null;
121 };
122 };
123 };
124
125 FileAPI.disableFileInput = function (elem, disable) {
126 if (disable) {
127 elem.removeClass('js-fileapi-wrapper');
128 } else {
129 elem.addClass('js-fileapi-wrapper');
130 }
131 };
132 }
133 })();
+0
-55
src/shim-filereader.js less more
0 if (!window.FileReader) {
1 window.FileReader = function () {
2 var _this = this, loadStarted = false;
3 this.listeners = {};
4 this.addEventListener = function (type, fn) {
5 _this.listeners[type] = _this.listeners[type] || [];
6 _this.listeners[type].push(fn);
7 };
8 this.removeEventListener = function (type, fn) {
9 if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
10 };
11 this.dispatchEvent = function (evt) {
12 var list = _this.listeners[evt.type];
13 if (list) {
14 for (var i = 0; i < list.length; i++) {
15 list[i].call(_this, evt);
16 }
17 }
18 };
19 this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
20
21 var constructEvent = function (type, evt) {
22 var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
23 if (evt.result != null) e.target.result = evt.result;
24 return e;
25 };
26 var listener = function (evt) {
27 if (!loadStarted) {
28 loadStarted = true;
29 if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
30 }
31 var e;
32 if (evt.type === 'load') {
33 if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
34 e = constructEvent('load', evt);
35 if (_this.onload) _this.onload(e);
36 _this.dispatchEvent(e);
37 } else if (evt.type === 'progress') {
38 e = constructEvent('progress', evt);
39 if (_this.onprogress) _this.onprogress(e);
40 _this.dispatchEvent(e);
41 } else {
42 e = constructEvent('error', evt);
43 if (_this.onerror) _this.onerror(e);
44 _this.dispatchEvent(e);
45 }
46 };
47 this.readAsDataURL = function (file) {
48 FileAPI.readAsDataURL(file, listener);
49 };
50 this.readAsText = function (file) {
51 FileAPI.readAsText(file, listener);
52 };
53 };
54 }
+0
-230
src/shim-upload.js less more
0 /**!
1 * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
2 * progress, resize, thumbnail, preview, validation and CORS
3 * FileAPI Flash shim for old browsers not supporting FormData
4 * @author Danial <danial.farid@gmail.com>
5 * @version <%= pkg.version %>
6 */
7
8 (function () {
9 /** @namespace FileAPI.noContentTimeout */
10
11 function patchXHR(fnName, newFn) {
12 window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
13 }
14
15 function redefineProp(xhr, prop, fn) {
16 try {
17 Object.defineProperty(xhr, prop, {get: fn});
18 } catch (e) {/*ignore*/
19 }
20 }
21
22 if (!window.FileAPI) {
23 window.FileAPI = {};
24 }
25
26 if (!window.XMLHttpRequest) {
27 throw 'AJAX is not supported. XMLHttpRequest is not defined.';
28 }
29
30 FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
31 if (FileAPI.shouldLoad) {
32 var initializeUploadListener = function (xhr) {
33 if (!xhr.__listeners) {
34 if (!xhr.upload) xhr.upload = {};
35 xhr.__listeners = [];
36 var origAddEventListener = xhr.upload.addEventListener;
37 xhr.upload.addEventListener = function (t, fn) {
38 xhr.__listeners[t] = fn;
39 if (origAddEventListener) origAddEventListener.apply(this, arguments);
40 };
41 }
42 };
43
44 patchXHR('open', function (orig) {
45 return function (m, url, b) {
46 initializeUploadListener(this);
47 this.__url = url;
48 try {
49 orig.apply(this, [m, url, b]);
50 } catch (e) {
51 if (e.message.indexOf('Access is denied') > -1) {
52 this.__origError = e;
53 orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
54 }
55 }
56 };
57 });
58
59 patchXHR('getResponseHeader', function (orig) {
60 return function (h) {
61 return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
62 };
63 });
64
65 patchXHR('getAllResponseHeaders', function (orig) {
66 return function () {
67 return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
68 };
69 });
70
71 patchXHR('abort', function (orig) {
72 return function () {
73 return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
74 };
75 });
76
77 patchXHR('setRequestHeader', function (orig) {
78 return function (header, value) {
79 if (header === '__setXHR_') {
80 initializeUploadListener(this);
81 var val = value(this);
82 // fix for angular < 1.2.0
83 if (val instanceof Function) {
84 val(this);
85 }
86 } else {
87 this.__requestHeaders = this.__requestHeaders || {};
88 this.__requestHeaders[header] = value;
89 orig.apply(this, arguments);
90 }
91 };
92 });
93
94 patchXHR('send', function (orig) {
95 return function () {
96 var xhr = this;
97 if (arguments[0] && arguments[0].__isFileAPIShim) {
98 var formData = arguments[0];
99 var config = {
100 url: xhr.__url,
101 jsonp: false, //removes the callback form param
102 cache: true, //removes the ?fileapiXXX in the url
103 complete: function (err, fileApiXHR) {
104 if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
105 // this error seems to be fine the file is being uploaded properly.
106 err = null;
107 }
108 xhr.__completed = true;
109 if (!err && xhr.__listeners.load)
110 xhr.__listeners.load({
111 type: 'load',
112 loaded: xhr.__loaded,
113 total: xhr.__total,
114 target: xhr,
115 lengthComputable: true
116 });
117 if (!err && xhr.__listeners.loadend)
118 xhr.__listeners.loadend({
119 type: 'loadend',
120 loaded: xhr.__loaded,
121 total: xhr.__total,
122 target: xhr,
123 lengthComputable: true
124 });
125 if (err === 'abort' && xhr.__listeners.abort)
126 xhr.__listeners.abort({
127 type: 'abort',
128 loaded: xhr.__loaded,
129 total: xhr.__total,
130 target: xhr,
131 lengthComputable: true
132 });
133 if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
134 return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
135 });
136 if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
137 return fileApiXHR.statusText;
138 });
139 redefineProp(xhr, 'readyState', function () {
140 return 4;
141 });
142 if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
143 return fileApiXHR.response;
144 });
145 var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
146 redefineProp(xhr, 'responseText', function () {
147 return resp;
148 });
149 redefineProp(xhr, 'response', function () {
150 return resp;
151 });
152 if (err) redefineProp(xhr, 'err', function () {
153 return err;
154 });
155 xhr.__fileApiXHR = fileApiXHR;
156 if (xhr.onreadystatechange) xhr.onreadystatechange();
157 if (xhr.onload) xhr.onload();
158 },
159 progress: function (e) {
160 e.target = xhr;
161 if (xhr.__listeners.progress) xhr.__listeners.progress(e);
162 xhr.__total = e.total;
163 xhr.__loaded = e.loaded;
164 if (e.total === e.loaded) {
165 // fix flash issue that doesn't call complete if there is no response text from the server
166 var _this = this;
167 setTimeout(function () {
168 if (!xhr.__completed) {
169 xhr.getAllResponseHeaders = function () {
170 };
171 _this.complete(null, {status: 204, statusText: 'No Content'});
172 }
173 }, FileAPI.noContentTimeout || 10000);
174 }
175 },
176 headers: xhr.__requestHeaders
177 };
178 config.data = {};
179 config.files = {};
180 for (var i = 0; i < formData.data.length; i++) {
181 var item = formData.data[i];
182 if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
183 config.files[item.key] = item.val;
184 } else {
185 config.data[item.key] = item.val;
186 }
187 }
188
189 setTimeout(function () {
190 if (!FileAPI.hasFlash) {
191 throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
192 }
193 xhr.__fileApiXHR = FileAPI.upload(config);
194 }, 1);
195 } else {
196 if (this.__origError) {
197 throw this.__origError;
198 }
199 orig.apply(xhr, arguments);
200 }
201 };
202 });
203 window.XMLHttpRequest.__isFileAPIShim = true;
204 window.FormData = FormData = function () {
205 return {
206 append: function (key, val, name) {
207 if (val.__isFileAPIBlobShim) {
208 val = val.data[0];
209 }
210 this.data.push({
211 key: key,
212 val: val,
213 name: name
214 });
215 },
216 data: [],
217 __isFileAPIShim: true
218 };
219 };
220
221 window.Blob = Blob = function (b) {
222 return {
223 data: b,
224 __isFileAPIBlobShim: true
225 };
226 };
227 }
228
229 })();
+0
-391
src/upload.js less more
0 /**!
1 * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
2 * progress, resize, thumbnail, preview, validation and CORS
3 * @author Danial <danial.farid@gmail.com>
4 * @version <%= pkg.version %>
5 */
6
7 if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) {
8 window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) {
9 return function (header, value) {
10 if (header === '__setXHR_') {
11 var val = value(this);
12 // fix for angular < 1.2.0
13 if (val instanceof Function) {
14 val(this);
15 }
16 } else {
17 orig.apply(this, arguments);
18 }
19 };
20 })(window.XMLHttpRequest.prototype.setRequestHeader);
21 }
22
23 var ngFileUpload = angular.module('ngFileUpload', []);
24
25 ngFileUpload.version = '<%= pkg.version %>';
26
27 ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
28 var upload = this;
29 upload.promisesCount = 0;
30
31 this.isResumeSupported = function () {
32 return window.Blob && window.Blob.prototype.slice;
33 };
34
35 var resumeSupported = this.isResumeSupported();
36
37 function sendHttp(config) {
38 config.method = config.method || 'POST';
39 config.headers = config.headers || {};
40
41 var deferred = config._deferred = config._deferred || $q.defer();
42 var promise = deferred.promise;
43
44 function notifyProgress(e) {
45 if (deferred.notify) {
46 deferred.notify(e);
47 }
48 if (promise.progressFunc) {
49 $timeout(function () {
50 promise.progressFunc(e);
51 });
52 }
53 }
54
55 function getNotifyEvent(n) {
56 if (config._start != null && resumeSupported) {
57 return {
58 loaded: n.loaded + config._start,
59 total: (config._file && config._file.size) || n.total,
60 type: n.type, config: config,
61 lengthComputable: true, target: n.target
62 };
63 } else {
64 return n;
65 }
66 }
67
68 if (!config.disableProgress) {
69 config.headers.__setXHR_ = function () {
70 return function (xhr) {
71 if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return;
72 config.__XHR = xhr;
73 if (config.xhrFn) config.xhrFn(xhr);
74 xhr.upload.addEventListener('progress', function (e) {
75 e.config = config;
76 notifyProgress(getNotifyEvent(e));
77 }, false);
78 //fix for firefox not firing upload progress end, also IE8-9
79 xhr.upload.addEventListener('load', function (e) {
80 if (e.lengthComputable) {
81 e.config = config;
82 notifyProgress(getNotifyEvent(e));
83 }
84 }, false);
85 };
86 };
87 }
88
89 function uploadWithAngular() {
90 $http(config).then(function (r) {
91 if (resumeSupported && config._chunkSize && !config._finished && config._file) {
92 notifyProgress({
93 loaded: config._end,
94 total: config._file && config._file.size,
95 config: config, type: 'progress'
96 }
97 );
98 upload.upload(config, true);
99 } else {
100 if (config._finished) delete config._finished;
101 deferred.resolve(r);
102 }
103 }, function (e) {
104 deferred.reject(e);
105 }, function (n) {
106 deferred.notify(n);
107 }
108 );
109 }
110
111 if (!resumeSupported) {
112 uploadWithAngular();
113 } else if (config._chunkSize && config._end && !config._finished) {
114 config._start = config._end;
115 config._end += config._chunkSize;
116 uploadWithAngular();
117 } else if (config.resumeSizeUrl) {
118 $http.get(config.resumeSizeUrl).then(function (resp) {
119 if (config.resumeSizeResponseReader) {
120 config._start = config.resumeSizeResponseReader(resp.data);
121 } else {
122 config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString());
123 }
124 if (config._chunkSize) {
125 config._end = config._start + config._chunkSize;
126 }
127 uploadWithAngular();
128 }, function (e) {
129 throw e;
130 });
131 } else if (config.resumeSize) {
132 config.resumeSize().then(function (size) {
133 config._start = size;
134 uploadWithAngular();
135 }, function (e) {
136 throw e;
137 });
138 } else {
139 if (config._chunkSize) {
140 config._start = 0;
141 config._end = config._start + config._chunkSize;
142 }
143 uploadWithAngular();
144 }
145
146
147 promise.success = function (fn) {
148 promise.then(function (response) {
149 fn(response.data, response.status, response.headers, config);
150 });
151 return promise;
152 };
153
154 promise.error = function (fn) {
155 promise.then(null, function (response) {
156 fn(response.data, response.status, response.headers, config);
157 });
158 return promise;
159 };
160
161 promise.progress = function (fn) {
162 promise.progressFunc = fn;
163 promise.then(null, null, function (n) {
164 fn(n);
165 });
166 return promise;
167 };
168 promise.abort = promise.pause = function () {
169 if (config.__XHR) {
170 $timeout(function () {
171 config.__XHR.abort();
172 });
173 }
174 return promise;
175 };
176 promise.xhr = function (fn) {
177 config.xhrFn = (function (origXhrFn) {
178 return function () {
179 if (origXhrFn) origXhrFn.apply(promise, arguments);
180 fn.apply(promise, arguments);
181 };
182 })(config.xhrFn);
183 return promise;
184 };
185
186 upload.promisesCount++;
187 promise['finally'](function () {
188 upload.promisesCount--;
189 });
190 return promise;
191 }
192
193 this.isUploadInProgress = function () {
194 return upload.promisesCount > 0;
195 };
196
197 this.rename = function (file, name) {
198 file.ngfName = name;
199 return file;
200 };
201
202 this.jsonBlob = function (val) {
203 if (val != null && !angular.isString(val)) {
204 val = JSON.stringify(val);
205 }
206 var blob = new window.Blob([val], {type: 'application/json'});
207 blob._ngfBlob = true;
208 return blob;
209 };
210
211 this.json = function (val) {
212 return angular.toJson(val);
213 };
214
215 function copy(obj) {
216 var clone = {};
217 for (var key in obj) {
218 if (obj.hasOwnProperty(key)) {
219 clone[key] = obj[key];
220 }
221 }
222 return clone;
223 }
224
225 this.isFile = function (file) {
226 return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size));
227 };
228
229 this.upload = function (config, internal) {
230 function toResumeFile(file, formData) {
231 if (file._ngfBlob) return file;
232 config._file = config._file || file;
233 if (config._start != null && resumeSupported) {
234 if (config._end && config._end >= file.size) {
235 config._finished = true;
236 config._end = file.size;
237 }
238 var slice = file.slice(config._start, config._end || file.size);
239 slice.name = file.name;
240 slice.ngfName = file.ngfName;
241 if (config._chunkSize) {
242 formData.append('_chunkSize', config._chunkSize);
243 formData.append('_currentChunkSize', config._end - config._start);
244 formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize));
245 formData.append('_totalSize', config._file.size);
246 }
247 return slice;
248 }
249 return file;
250 }
251
252 function addFieldToFormData(formData, val, key) {
253 if (val !== undefined) {
254 if (angular.isDate(val)) {
255 val = val.toISOString();
256 }
257 if (angular.isString(val)) {
258 formData.append(key, val);
259 } else if (upload.isFile(val)) {
260 var file = toResumeFile(val, formData);
261 var split = key.split(',');
262 if (split[1]) {
263 file.ngfName = split[1].replace(/^\s+|\s+$/g, '');
264 key = split[0];
265 }
266 config._fileKey = config._fileKey || key;
267 formData.append(key, file, file.ngfName || file.name);
268 } else {
269 if (angular.isObject(val)) {
270 if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key;
271
272 val.$$ngfCircularDetection = true;
273 try {
274 for (var k in val) {
275 if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') {
276 var objectKey = config.objectKey == null ? '[i]' : config.objectKey;
277 if (val.length && parseInt(k) > -1) {
278 objectKey = config.arrayKey == null ? objectKey : config.arrayKey;
279 }
280 addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k));
281 }
282 }
283 } finally {
284 delete val.$$ngfCircularDetection;
285 }
286 } else {
287 formData.append(key, val);
288 }
289 }
290 }
291 }
292
293 function digestConfig() {
294 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
295 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
296
297 config.headers = config.headers || {};
298 config.headers['Content-Type'] = undefined;
299 config.transformRequest = config.transformRequest ?
300 (angular.isArray(config.transformRequest) ?
301 config.transformRequest : [config.transformRequest]) : [];
302 config.transformRequest.push(function (data) {
303 var formData = new window.FormData(), key;
304 data = data || config.fields || {};
305 if (config.file) {
306 data.file = config.file;
307 }
308 for (key in data) {
309 if (data.hasOwnProperty(key)) {
310 var val = data[key];
311 if (config.formDataAppender) {
312 config.formDataAppender(formData, key, val);
313 } else {
314 addFieldToFormData(formData, val, key);
315 }
316 }
317 }
318
319 return formData;
320 });
321 }
322
323 if (!internal) config = copy(config);
324 if (!config._isDigested) {
325 config._isDigested = true;
326 digestConfig();
327 }
328
329 return sendHttp(config);
330 };
331
332 this.http = function (config) {
333 config = copy(config);
334 config.transformRequest = config.transformRequest || function (data) {
335 if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) {
336 return data;
337 }
338 return $http.defaults.transformRequest[0].apply(this, arguments);
339 };
340 config._chunkSize = upload.translateScalars(config.resumeChunkSize);
341 config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null;
342
343 return sendHttp(config);
344 };
345
346 this.translateScalars = function (str) {
347 if (angular.isString(str)) {
348 if (str.search(/kb/i) === str.length - 2) {
349 return parseFloat(str.substring(0, str.length - 2) * 1024);
350 } else if (str.search(/mb/i) === str.length - 2) {
351 return parseFloat(str.substring(0, str.length - 2) * 1048576);
352 } else if (str.search(/gb/i) === str.length - 2) {
353 return parseFloat(str.substring(0, str.length - 2) * 1073741824);
354 } else if (str.search(/b/i) === str.length - 1) {
355 return parseFloat(str.substring(0, str.length - 1));
356 } else if (str.search(/s/i) === str.length - 1) {
357 return parseFloat(str.substring(0, str.length - 1));
358 } else if (str.search(/m/i) === str.length - 1) {
359 return parseFloat(str.substring(0, str.length - 1) * 60);
360 } else if (str.search(/h/i) === str.length - 1) {
361 return parseFloat(str.substring(0, str.length - 1) * 3600);
362 }
363 }
364 return str;
365 };
366
367 this.urlToBlob = function(url) {
368 var defer = $q.defer();
369 $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) {
370 var arrayBufferView = new Uint8Array(resp.data);
371 var type = resp.headers('content-type') || 'image/WebP';
372 var blob = new window.Blob([arrayBufferView], {type: type});
373 defer.resolve(blob);
374 //var split = type.split('[/;]');
375 //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg');
376 }, function (e) {
377 defer.reject(e);
378 });
379 return defer.promise;
380 };
381
382 this.setDefaults = function (defaults) {
383 this.defaults = defaults || {};
384 };
385
386 this.defaults = {};
387 this.version = ngFileUpload.version;
388 }
389
390 ]);
+0
-452
src/validate.js less more
0 ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) {
1 var upload = UploadDataUrl;
2
3 function globStringToRegex(str) {
4 var regexp = '', excludes = [];
5 if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') {
6 regexp = str.substring(1, str.length - 1);
7 } else {
8 var split = str.split(',');
9 if (split.length > 1) {
10 for (var i = 0; i < split.length; i++) {
11 var r = globStringToRegex(split[i]);
12 if (r.regexp) {
13 regexp += '(' + r.regexp + ')';
14 if (i < split.length - 1) {
15 regexp += '|';
16 }
17 } else {
18 excludes = excludes.concat(r.excludes);
19 }
20 }
21 } else {
22 if (str.indexOf('!') === 0) {
23 excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$');
24 } else {
25 if (str.indexOf('.') === 0) {
26 str = '*' + str;
27 }
28 regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$';
29 regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
30 }
31 }
32 }
33 return {regexp: regexp, excludes: excludes};
34 }
35
36 upload.validatePattern = function (file, val) {
37 if (!val) {
38 return true;
39 }
40 var pattern = globStringToRegex(val), valid = true;
41 if (pattern.regexp && pattern.regexp.length) {
42 var regexp = new RegExp(pattern.regexp, 'i');
43 valid = (file.type != null && regexp.test(file.type)) ||
44 (file.name != null && regexp.test(file.name));
45 }
46 var len = pattern.excludes.length;
47 while (len--) {
48 var exclude = new RegExp(pattern.excludes[len], 'i');
49 valid = valid && (file.type == null || exclude.test(file.type)) &&
50 (file.name == null || exclude.test(file.name));
51 }
52 return valid;
53 };
54
55 upload.ratioToFloat = function (val) {
56 var r = val.toString(), xIndex = r.search(/[x:]/i);
57 if (xIndex > -1) {
58 r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1));
59 } else {
60 r = parseFloat(r);
61 }
62 return r;
63 };
64
65 upload.registerModelChangeValidator = function (ngModel, attr, scope) {
66 if (ngModel) {
67 ngModel.$formatters.push(function (files) {
68 if (ngModel.$dirty) {
69 if (files && !angular.isArray(files)) {
70 files = [files];
71 }
72 upload.validate(files, 0, ngModel, attr, scope).then(function () {
73 upload.applyModelValidation(ngModel, files);
74 });
75 }
76 });
77 }
78 };
79
80 function markModelAsDirty(ngModel, files) {
81 if (files != null && !ngModel.$dirty) {
82 if (ngModel.$setDirty) {
83 ngModel.$setDirty();
84 } else {
85 ngModel.$dirty = true;
86 }
87 }
88 }
89
90 upload.applyModelValidation = function (ngModel, files) {
91 markModelAsDirty(ngModel, files);
92 angular.forEach(ngModel.$ngfValidations, function (validation) {
93 ngModel.$setValidity(validation.name, validation.valid);
94 });
95 };
96
97 upload.getValidationAttr = function (attr, scope, name, validationName, file) {
98 var dName = 'ngf' + name[0].toUpperCase() + name.substr(1);
99 var val = upload.attrGetter(dName, attr, scope, {$file: file});
100 if (val == null) {
101 val = upload.attrGetter('ngfValidate', attr, scope, {$file: file});
102 if (val) {
103 var split = (validationName || name).split('.');
104 val = val[split[0]];
105 if (split.length > 1) {
106 val = val && val[split[1]];
107 }
108 }
109 }
110 return val;
111 };
112
113 upload.validate = function (files, prevLength, ngModel, attr, scope) {
114 ngModel = ngModel || {};
115 ngModel.$ngfValidations = ngModel.$ngfValidations || [];
116
117 angular.forEach(ngModel.$ngfValidations, function (v) {
118 v.valid = true;
119 });
120
121 var attrGetter = function (name, params) {
122 return upload.attrGetter(name, attr, scope, params);
123 };
124
125 if (files == null || files.length === 0) {
126 return upload.emptyPromise(ngModel);
127 }
128
129 files = files.length === undefined ? [files] : files.slice(0);
130
131 function validateSync(name, validationName, fn) {
132 if (files) {
133 var i = files.length, valid = null;
134 while (i--) {
135 var file = files[i];
136 if (file) {
137 var val = upload.getValidationAttr(attr, scope, name, validationName, file);
138 if (val != null) {
139 if (!fn(file, val, i)) {
140 file.$error = name;
141 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
142 file.$errorParam = val;
143 files.splice(i, 1);
144 valid = false;
145 }
146 }
147 }
148 }
149 if (valid !== null) {
150 ngModel.$ngfValidations.push({name: name, valid: valid});
151 }
152 }
153 }
154
155 validateSync('maxFiles', null, function (file, val, i) {
156 return prevLength + i < val;
157 });
158 validateSync('pattern', null, upload.validatePattern);
159 validateSync('minSize', 'size.min', function (file, val) {
160 return file.size + 0.1 >= upload.translateScalars(val);
161 });
162 validateSync('maxSize', 'size.max', function (file, val) {
163 return file.size - 0.1 <= upload.translateScalars(val);
164 });
165 var totalSize = 0;
166 validateSync('maxTotalSize', null, function (file, val) {
167 totalSize += file.size;
168 if (totalSize > upload.translateScalars(val)) {
169 files.splice(0, files.length);
170 return false;
171 }
172 return true;
173 });
174
175 validateSync('validateFn', null, function (file, r) {
176 return r === true || r === null || r === '';
177 });
178
179 if (!files.length) {
180 return upload.emptyPromise(ngModel, ngModel.$ngfValidations);
181 }
182
183 function validateAsync(name, validationName, type, asyncFn, fn) {
184 function resolveResult(defer, file, val) {
185 if (val != null) {
186 asyncFn(file, val).then(function (d) {
187 if (!fn(d, val)) {
188 file.$error = name;
189 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
190 file.$errorParam = val;
191 defer.reject();
192 } else {
193 defer.resolve();
194 }
195 }, function () {
196 if (attrGetter('ngfValidateForce', {$file: file})) {
197 file.$error = name;
198 (file.$errorMessages = (file.$errorMessages || {}))[name] = true;
199 file.$errorParam = val;
200 defer.reject();
201 } else {
202 defer.resolve();
203 }
204 });
205 } else {
206 defer.resolve();
207 }
208 }
209
210 var promises = [upload.emptyPromise()];
211 if (files) {
212 files = files.length === undefined ? [files] : files;
213 angular.forEach(files, function (file) {
214 var defer = $q.defer();
215 promises.push(defer.promise);
216 if (type && (file.type == null || file.type.search(type) !== 0)) {
217 defer.resolve();
218 return;
219 }
220 if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) {
221 upload.imageDimensions(file).then(function (d) {
222 resolveResult(defer, file,
223 attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height}));
224 }, function () {
225 defer.reject();
226 });
227 } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) {
228 upload.mediaDuration(file).then(function (d) {
229 resolveResult(defer, file,
230 attrGetter('ngfDuration', {$file: file, $duration: d}));
231 }, function () {
232 defer.reject();
233 });
234 } else {
235 resolveResult(defer, file,
236 upload.getValidationAttr(attr, scope, name, validationName, file));
237 }
238 });
239 return $q.all(promises).then(function () {
240 ngModel.$ngfValidations.push({name: name, valid: true});
241 }, function () {
242 ngModel.$ngfValidations.push({name: name, valid: false});
243 });
244 }
245 }
246
247 var deffer = $q.defer();
248 var promises = [];
249
250 promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/,
251 this.imageDimensions, function (d, val) {
252 return d.height <= val;
253 })));
254 promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/,
255 this.imageDimensions, function (d, val) {
256 return d.height >= val;
257 })));
258 promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/,
259 this.imageDimensions, function (d, val) {
260 return d.width <= val;
261 })));
262 promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/,
263 this.imageDimensions, function (d, val) {
264 return d.width >= val;
265 })));
266 promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/,
267 function (file, val) {
268 return upload.emptyPromise(val);
269 }, function (r) {
270 return r;
271 })));
272 promises.push(upload.happyPromise(validateAsync('ratio', null, /image/,
273 this.imageDimensions, function (d, val) {
274 var split = val.toString().split(','), valid = false;
275 for (var i = 0; i < split.length; i++) {
276 if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) {
277 valid = true;
278 }
279 }
280 return valid;
281 })));
282 promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/,
283 this.imageDimensions, function (d, val) {
284 return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001;
285 })));
286 promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/,
287 this.imageDimensions, function (d, val) {
288 return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001;
289 })));
290 promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/,
291 this.mediaDuration, function (d, val) {
292 return d <= upload.translateScalars(val);
293 })));
294 promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/,
295 this.mediaDuration, function (d, val) {
296 return d >= upload.translateScalars(val);
297 })));
298 promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/,
299 function (file, val) {
300 return upload.emptyPromise(val);
301 }, function (r) {
302 return r;
303 })));
304
305 promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null,
306 function (file, val) {
307 return val;
308 }, function (r) {
309 return r === true || r === null || r === '';
310 })));
311
312 return $q.all(promises).then(function () {
313 deffer.resolve(ngModel, ngModel.$ngfValidations);
314 });
315 };
316
317 upload.imageDimensions = function (file) {
318 if (file.$ngfWidth && file.$ngfHeight) {
319 var d = $q.defer();
320 $timeout(function () {
321 d.resolve({width: file.$ngfWidth, height: file.$ngfHeight});
322 });
323 return d.promise;
324 }
325 if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise;
326
327 var deferred = $q.defer();
328 $timeout(function () {
329 if (file.type.indexOf('image') !== 0) {
330 deferred.reject('not image');
331 return;
332 }
333 upload.dataUrl(file).then(function (dataUrl) {
334 var img = angular.element('<img>').attr('src', dataUrl)
335 .css('visibility', 'hidden').css('position', 'fixed')
336 .css('max-width', 'none !important').css('max-height', 'none !important');
337
338 function success() {
339 var width = img[0].clientWidth;
340 var height = img[0].clientHeight;
341 img.remove();
342 file.$ngfWidth = width;
343 file.$ngfHeight = height;
344 deferred.resolve({width: width, height: height});
345 }
346
347 function error() {
348 img.remove();
349 deferred.reject('load error');
350 }
351
352 img.on('load', success);
353 img.on('error', error);
354 var count = 0;
355
356 function checkLoadError() {
357 $timeout(function () {
358 if (img[0].parentNode) {
359 if (img[0].clientWidth) {
360 success();
361 } else if (count > 10) {
362 error();
363 } else {
364 checkLoadError();
365 }
366 }
367 }, 1000);
368 }
369
370 checkLoadError();
371
372 angular.element(document.getElementsByTagName('body')[0]).append(img);
373 }, function () {
374 deferred.reject('load error');
375 });
376 });
377
378 file.$ngfDimensionPromise = deferred.promise;
379 file.$ngfDimensionPromise['finally'](function () {
380 delete file.$ngfDimensionPromise;
381 });
382 return file.$ngfDimensionPromise;
383 };
384
385 upload.mediaDuration = function (file) {
386 if (file.$ngfDuration) {
387 var d = $q.defer();
388 $timeout(function () {
389 d.resolve(file.$ngfDuration);
390 });
391 return d.promise;
392 }
393 if (file.$ngfDurationPromise) return file.$ngfDurationPromise;
394
395 var deferred = $q.defer();
396 $timeout(function () {
397 if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) {
398 deferred.reject('not media');
399 return;
400 }
401 upload.dataUrl(file).then(function (dataUrl) {
402 var el = angular.element(file.type.indexOf('audio') === 0 ? '<audio>' : '<video>')
403 .attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');
404
405 function success() {
406 var duration = el[0].duration;
407 file.$ngfDuration = duration;
408 el.remove();
409 deferred.resolve(duration);
410 }
411
412 function error() {
413 el.remove();
414 deferred.reject('load error');
415 }
416
417 el.on('loadedmetadata', success);
418 el.on('error', error);
419 var count = 0;
420
421 function checkLoadError() {
422 $timeout(function () {
423 if (el[0].parentNode) {
424 if (el[0].duration) {
425 success();
426 } else if (count > 10) {
427 error();
428 } else {
429 checkLoadError();
430 }
431 }
432 }, 1000);
433 }
434
435 checkLoadError();
436
437 angular.element(document.body).append(el);
438 }, function () {
439 deferred.reject('load error');
440 });
441 });
442
443 file.$ngfDurationPromise = deferred.promise;
444 file.$ngfDurationPromise['finally'](function () {
445 delete file.$ngfDurationPromise;
446 });
447 return file.$ngfDurationPromise;
448 };
449 return upload;
450 }
451 ]);
+0
-3
test/.bowerrc less more
0 {
1 "directory": "bower_components"
2 }
+0
-9
test/bower.json less more
0 {
1 "name": "web",
2 "private": true,
3 "dependencies": {
4 "chai": "~1.8.0",
5 "mocha": "~1.14.0"
6 },
7 "devDependencies": {}
8 }
+0
-26
test/index.html less more
0 <!doctype html>
1 <html>
2 <head>
3 <meta charset="utf-8">
4 <title>Mocha Spec Runner</title>
5 <link rel="stylesheet" href="bower_components/mocha/mocha.css">
6 </head>
7 <body>
8 <div id="mocha"></div>
9 <script src="bower_components/mocha/mocha.js"></script>
10 <script>mocha.setup('bdd')</script>
11 <script src="bower_components/chai/chai.js"></script>
12 <script>
13 var assert = chai.assert;
14 var expect = chai.expect;
15 var should = chai.should();
16 </script>
17
18 <!-- include source files here... -->
19
20 <!-- include spec files here... -->
21 <script src="spec/test.js"></script>
22
23 <script>mocha.run()</script>
24 </body>
25 </html>
+0
-13
test/spec/test.js less more
0 /* global describe, it */
1
2 (function () {
3 'use strict';
4
5 describe('Give it some context', function () {
6 describe('maybe a bit more context here', function () {
7 it('should run here few assertions', function () {
8
9 });
10 });
11 });
12 })();