GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Code Duplication    Length = 4287-4292 lines in 4 locations

third-party/angularjs-modules-plugins/ng-file-upload-12.2.12/src/FileAPI.js 1 location

@@ 22-4313 (lines=4292) @@
19
/*jslint nomen: true, regexp: true */
20
/*global window, atob, Blob, ArrayBuffer, Uint8Array */
21
22
(function (window) {
23
    'use strict';
24
    var CanvasPrototype = window.HTMLCanvasElement &&
25
            window.HTMLCanvasElement.prototype,
26
        hasBlobConstructor = window.Blob && (function () {
27
            try {
28
                return Boolean(new Blob());
29
            } catch (e) {
30
                return false;
31
            }
32
        }()),
33
        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
34
            (function () {
35
                try {
36
                    return new Blob([new Uint8Array(100)]).size === 100;
37
                } catch (e) {
38
                    return false;
39
                }
40
            }()),
41
        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
42
            window.MozBlobBuilder || window.MSBlobBuilder,
43
        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
44
            window.ArrayBuffer && window.Uint8Array && function (dataURI) {
45
                var byteString,
46
                    arrayBuffer,
47
                    intArray,
48
                    i,
49
                    mimeString,
50
                    bb;
51
                if (dataURI.split(',')[0].indexOf('base64') >= 0) {
52
                    // Convert base64 to raw binary data held in a string:
53
                    byteString = atob(dataURI.split(',')[1]);
54
                } else {
55
                    // Convert base64/URLEncoded data component to raw binary data:
56
                    byteString = decodeURIComponent(dataURI.split(',')[1]);
57
                }
58
                // Write the bytes of the string to an ArrayBuffer:
59
                arrayBuffer = new ArrayBuffer(byteString.length);
60
                intArray = new Uint8Array(arrayBuffer);
61
                for (i = 0; i < byteString.length; i += 1) {
62
                    intArray[i] = byteString.charCodeAt(i);
63
                }
64
                // Separate out the mime component:
65
                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
66
                // Write the ArrayBuffer (or ArrayBufferView) to a blob:
67
                if (hasBlobConstructor) {
68
                    return new Blob(
69
                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],
70
                        {type: mimeString}
71
                    );
72
                }
73
                bb = new BlobBuilder();
74
                bb.append(arrayBuffer);
75
                return bb.getBlob(mimeString);
76
            };
77
    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
78
        if (CanvasPrototype.mozGetAsFile) {
79
            CanvasPrototype.toBlob = function (callback, type, quality) {
80
                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
81
                    callback(dataURLtoBlob(this.toDataURL(type, quality)));
82
                } else {
83
                    callback(this.mozGetAsFile('blob', type));
84
                }
85
            };
86
        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
87
            CanvasPrototype.toBlob = function (callback, type, quality) {
88
                callback(dataURLtoBlob(this.toDataURL(type, quality)));
89
            };
90
        }
91
    }
92
    window.dataURLtoBlob = dataURLtoBlob;
93
})(window);
94
95
/*jslint evil: true */
96
/*global window, URL, webkitURL, ActiveXObject */
97
98
(function (window, undef){
99
	'use strict';
100
101
	var
102
		gid = 1,
103
		noop = function (){},
104
105
		document = window.document,
106
		doctype = document.doctype || {},
107
		userAgent = window.navigator.userAgent,
108
109
		// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
110
		apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
111
112
		Blob = window.Blob,
113
		File = window.File,
114
		FileReader = window.FileReader,
115
		FormData = window.FormData,
116
117
118
		XMLHttpRequest = window.XMLHttpRequest,
119
		jQuery = window.jQuery,
120
121
		html5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
122
				&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
123
124
		cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
125
126
		chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
127
128
		// https://github.com/blueimp/JavaScript-Canvas-to-Blob
129
		dataURLtoBlob = window.dataURLtoBlob,
130
131
132
		_rimg = /img/i,
133
		_rcanvas = /canvas/i,
134
		_rimgcanvas = /img|canvas/i,
135
		_rinput = /input/i,
136
		_rdata = /^data:[^,]+,/,
137
138
		_toString = {}.toString,
139
140
141
		Math = window.Math,
142
143
		_SIZE_CONST = function (pow){
144
			pow = new window.Number(Math.pow(1024, pow));
145
			pow.from = function (sz){ return Math.round(sz * this); };
146
			return	pow;
147
		},
148
149
		_elEvents = {}, // element event listeners
150
		_infoReader = [], // list of file info processors
151
152
		_readerEvents = 'abort progress error load loadend',
153
		_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
154
155
		currentTarget = 'currentTarget', // for minimize
156
		preventDefault = 'preventDefault', // and this too
157
158
		_isArray = function (ar) {
159
			return	ar && ('length' in ar);
160
		},
161
162
		/**
163
		 * Iterate over a object or array
164
		 */
165
		_each = function (obj, fn, ctx){
166
			if( obj ){
167
				if( _isArray(obj) ){
168
					for( var i = 0, n = obj.length; i < n; i++ ){
169
						if( i in obj ){
170
							fn.call(ctx, obj[i], i, obj);
171
						}
172
					}
173
				}
174
				else {
175
					for( var key in obj ){
176
						if( obj.hasOwnProperty(key) ){
177
							fn.call(ctx, obj[key], key, obj);
178
						}
179
					}
180
				}
181
			}
182
		},
183
184
		/**
185
		 * Merge the contents of two or more objects together into the first object
186
		 */
187
		_extend = function (dst){
188
			var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
189
			for( ; i < args.length; i++ ){
190
				_each(args[i], _ext);
191
			}
192
			return  dst;
193
		},
194
195
		/**
196
		 * Add event listener
197
		 */
198
		_on = function (el, type, fn){
199
			if( el ){
200
				var uid = api.uid(el);
201
202
				if( !_elEvents[uid] ){
203
					_elEvents[uid] = {};
204
				}
205
206
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
207
				_each(type.split(/\s+/), function (type){
208
					if( jQuery && !isFileReader){
209
						jQuery.event.add(el, type, fn);
210
					} else {
211
						if( !_elEvents[uid][type] ){
212
							_elEvents[uid][type] = [];
213
						}
214
215
						_elEvents[uid][type].push(fn);
216
217
						if( el.addEventListener ){ el.addEventListener(type, fn, false); }
218
						else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
219
						else { el['on'+type] = fn; }
220
					}
221
				});
222
			}
223
		},
224
225
226
		/**
227
		 * Remove event listener
228
		 */
229
		_off = function (el, type, fn){
230
			if( el ){
231
				var uid = api.uid(el), events = _elEvents[uid] || {};
232
233
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
234
				_each(type.split(/\s+/), function (type){
235
					if( jQuery && !isFileReader){
236
						jQuery.event.remove(el, type, fn);
237
					}
238
					else {
239
						var fns = events[type] || [], i = fns.length;
240
241
						while( i-- ){
242
							if( fns[i] === fn ){
243
								fns.splice(i, 1);
244
								break;
245
							}
246
						}
247
248
						if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
249
						else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
250
						else { el['on'+type] = null; }
251
					}
252
				});
253
			}
254
		},
255
256
257
		_one = function(el, type, fn){
258
			_on(el, type, function _(evt){
259
				_off(el, type, _);
260
				fn(evt);
261
			});
262
		},
263
264
265
		_fixEvent = function (evt){
266
			if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
267
			if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
268
			return  evt;
269
		},
270
271
272
		_supportInputAttr = function (attr){
273
			var input = document.createElement('input');
274
			input.setAttribute('type', "file");
275
			return attr in input;
276
		},
277
278
		/**
279
		 * FileAPI (core object)
280
		 */
281
		api = {
282
			version: '2.0.7',
283
284
			cors: false,
285
			html5: true,
286
			media: false,
287
			formData: true,
288
			multiPassResize: true,
289
290
			debug: false,
291
			pingUrl: false,
292
			multiFlash: false,
293
			flashAbortTimeout: 0,
294
			withCredentials: true,
295
296
			staticPath: './dist/',
297
298
			flashUrl: 0, // @default: './FileAPI.flash.swf'
299
			flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
300
301
			postNameConcat: function (name, idx){
302
				return	name + (idx != null ? '['+ idx +']' : '');
303
			},
304
305
			ext2mime: {
306
				  jpg:	'image/jpeg'
307
				, tif:	'image/tiff'
308
				, txt:	'text/plain'
309
			},
310
311
			// Fallback for flash
312
			accept: {
313
				  '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'
314
				, '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'
315
				, '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'
316
			},
317
318
			uploadRetry : 0,
319
			networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
320
321
			chunkSize : 0,
322
			chunkUploadRetry : 0,
323
			chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
324
325
			KB: _SIZE_CONST(1),
326
			MB: _SIZE_CONST(2),
327
			GB: _SIZE_CONST(3),
328
			TB: _SIZE_CONST(4),
329
330
			EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
331
332
			expando: 'fileapi' + (new Date).getTime(),
333
334
			uid: function (obj){
335
				return	obj
336
					? (obj[api.expando] = obj[api.expando] || api.uid())
337
					: (++gid, api.expando + gid)
338
				;
339
			},
340
341
			log: function (){
342
				// ngf fix for IE8 #1071
343
				if( api.debug && api._supportConsoleLog ){
344
					if( api._supportConsoleLogApply ){
345
						console.log.apply(console, arguments);
346
					}
347
					else {
348
						console.log([].join.call(arguments, ' '));
349
					}
350
				}
351
			},
352
353
			/**
354
			 * Create new image
355
			 *
356
			 * @param {String} [src]
357
			 * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element
358
			 * @returns {HTMLElement}
359
			 */
360
			newImage: function (src, fn){
361
				var img = document.createElement('img');
362
				if( fn ){
363
					api.event.one(img, 'error load', function (evt){
364
						fn(evt.type == 'error', img);
365
						img = null;
366
					});
367
				}
368
				img.src = src;
369
				return	img;
370
			},
371
372
			/**
373
			 * Get XHR
374
			 * @returns {XMLHttpRequest}
375
			 */
376
			getXHR: function (){
377
				var xhr;
378
379
				if( XMLHttpRequest ){
380
					xhr = new XMLHttpRequest;
381
				}
382
				else if( window.ActiveXObject ){
383
					try {
384
						xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
385
					} catch (e) {
386
						xhr = new ActiveXObject('Microsoft.XMLHTTP');
387
					}
388
				}
389
390
				return  xhr;
391
			},
392
393
			isArray: _isArray,
394
395
			support: {
396
				dnd:     cors && ('ondrop' in document.createElement('div')),
397
				cors:    cors,
398
				html5:   html5,
399
				chunked: chunked,
400
				dataURI: true,
401
				accept:   _supportInputAttr('accept'),
402
				multiple: _supportInputAttr('multiple')
403
			},
404
405
			event: {
406
				  on: _on
407
				, off: _off
408
				, one: _one
409
				, fix: _fixEvent
410
			},
411
412
413
			throttle: function(fn, delay) {
414
				var id, args;
415
416
				return function _throttle(){
417
					args = arguments;
418
419
					if( !id ){
420
						fn.apply(window, args);
421
						id = setTimeout(function (){
422
							id = 0;
423
							fn.apply(window, args);
424
						}, delay);
425
					}
426
				};
427
			},
428
429
430
			F: function (){},
431
432
433
			parseJSON: function (str){
434
				var json;
435
				if( window.JSON && JSON.parse ){
436
					json = JSON.parse(str);
437
				}
438
				else {
439
					json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
440
				}
441
				return json;
442
			},
443
444
445
			trim: function (str){
446
				str = String(str);
447
				return	str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
448
			},
449
450
			/**
451
			 * Simple Defer
452
			 * @return	{Object}
453
			 */
454
			defer: function (){
455
				var
456
					  list = []
457
					, result
458
					, error
459
					, defer = {
460
						resolve: function (err, res){
461
							defer.resolve = noop;
462
							error	= err || false;
463
							result	= res;
464
465
							while( res = list.shift() ){
466
								res(error, result);
467
							}
468
						},
469
470
						then: function (fn){
471
							if( error !== undef ){
472
								fn(error, result);
473
							} else {
474
								list.push(fn);
475
							}
476
						}
477
				};
478
479
				return	defer;
480
			},
481
482
			queue: function (fn){
483
				var
484
					  _idx = 0
485
					, _length = 0
486
					, _fail = false
487
					, _end = false
488
					, queue = {
489
						inc: function (){
490
							_length++;
491
						},
492
493
						next: function (){
494
							_idx++;
495
							setTimeout(queue.check, 0);
496
						},
497
498
						check: function (){
499
							(_idx >= _length) && !_fail && queue.end();
500
						},
501
502
						isFail: function (){
503
							return _fail;
504
						},
505
506
						fail: function (){
507
							!_fail && fn(_fail = true);
508
						},
509
510
						end: function (){
511
							if( !_end ){
512
								_end = true;
513
								fn();
514
							}
515
						}
516
					}
517
				;
518
				return queue;
519
			},
520
521
522
			/**
523
			 * For each object
524
			 *
525
			 * @param	{Object|Array}	obj
526
			 * @param	{Function}		fn
527
			 * @param	{*}				[ctx]
528
			 */
529
			each: _each,
530
531
532
			/**
533
			 * Async for
534
			 * @param {Array} array
535
			 * @param {Function} callback
536
			 */
537
			afor: function (array, callback){
538
				var i = 0, n = array.length;
539
540
				if( _isArray(array) && n-- ){
541
					(function _next(){
542
						callback(n != i && _next, array[i], i++);
543
					})();
544
				}
545
				else {
546
					callback(false);
547
				}
548
			},
549
550
551
			/**
552
			 * Merge the contents of two or more objects together into the first object
553
			 *
554
			 * @param	{Object}	dst
555
			 * @return	{Object}
556
			 */
557
			extend: _extend,
558
559
560
			/**
561
			 * Is file?
562
			 * @param  {File}  file
563
			 * @return {Boolean}
564
			 */
565
			isFile: function (file){
566
				return _toString.call(file) === '[object File]';
567
			},
568
569
570
			/**
571
			 * Is blob?
572
			 * @param   {Blob}  blob
573
			 * @returns {Boolean}
574
			 */
575
			isBlob: function (blob) {
576
				return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
577
			},
578
579
580
			/**
581
			 * Is canvas element
582
			 *
583
			 * @param	{HTMLElement}	el
584
			 * @return	{Boolean}
585
			 */
586
			isCanvas: function (el){
587
				return	el && _rcanvas.test(el.nodeName);
588
			},
589
590
591
			getFilesFilter: function (filter){
592
				filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
593
				return	filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
594
			},
595
596
597
598
			/**
599
			 * Read as DataURL
600
			 *
601
			 * @param {File|Element} file
602
			 * @param {Function} fn
603
			 */
604
			readAsDataURL: function (file, fn){
605
				if( api.isCanvas(file) ){
606
					_emit(file, fn, 'load', api.toDataURL(file));
607
				}
608
				else {
609
					_readAs(file, fn, 'DataURL');
610
				}
611
			},
612
613
614
			/**
615
			 * Read as Binary string
616
			 *
617
			 * @param {File} file
618
			 * @param {Function} fn
619
			 */
620
			readAsBinaryString: function (file, fn){
621
				if( _hasSupportReadAs('BinaryString') ){
622
					_readAs(file, fn, 'BinaryString');
623
				} else {
624
					// Hello IE10!
625
					_readAs(file, function (evt){
626
						if( evt.type == 'load' ){
627
							try {
628
								// dataURL -> binaryString
629
								evt.result = api.toBinaryString(evt.result);
630
							} catch (e){
631
								evt.type = 'error';
632
								evt.message = e.toString();
633
							}
634
						}
635
						fn(evt);
636
					}, 'DataURL');
637
				}
638
			},
639
640
641
			/**
642
			 * Read as ArrayBuffer
643
			 *
644
			 * @param {File} file
645
			 * @param {Function} fn
646
			 */
647
			readAsArrayBuffer: function(file, fn){
648
				_readAs(file, fn, 'ArrayBuffer');
649
			},
650
651
652
			/**
653
			 * Read as text
654
			 *
655
			 * @param {File} file
656
			 * @param {String} encoding
657
			 * @param {Function} [fn]
658
			 */
659
			readAsText: function(file, encoding, fn){
660
				if( !fn ){
661
					fn	= encoding;
662
					encoding = 'utf-8';
663
				}
664
665
				_readAs(file, fn, 'Text', encoding);
666
			},
667
668
669
			/**
670
			 * Convert image or canvas to DataURL
671
			 *
672
			 * @param   {Element}  el      Image or Canvas element
673
			 * @param   {String}   [type]  mime-type
674
			 * @return  {String}
675
			 */
676
			toDataURL: function (el, type){
677
				if( typeof el == 'string' ){
678
					return  el;
679
				}
680
				else if( el.toDataURL ){
681
					return  el.toDataURL(type || 'image/png');
682
				}
683
			},
684
685
686
			/**
687
			 * Canvert string, image or canvas to binary string
688
			 *
689
			 * @param   {String|Element} val
690
			 * @return  {String}
691
			 */
692
			toBinaryString: function (val){
693
				return  window.atob(api.toDataURL(val).replace(_rdata, ''));
694
			},
695
696
697
			/**
698
			 * Read file or DataURL as ImageElement
699
			 *
700
			 * @param	{File|String}	file
701
			 * @param	{Function}		fn
702
			 * @param	{Boolean}		[progress]
703
			 */
704
			readAsImage: function (file, fn, progress){
705
				if( api.isFile(file) ){
706
					if( apiURL ){
707
						/** @namespace apiURL.createObjectURL */
708
						var data = apiURL.createObjectURL(file);
709
						if( data === undef ){
710
							_emit(file, fn, 'error');
711
						}
712
						else {
713
							api.readAsImage(data, fn, progress);
714
						}
715
					}
716
					else {
717
						api.readAsDataURL(file, function (evt){
718
							if( evt.type == 'load' ){
719
								api.readAsImage(evt.result, fn, progress);
720
							}
721
							else if( progress || evt.type == 'error' ){
722
								_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
723
							}
724
						});
725
					}
726
				}
727
				else if( api.isCanvas(file) ){
728
					_emit(file, fn, 'load', file);
729
				}
730
				else if( _rimg.test(file.nodeName) ){
731
					if( file.complete ){
732
						_emit(file, fn, 'load', file);
733
					}
734
					else {
735
						var events = 'error abort load';
736
						_one(file, events, function _fn(evt){
737
							if( evt.type == 'load' && apiURL ){
738
								/** @namespace apiURL.revokeObjectURL */
739
								apiURL.revokeObjectURL(file.src);
740
							}
741
742
							_off(file, events, _fn);
743
							_emit(file, fn, evt, file);
744
						});
745
					}
746
				}
747
				else if( file.iframe ){
748
					_emit(file, fn, { type: 'error' });
749
				}
750
				else {
751
					// Created image
752
					var img = api.newImage(file.dataURL || file);
753
					api.readAsImage(img, fn, progress);
754
				}
755
			},
756
757
758
			/**
759
			 * Make file by name
760
			 *
761
			 * @param	{String}	name
762
			 * @return	{Array}
763
			 */
764
			checkFileObj: function (name){
765
				var file = {}, accept = api.accept;
766
767
				if( typeof name == 'object' ){
768
					file = name;
769
				}
770
				else {
771
					file.name = (name + '').split(/\\|\//g).pop();
772
				}
773
774
				if( file.type == null ){
775
					file.type = file.name.split('.').pop();
776
				}
777
778
				_each(accept, function (ext, type){
779
					ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
780
					if( ext.test(file.type) || api.ext2mime[file.type] ){
781
						file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
782
					}
783
				});
784
785
				return	file;
786
			},
787
788
789
			/**
790
			 * Get drop files
791
			 *
792
			 * @param	{Event}	evt
793
			 * @param	{Function} callback
794
			 */
795
			getDropFiles: function (evt, callback){
796
				var
797
					  files = []
798
					, dataTransfer = _getDataTransfer(evt)
799
					, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
800
					, queue = api.queue(function (){ callback(files); })
801
				;
802
803
				_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
804
					queue.inc();
805
806
					try {
807
						if( entrySupport ){
808
							_readEntryAsFiles(item, function (err, entryFiles){
809
								if( err ){
810
									api.log('[err] getDropFiles:', err);
811
								} else {
812
									files.push.apply(files, entryFiles);
813
								}
814
								queue.next();
815
							});
816
						}
817
						else {
818
							_isRegularFile(item, function (yes){
819
								yes && files.push(item);
820
								queue.next();
821
							});
822
						}
823
					}
824
					catch( err ){
825
						queue.next();
826
						api.log('[err] getDropFiles: ', err);
827
					}
828
				});
829
830
				queue.check();
831
			},
832
833
834
			/**
835
			 * Get file list
836
			 *
837
			 * @param	{HTMLInputElement|Event}	input
838
			 * @param	{String|Function}	[filter]
839
			 * @param	{Function}			[callback]
840
			 * @return	{Array|Null}
841
			 */
842
			getFiles: function (input, filter, callback){
843
				var files = [];
844
845
				if( callback ){
846
					api.filterFiles(api.getFiles(input), filter, callback);
847
					return null;
848
				}
849
850
				if( input.jquery ){
851
					// jQuery object
852
					input.each(function (){
853
						files = files.concat(api.getFiles(this));
854
					});
855
					input	= files;
856
					files	= [];
857
				}
858
859
				if( typeof filter == 'string' ){
860
					filter	= api.getFilesFilter(filter);
861
				}
862
863
				if( input.originalEvent ){
864
					// jQuery event
865
					input = _fixEvent(input.originalEvent);
866
				}
867
				else if( input.srcElement ){
868
					// IE Event
869
					input = _fixEvent(input);
870
				}
871
872
873
				if( input.dataTransfer ){
874
					// Drag'n'Drop
875
					input = input.dataTransfer;
876
				}
877
				else if( input.target ){
878
					// Event
879
					input = input.target;
880
				}
881
882
				if( input.files ){
883
					// Input[type="file"]
884
					files = input.files;
885
886
					if( !html5 ){
887
						// Partial support for file api
888
						files[0].blob	= input;
889
						files[0].iframe	= true;
890
					}
891
				}
892
				else if( !html5 && isInputFile(input) ){
893
					if( api.trim(input.value) ){
894
						files = [api.checkFileObj(input.value)];
895
						files[0].blob   = input;
896
						files[0].iframe = true;
897
					}
898
				}
899
				else if( _isArray(input) ){
900
					files	= input;
901
				}
902
903
				return	api.filter(files, function (file){ return !filter || filter.test(file.name); });
904
			},
905
906
907
			/**
908
			 * Get total file size
909
			 * @param	{Array}	files
910
			 * @return	{Number}
911
			 */
912
			getTotalSize: function (files){
913
				var size = 0, i = files && files.length;
914
				while( i-- ){
915
					size += files[i].size;
916
				}
917
				return	size;
918
			},
919
920
921
			/**
922
			 * Get image information
923
			 *
924
			 * @param	{File}		file
925
			 * @param	{Function}	fn
926
			 */
927
			getInfo: function (file, fn){
928
				var info = {}, readers = _infoReader.concat();
929
930
				if( api.isFile(file) ){
931
					(function _next(){
932
						var reader = readers.shift();
933
						if( reader ){
934
							if( reader.test(file.type) ){
935
								reader(file, function (err, res){
936
									if( err ){
937
										fn(err);
938
									}
939
									else {
940
										_extend(info, res);
941
										_next();
942
									}
943
								});
944
							}
945
							else {
946
								_next();
947
							}
948
						}
949
						else {
950
							fn(false, info);
951
						}
952
					})();
953
				}
954
				else {
955
					fn('not_support_info', info);
956
				}
957
			},
958
959
960
			/**
961
			 * Add information reader
962
			 *
963
			 * @param {RegExp} mime
964
			 * @param {Function} fn
965
			 */
966
			addInfoReader: function (mime, fn){
967
				fn.test = function (type){ return mime.test(type); };
968
				_infoReader.push(fn);
969
			},
970
971
972
			/**
973
			 * Filter of array
974
			 *
975
			 * @param	{Array}		input
976
			 * @param	{Function}	fn
977
			 * @return	{Array}
978
			 */
979
			filter: function (input, fn){
980
				var result = [], i = 0, n = input.length, val;
981
982
				for( ; i < n; i++ ){
983
					if( i in input ){
984
						val = input[i];
985
						if( fn.call(val, val, i, input) ){
986
							result.push(val);
987
						}
988
					}
989
				}
990
991
				return	result;
992
			},
993
994
995
			/**
996
			 * Filter files
997
			 *
998
			 * @param	{Array}		files
999
			 * @param	{Function}	eachFn
1000
			 * @param	{Function}	resultFn
1001
			 */
1002
			filterFiles: function (files, eachFn, resultFn){
1003
				if( files.length ){
1004
					// HTML5 or Flash
1005
					var queue = files.concat(), file, result = [], deleted = [];
1006
1007
					(function _next(){
1008
						if( queue.length ){
1009
							file = queue.shift();
1010
							api.getInfo(file, function (err, info){
1011
								(eachFn(file, err ? false : info) ? result : deleted).push(file);
1012
								_next();
1013
							});
1014
						}
1015
						else {
1016
							resultFn(result, deleted);
1017
						}
1018
					})();
1019
				}
1020
				else {
1021
					resultFn([], files);
1022
				}
1023
			},
1024
1025
1026
			upload: function (options){
1027
				options = _extend({
1028
					  jsonp: 'callback'
1029
					, prepare: api.F
1030
					, beforeupload: api.F
1031
					, upload: api.F
1032
					, fileupload: api.F
1033
					, fileprogress: api.F
1034
					, filecomplete: api.F
1035
					, progress: api.F
1036
					, complete: api.F
1037
					, pause: api.F
1038
					, imageOriginal: true
1039
					, chunkSize: api.chunkSize
1040
					, chunkUploadRetry: api.chunkUploadRetry
1041
					, uploadRetry: api.uploadRetry
1042
				}, options);
1043
1044
1045
				if( options.imageAutoOrientation && !options.imageTransform ){
1046
					options.imageTransform = { rotate: 'auto' };
1047
				}
1048
1049
1050
				var
1051
					  proxyXHR = new api.XHR(options)
1052
					, dataArray = this._getFilesDataArray(options.files)
1053
					, _this = this
1054
					, _total = 0
1055
					, _loaded = 0
1056
					, _nextFile
1057
					, _complete = false
1058
				;
1059
1060
1061
				// calc total size
1062
				_each(dataArray, function (data){
1063
					_total += data.size;
1064
				});
1065
1066
				// Array of files
1067
				proxyXHR.files = [];
1068
				_each(dataArray, function (data){
1069
					proxyXHR.files.push(data.file);
1070
				});
1071
1072
				// Set upload status props
1073
				proxyXHR.total	= _total;
1074
				proxyXHR.loaded	= 0;
1075
				proxyXHR.filesLeft = dataArray.length;
1076
1077
				// emit "beforeupload"  event
1078
				options.beforeupload(proxyXHR, options);
1079
1080
				// Upload by file
1081
				_nextFile = function (){
1082
					var
1083
						  data = dataArray.shift()
1084
						, _file = data && data.file
1085
						, _fileLoaded = false
1086
						, _fileOptions = _simpleClone(options)
1087
					;
1088
1089
					proxyXHR.filesLeft = dataArray.length;
1090
1091
					if( _file && _file.name === api.expando ){
1092
						_file = null;
1093
						api.log('[warn] FileAPI.upload() — called without files');
1094
					}
1095
1096
					if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1097
						// Mark active job
1098
						_complete = false;
1099
1100
						// Set current upload file
1101
						proxyXHR.currentFile = _file;
1102
1103
						// Prepare file options
1104
						if (_file && options.prepare(_file, _fileOptions) === false) {
1105
							_nextFile.call(_this);
1106
							return;
1107
						}
1108
						_fileOptions.file = _file;
1109
1110
						_this._getFormData(_fileOptions, data, function (form){
1111
							if( !_loaded ){
1112
								// emit "upload" event
1113
								options.upload(proxyXHR, options);
1114
							}
1115
1116
							var xhr = new api.XHR(_extend({}, _fileOptions, {
1117
1118
								upload: _file ? function (){
1119
									// emit "fileupload" event
1120
									options.fileupload(_file, xhr, _fileOptions);
1121
								} : noop,
1122
1123
								progress: _file ? function (evt){
1124
									if( !_fileLoaded ){
1125
										// For ignore the double calls.
1126
										_fileLoaded = (evt.loaded === evt.total);
1127
1128
										// emit "fileprogress" event
1129
										options.fileprogress({
1130
											  type:   'progress'
1131
											, total:  data.total = evt.total
1132
											, loaded: data.loaded = evt.loaded
1133
										}, _file, xhr, _fileOptions);
1134
1135
										// emit "progress" event
1136
										options.progress({
1137
											  type:   'progress'
1138
											, total:  _total
1139
											, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1140
										}, _file, xhr, _fileOptions);
1141
									}
1142
								} : noop,
1143
1144
								complete: function (err){
1145
									_each(_xhrPropsExport, function (name){
1146
										proxyXHR[name] = xhr[name];
1147
									});
1148
1149
									if( _file ){
1150
										data.total = (data.total || data.size);
1151
										data.loaded	= data.total;
1152
1153
										if( !err ) {
1154
											// emulate 100% "progress"
1155
											this.progress(data);
1156
1157
											// fixed throttle event
1158
											_fileLoaded = true;
1159
1160
											// bytes loaded
1161
											_loaded += data.size; // data.size != data.total, it's desirable fix this
1162
											proxyXHR.loaded = _loaded;
1163
										}
1164
1165
										// emit "filecomplete" event
1166
										options.filecomplete(err, xhr, _file, _fileOptions);
1167
									}
1168
1169
									// upload next file
1170
									setTimeout(function () {_nextFile.call(_this);}, 0);
1171
								}
1172
							})); // xhr
1173
1174
1175
							// ...
1176
							proxyXHR.abort = function (current){
1177
								if (!current) { dataArray.length = 0; }
1178
								this.current = current;
1179
								xhr.abort();
1180
							};
1181
1182
							// Start upload
1183
							xhr.send(form);
1184
						});
1185
					}
1186
					else {
1187
						var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1188
						options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1189
						// Mark done state
1190
						_complete = true;
1191
					}
1192
				};
1193
1194
1195
				// Next tick
1196
				setTimeout(_nextFile, 0);
1197
1198
1199
				// Append more files to the existing request
1200
				// first - add them to the queue head/tail
1201
				proxyXHR.append = function (files, first) {
1202
					files = api._getFilesDataArray([].concat(files));
1203
1204
					_each(files, function (data) {
1205
						_total += data.size;
1206
						proxyXHR.files.push(data.file);
1207
						if (first) {
1208
							dataArray.unshift(data);
1209
						} else {
1210
							dataArray.push(data);
1211
						}
1212
					});
1213
1214
					proxyXHR.statusText = "";
1215
1216
					if( _complete ){
1217
						_nextFile.call(_this);
1218
					}
1219
				};
1220
1221
1222
				// Removes file from queue by file reference and returns it
1223
				proxyXHR.remove = function (file) {
1224
				    var i = dataArray.length, _file;
1225
				    while( i-- ){
1226
						if( dataArray[i].file == file ){
1227
							_file = dataArray.splice(i, 1);
1228
							_total -= _file.size;
1229
						}
1230
					}
1231
					return	_file;
1232
				};
1233
1234
				return proxyXHR;
1235
			},
1236
1237
1238
			_getFilesDataArray: function (data){
1239
				var files = [], oFiles = {};
1240
1241
				if( isInputFile(data) ){
1242
					var tmp = api.getFiles(data);
1243
					oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1244
				}
1245
				else if( _isArray(data) && isInputFile(data[0]) ){
1246
					_each(data, function (input){
1247
						oFiles[input.name || 'file'] = api.getFiles(input);
1248
					});
1249
				}
1250
				else {
1251
					oFiles = data;
1252
				}
1253
1254
				_each(oFiles, function add(file, name){
1255
					if( _isArray(file) ){
1256
						_each(file, function (file){
1257
							add(file, name);
1258
						});
1259
					}
1260
					else if( file && (file.name || file.image) ){
1261
						files.push({
1262
							  name: name
1263
							, file: file
1264
							, size: file.size
1265
							, total: file.size
1266
							, loaded: 0
1267
						});
1268
					}
1269
				});
1270
1271
				if( !files.length ){
1272
					// Create fake `file` object
1273
					files.push({ file: { name: api.expando } });
1274
				}
1275
1276
				return	files;
1277
			},
1278
1279
1280
			_getFormData: function (options, data, fn){
1281
				var
1282
					  file = data.file
1283
					, name = data.name
1284
					, filename = file.name
1285
					, filetype = file.type
1286
					, trans = api.support.transform && options.imageTransform
1287
					, Form = new api.Form
1288
					, queue = api.queue(function (){ fn(Form); })
1289
					, isOrignTrans = trans && _isOriginTransform(trans)
1290
					, postNameConcat = api.postNameConcat
1291
				;
1292
1293
				// Append data
1294
				_each(options.data, function add(val, name){
1295
					if( typeof val == 'object' ){
1296
						_each(val, function (v, i){
1297
							add(v, postNameConcat(name, i));
1298
						});
1299
					}
1300
					else {
1301
						Form.append(name, val);
1302
					}
1303
				});
1304
1305
				(function _addFile(file/**Object*/){
1306
					if( file.image ){ // This is a FileAPI.Image
1307
						queue.inc();
1308
1309
						file.toData(function (err, image){
1310
							// @todo: error
1311
							filename = filename || (new Date).getTime()+'.png';
1312
1313
							_addFile(image);
1314
							queue.next();
1315
						});
1316
					}
1317
					else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1318
						queue.inc();
1319
1320
						if( isOrignTrans ){
1321
							// Convert to array for transform function
1322
							trans = [trans];
1323
						}
1324
1325
						api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1326
							if( isOrignTrans && !err ){
1327
								if( !dataURLtoBlob && !api.flashEngine ){
1328
									// Canvas.toBlob or Flash not supported, use multipart
1329
									Form.multipart = true;
1330
								}
1331
1332
								Form.append(name, images[0], filename,  trans[0].type || filetype);
1333
							}
1334
							else {
1335
								var addOrigin = 0;
1336
1337
								if( !err ){
1338
									_each(images, function (image, idx){
1339
										if( !dataURLtoBlob && !api.flashEngine ){
1340
											Form.multipart = true;
1341
										}
1342
1343
										if( !trans[idx].postName ){
1344
											addOrigin = 1;
1345
										}
1346
1347
										Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1348
									});
1349
								}
1350
1351
								if( err || options.imageOriginal ){
1352
									Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1353
								}
1354
							}
1355
1356
							queue.next();
1357
						});
1358
					}
1359
					else if( filename !== api.expando ){
1360
						Form.append(name, file, filename);
1361
					}
1362
				})(file);
1363
1364
				queue.check();
1365
			},
1366
1367
1368
			reset: function (inp, notRemove){
1369
				var parent, clone;
1370
1371
				if( jQuery ){
1372
					clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1373
					if( !notRemove ){
1374
						jQuery(inp).remove();
1375
					}
1376
				} else {
1377
					parent  = inp.parentNode;
1378
					clone   = parent.insertBefore(inp.cloneNode(true), inp);
1379
					clone.value = '';
1380
1381
					if( !notRemove ){
1382
						parent.removeChild(inp);
1383
					}
1384
1385
					_each(_elEvents[api.uid(inp)], function (fns, type){
1386
						_each(fns, function (fn){
1387
							_off(inp, type, fn);
1388
							_on(clone, type, fn);
1389
						});
1390
					});
1391
				}
1392
1393
				return  clone;
1394
			},
1395
1396
1397
			/**
1398
			 * Load remote file
1399
			 *
1400
			 * @param   {String}    url
1401
			 * @param   {Function}  fn
1402
			 * @return  {XMLHttpRequest}
1403
			 */
1404
			load: function (url, fn){
1405
				var xhr = api.getXHR();
1406
				if( xhr ){
1407
					xhr.open('GET', url, true);
1408
1409
					if( xhr.overrideMimeType ){
1410
				        xhr.overrideMimeType('text/plain; charset=x-user-defined');
1411
					}
1412
1413
					_on(xhr, 'progress', function (/**Event*/evt){
1414
						/** @namespace evt.lengthComputable */
1415
						if( evt.lengthComputable ){
1416
							fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1417
						}
1418
					});
1419
1420
					xhr.onreadystatechange = function(){
1421
						if( xhr.readyState == 4 ){
1422
							xhr.onreadystatechange = null;
1423
							if( xhr.status == 200 ){
1424
								url = url.split('/');
1425
								/** @namespace xhr.responseBody */
1426
								var file = {
1427
								      name: url[url.length-1]
1428
									, size: xhr.getResponseHeader('Content-Length')
1429
									, type: xhr.getResponseHeader('Content-Type')
1430
								};
1431
								file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1432
								fn({ type: 'load', result: file }, xhr);
1433
							}
1434
							else {
1435
								fn({ type: 'error' }, xhr);
1436
							}
1437
					    }
1438
					};
1439
				    xhr.send(null);
1440
				} else {
1441
					fn({ type: 'error' });
1442
				}
1443
1444
				return  xhr;
1445
			},
1446
1447
			encode64: function (str){
1448
				var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1449
1450
				if( typeof str !== 'string' ){
1451
					str	= String(str);
1452
				}
1453
1454
				while( i < str.length ){
1455
					//all three "& 0xff" added below are there to fix a known bug
1456
					//with bytes returned by xhr.responseText
1457
					var
1458
						  byte1 = str.charCodeAt(i++) & 0xff
1459
						, byte2 = str.charCodeAt(i++) & 0xff
1460
						, byte3 = str.charCodeAt(i++) & 0xff
1461
						, enc1 = byte1 >> 2
1462
						, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1463
						, enc3, enc4
1464
					;
1465
1466
					if( isNaN(byte2) ){
1467
						enc3 = enc4 = 64;
1468
					} else {
1469
						enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1470
						enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1471
					}
1472
1473
					outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1474
				}
1475
1476
				return  outStr;
1477
			}
1478
1479
		} // api
1480
	;
1481
1482
1483
	function _emit(target, fn, name, res, ext){
1484
		var evt = {
1485
			  type:		name.type || name
1486
			, target:	target
1487
			, result:	res
1488
		};
1489
		_extend(evt, ext);
1490
		fn(evt);
1491
	}
1492
1493
1494
	function _hasSupportReadAs(as){
1495
		return	FileReader && !!FileReader.prototype['readAs'+as];
1496
	}
1497
1498
1499
	function _readAs(file, fn, as, encoding){
1500
		if( api.isBlob(file) && _hasSupportReadAs(as) ){
1501
			var Reader = new FileReader;
1502
1503
			// Add event listener
1504
			_on(Reader, _readerEvents, function _fn(evt){
1505
				var type = evt.type;
1506
				if( type == 'progress' ){
1507
					_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1508
				}
1509
				else if( type == 'loadend' ){
1510
					_off(Reader, _readerEvents, _fn);
1511
					Reader = null;
1512
				}
1513
				else {
1514
					_emit(file, fn, evt, evt.target.result);
1515
				}
1516
			});
1517
1518
1519
			try {
1520
				// ReadAs ...
1521
				if( encoding ){
1522
					Reader['readAs'+as](file, encoding);
1523
				}
1524
				else {
1525
					Reader['readAs'+as](file);
1526
				}
1527
			}
1528
			catch (err){
1529
				_emit(file, fn, 'error', undef, { error: err.toString() });
1530
			}
1531
		}
1532
		else {
1533
			_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });
1534
		}
1535
	}
1536
1537
1538
	function _isRegularFile(file, callback){
1539
		// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1540
		if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1541
			if( FileReader ){
1542
				try {
1543
					var Reader = new FileReader();
1544
1545
					_one(Reader, _readerEvents, function (evt){
1546
						var isFile = evt.type != 'error';
1547
						callback(isFile);
1548
						if( isFile ){
1549
							Reader.abort();
1550
						}
1551
					});
1552
1553
					Reader.readAsDataURL(file);
1554
				} catch( err ){
1555
					callback(false);
1556
				}
1557
			}
1558
			else {
1559
				callback(null);
1560
			}
1561
		}
1562
		else {
1563
			callback(true);
1564
		}
1565
	}
1566
1567
1568
	function _getAsEntry(item){
1569
		var entry;
1570
		if( item.getAsEntry ){ entry = item.getAsEntry(); }
1571
		else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1572
		return	entry;
1573
	}
1574
1575
1576
	function _readEntryAsFiles(entry, callback){
1577
		if( !entry ){
1578
			// error
1579
			callback('invalid entry');
1580
		}
1581
		else if( entry.isFile ){
1582
			// Read as file
1583
			entry.file(function(file){
1584
				// success
1585
				file.fullPath = entry.fullPath;
1586
				callback(false, [file]);
1587
			}, function (err){
1588
				// error
1589
				callback('FileError.code: '+err.code);
1590
			});
1591
		}
1592
		else if( entry.isDirectory ){
1593
			var reader = entry.createReader(), result = [];
1594
1595
			reader.readEntries(function(entries){
1596
				// success
1597
				api.afor(entries, function (next, entry){
1598
					_readEntryAsFiles(entry, function (err, files){
1599
						if( err ){
1600
							api.log(err);
1601
						}
1602
						else {
1603
							result = result.concat(files);
1604
						}
1605
1606
						if( next ){
1607
							next();
1608
						}
1609
						else {
1610
							callback(false, result);
1611
						}
1612
					});
1613
				});
1614
			}, function (err){
1615
				// error
1616
				callback('directory_reader: ' + err);
1617
			});
1618
		}
1619
		else {
1620
			_readEntryAsFiles(_getAsEntry(entry), callback);
1621
		}
1622
	}
1623
1624
1625
	function _simpleClone(obj){
1626
		var copy = {};
1627
		_each(obj, function (val, key){
1628
			if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1629
				val = _extend({}, val);
1630
			}
1631
			copy[key] = val;
1632
		});
1633
		return	copy;
1634
	}
1635
1636
1637
	function isInputFile(el){
1638
		return	_rinput.test(el && el.tagName);
1639
	}
1640
1641
1642
	function _getDataTransfer(evt){
1643
		return	(evt.originalEvent || evt || '').dataTransfer || {};
1644
	}
1645
1646
1647
	function _isOriginTransform(trans){
1648
		var key;
1649
		for( key in trans ){
1650
			if( trans.hasOwnProperty(key) ){
1651
				if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1652
					return	true;
1653
				}
1654
			}
1655
		}
1656
		return	false;
1657
	}
1658
1659
1660
	// Add default image info reader
1661
	api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1662
		if( !file.__dimensions ){
1663
			var defer = file.__dimensions = api.defer();
1664
1665
			api.readAsImage(file, function (evt){
1666
				var img = evt.target;
1667
				defer.resolve(evt.type == 'load' ? false : 'error', {
1668
					  width:  img.width
1669
					, height: img.height
1670
				});
1671
                img.src = api.EMPTY_PNG;
1672
				img = null;
1673
			});
1674
		}
1675
1676
		file.__dimensions.then(callback);
1677
	});
1678
1679
1680
	/**
1681
	 * Drag'n'Drop special event
1682
	 *
1683
	 * @param	{HTMLElement}	el
1684
	 * @param	{Function}		onHover
1685
	 * @param	{Function}		onDrop
1686
	 */
1687
	api.event.dnd = function (el, onHover, onDrop){
1688
		var _id, _type;
1689
1690
		if( !onDrop ){
1691
			onDrop = onHover;
1692
			onHover = api.F;
1693
		}
1694
1695
		if( FileReader ){
1696
			// Hover
1697
			_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1698
				var
1699
					  types = _getDataTransfer(evt).types
1700
					, i = types && types.length
1701
					, debounceTrigger = false
1702
				;
1703
1704
				while( i-- ){
1705
					if( ~types[i].indexOf('File') ){
1706
						evt[preventDefault]();
1707
1708
						if( _type !== evt.type ){
1709
							_type = evt.type; // Store current type of event
1710
1711
							if( _type != 'dragleave' ){
1712
								onHover.call(evt[currentTarget], true, evt);
1713
							}
1714
1715
							debounceTrigger = true;
1716
						}
1717
1718
						break; // exit from "while"
1719
					}
1720
				}
1721
1722
				if( debounceTrigger ){
1723
					clearTimeout(_id);
1724
					_id = setTimeout(function (){
1725
						onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1726
					}, 50);
1727
				}
1728
			});
1729
1730
1731
			// Drop
1732
			_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1733
				evt[preventDefault]();
1734
1735
				_type = 0;
1736
				onHover.call(evt[currentTarget], false, evt);
1737
1738
				api.getDropFiles(evt, function (files){
1739
					onDrop.call(evt[currentTarget], files, evt);
1740
				});
1741
			});
1742
		}
1743
		else {
1744
			api.log("Drag'n'Drop -- not supported");
1745
		}
1746
	};
1747
1748
1749
	/**
1750
	 * Remove drag'n'drop
1751
	 * @param	{HTMLElement}	el
1752
	 * @param	{Function}		onHover
1753
	 * @param	{Function}		onDrop
1754
	 */
1755
	api.event.dnd.off = function (el, onHover, onDrop){
1756
		_off(el, 'dragenter dragleave dragover', onHover.ff);
1757
		_off(el, 'drop', onDrop.ff);
1758
	};
1759
1760
1761
	// Support jQuery
1762
	if( jQuery && !jQuery.fn.dnd ){
1763
		jQuery.fn.dnd = function (onHover, onDrop){
1764
			return this.each(function (){
1765
				api.event.dnd(this, onHover, onDrop);
1766
			});
1767
		};
1768
1769
		jQuery.fn.offdnd = function (onHover, onDrop){
1770
			return this.each(function (){
1771
				api.event.dnd.off(this, onHover, onDrop);
1772
			});
1773
		};
1774
	}
1775
1776
	// @export
1777
	window.FileAPI  = _extend(api, window.FileAPI);
1778
1779
1780
	// Debug info
1781
	api.log('FileAPI: ' + api.version);
1782
	api.log('protocol: ' + window.location.protocol);
1783
	api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1784
1785
1786
	// @detect 'x-ua-compatible'
1787
	_each(document.getElementsByTagName('meta'), function (meta){
1788
		if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1789
			api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1790
		}
1791
	});
1792
1793
1794
	// configuration
1795
	try {
1796
		api._supportConsoleLog = !!console.log;
1797
		api._supportConsoleLogApply = !!console.log.apply;
1798
	} catch (err) {}
1799
1800
	if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1801
	if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1802
	if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1803
})(window, void 0);
1804
1805
/*global window, FileAPI, document */
1806
1807
(function (api, document, undef) {
1808
	'use strict';
1809
1810
	var
1811
		min = Math.min,
1812
		round = Math.round,
1813
		getCanvas = function () { return document.createElement('canvas'); },
1814
		support = false,
1815
		exifOrientation = {
1816
			  8:	270
1817
			, 3:	180
1818
			, 6:	90
1819
			, 7:	270
1820
			, 4:	180
1821
			, 5:	90
1822
		}
1823
	;
1824
1825
	try {
1826
		support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1827
	}
1828
	catch (e){}
1829
1830
1831
	function Image(file){
1832
		if( file instanceof Image ){
1833
			var img = new Image(file.file);
1834
			api.extend(img.matrix, file.matrix);
1835
			return	img;
1836
		}
1837
		else if( !(this instanceof Image) ){
1838
			return	new Image(file);
1839
		}
1840
1841
		this.file   = file;
1842
		this.size   = file.size || 100;
1843
1844
		this.matrix	= {
1845
			sx: 0,
1846
			sy: 0,
1847
			sw: 0,
1848
			sh: 0,
1849
			dx: 0,
1850
			dy: 0,
1851
			dw: 0,
1852
			dh: 0,
1853
			resize: 0, // min, max OR preview
1854
			deg: 0,
1855
			quality: 1, // jpeg quality
1856
			filter: 0
1857
		};
1858
	}
1859
1860
1861
	Image.prototype = {
1862
		image: true,
1863
		constructor: Image,
1864
1865
		set: function (attrs){
1866
			api.extend(this.matrix, attrs);
1867
			return	this;
1868
		},
1869
1870
		crop: function (x, y, w, h){
1871
			if( w === undef ){
1872
				w	= x;
1873
				h	= y;
1874
				x = y = 0;
1875
			}
1876
			return	this.set({ sx: x, sy: y, sw: w, sh: h || w });
1877
		},
1878
1879
		resize: function (w, h, strategy){
1880
			if( /min|max/.test(h) ){
1881
				strategy = h;
1882
				h = w;
1883
			}
1884
1885
			return	this.set({ dw: w, dh: h || w, resize: strategy });
1886
		},
1887
1888
		preview: function (w, h){
1889
			return	this.resize(w, h || w, 'preview');
1890
		},
1891
1892
		rotate: function (deg){
1893
			return	this.set({ deg: deg });
1894
		},
1895
1896
		filter: function (filter){
1897
			return	this.set({ filter: filter });
1898
		},
1899
1900
		overlay: function (images){
1901
			return	this.set({ overlay: images });
1902
		},
1903
1904
		clone: function (){
1905
			return	new Image(this);
1906
		},
1907
1908
		_load: function (image, fn){
1909
			var self = this;
1910
1911
			if( /img|video/i.test(image.nodeName) ){
1912
				fn.call(self, null, image);
1913
			}
1914
			else {
1915
				api.readAsImage(image, function (evt){
1916
					fn.call(self, evt.type != 'load', evt.result);
1917
				});
1918
			}
1919
		},
1920
1921
		_apply: function (image, fn){
1922
			var
1923
				  canvas = getCanvas()
1924
				, m = this.getMatrix(image)
1925
				, ctx = canvas.getContext('2d')
1926
				, width = image.videoWidth || image.width
1927
				, height = image.videoHeight || image.height
1928
				, deg = m.deg
1929
				, dw = m.dw
1930
				, dh = m.dh
1931
				, w = width
1932
				, h = height
1933
				, filter = m.filter
1934
				, copy // canvas copy
1935
				, buffer = image
1936
				, overlay = m.overlay
1937
				, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1938
				, renderImageToCanvas = api.renderImageToCanvas
1939
			;
1940
1941
			// Normalize angle
1942
			deg = deg - Math.floor(deg/360)*360;
1943
1944
			// For `renderImageToCanvas`
1945
			image._type = this.file.type;
1946
1947
			while(m.multipass && min(w/dw, h/dh) > 2 ){
1948
				w = (w/2 + 0.5)|0;
1949
				h = (h/2 + 0.5)|0;
1950
1951
				copy = getCanvas();
1952
				copy.width  = w;
1953
				copy.height = h;
1954
1955
				if( buffer !== image ){
1956
					renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1957
					buffer = copy;
1958
				}
1959
				else {
1960
					buffer = copy;
1961
					renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1962
					m.sx = m.sy = m.sw = m.sh = 0;
1963
				}
1964
			}
1965
1966
1967
			canvas.width  = (deg % 180) ? dh : dw;
1968
			canvas.height = (deg % 180) ? dw : dh;
1969
1970
			canvas.type = m.type;
1971
			canvas.quality = m.quality;
1972
1973
			ctx.rotate(deg * Math.PI / 180);
1974
			renderImageToCanvas(ctx.canvas, buffer
1975
				, m.sx, m.sy
1976
				, m.sw || buffer.width
1977
				, m.sh || buffer.height
1978
				, (deg == 180 || deg == 270 ? -dw : 0)
1979
				, (deg == 90 || deg == 180 ? -dh : 0)
1980
				, dw, dh
1981
			);
1982
			dw = canvas.width;
1983
			dh = canvas.height;
1984
1985
			// Apply overlay
1986
			overlay && api.each([].concat(overlay), function (over){
1987
				queue.inc();
1988
				// preload
1989
				var img = new window.Image, fn = function (){
1990
					var
1991
						  x = over.x|0
1992
						, y = over.y|0
1993
						, w = over.w || img.width
1994
						, h = over.h || img.height
1995
						, rel = over.rel
1996
					;
1997
1998
					// center  |  right  |  left
1999
					x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
2000
2001
					// center  |  bottom  |  top
2002
					y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
2003
2004
					api.event.off(img, 'error load abort', fn);
2005
2006
					try {
2007
						ctx.globalAlpha = over.opacity || 1;
2008
						ctx.drawImage(img, x, y, w, h);
2009
					}
2010
					catch (er){}
2011
2012
					queue.next();
2013
				};
2014
2015
				api.event.on(img, 'error load abort', fn);
2016
				img.src = over.src;
2017
2018
				if( img.complete ){
2019
					fn();
2020
				}
2021
			});
2022
2023
			if( filter ){
2024
				queue.inc();
2025
				Image.applyFilter(canvas, filter, queue.next);
2026
			}
2027
2028
			queue.check();
2029
		},
2030
2031
		getMatrix: function (image){
2032
			var
2033
				  m  = api.extend({}, this.matrix)
2034
				, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width
2035
				, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2036
				, dw = m.dw = m.dw || sw
2037
				, dh = m.dh = m.dh || sh
2038
				, sf = sw/sh, df = dw/dh
2039
				, strategy = m.resize
2040
			;
2041
2042
			if( strategy == 'preview' ){
2043
				if( dw != sw || dh != sh ){
2044
					// Make preview
2045
					var w, h;
2046
2047
					if( df >= sf ){
2048
						w	= sw;
2049
						h	= w / df;
2050
					} else {
2051
						h	= sh;
2052
						w	= h * df;
2053
					}
2054
2055
					if( w != sw || h != sh ){
2056
						m.sx	= ~~((sw - w)/2);
2057
						m.sy	= ~~((sh - h)/2);
2058
						sw		= w;
2059
						sh		= h;
2060
					}
2061
				}
2062
			}
2063
			else if( strategy ){
2064
				if( !(sw > dw || sh > dh) ){
2065
					dw = sw;
2066
					dh = sh;
2067
				}
2068
				else if( strategy == 'min' ){
2069
					dw = round(sf < df ? min(sw, dw) : dh*sf);
2070
					dh = round(sf < df ? dw/sf : min(sh, dh));
2071
				}
2072
				else {
2073
					dw = round(sf >= df ? min(sw, dw) : dh*sf);
2074
					dh = round(sf >= df ? dw/sf : min(sh, dh));
2075
				}
2076
			}
2077
2078
			m.sw = sw;
2079
			m.sh = sh;
2080
			m.dw = dw;
2081
			m.dh = dh;
2082
			m.multipass = api.multiPassResize;
2083
			return	m;
2084
		},
2085
2086
		_trans: function (fn){
2087
			this._load(this.file, function (err, image){
2088
				if( err ){
2089
					fn(err);
2090
				}
2091
				else {
2092
					try {
2093
						this._apply(image, fn);
2094
					} catch (err){
2095
						api.log('[err] FileAPI.Image.fn._apply:', err);
2096
						fn(err);
2097
					}
2098
				}
2099
			});
2100
		},
2101
2102
2103
		get: function (fn){
2104
			if( api.support.transform ){
2105
				var _this = this, matrix = _this.matrix;
2106
2107
				if( matrix.deg == 'auto' ){
2108
					api.getInfo(_this.file, function (err, info){
2109
						// rotate by exif orientation
2110
						matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2111
						_this._trans(fn);
2112
					});
2113
				}
2114
				else {
2115
					_this._trans(fn);
2116
				}
2117
			}
2118
			else {
2119
				fn('not_support_transform');
2120
			}
2121
2122
			return this;
2123
		},
2124
2125
2126
		toData: function (fn){
2127
			return this.get(fn);
2128
		}
2129
2130
	};
2131
2132
2133
	Image.exifOrientation = exifOrientation;
2134
2135
2136
	Image.transform = function (file, transform, autoOrientation, fn){
2137
		function _transform(err, img){
2138
			// img -- info object
2139
			var
2140
				  images = {}
2141
				, queue = api.queue(function (err){
2142
					fn(err, images);
2143
				})
2144
			;
2145
2146
			if( !err ){
2147
				api.each(transform, function (params, name){
2148
					if( !queue.isFail() ){
2149
						var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2150
2151
						if( isFn ){
2152
							params(img, ImgTrans);
2153
						}
2154
						else if( params.width ){
2155
							ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2156
						}
2157
						else {
2158
							if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2159
								ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2160
							}
2161
						}
2162
2163
						if( params.crop ){
2164
							var crop = params.crop;
2165
							ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2166
						}
2167
2168
						if( params.rotate === undef && autoOrientation ){
2169
							params.rotate = 'auto';
2170
						}
2171
2172
						ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2173
2174
						if( !isFn ){
2175
							ImgTrans.set({
2176
								  deg: params.rotate
2177
								, overlay: params.overlay
2178
								, filter: params.filter
2179
								, quality: params.quality || 1
2180
							});
2181
						}
2182
2183
						queue.inc();
2184
						ImgTrans.toData(function (err, image){
2185
							if( err ){
2186
								queue.fail();
2187
							}
2188
							else {
2189
								images[name] = image;
2190
								queue.next();
2191
							}
2192
						});
2193
					}
2194
				});
2195
			}
2196
			else {
2197
				queue.fail();
2198
			}
2199
		}
2200
2201
2202
		// @todo: Оло-ло, нужно рефакторить это место
2203
		if( file.width ){
2204
			_transform(false, file);
2205
		} else {
2206
			api.getInfo(file, _transform);
2207
		}
2208
	};
2209
2210
2211
	// @const
2212
	api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2213
		api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2214
			Image[x+'_'+y] = i*3 + j;
2215
			Image[y+'_'+x] = i*3 + j;
2216
		});
2217
	});
2218
2219
2220
	/**
2221
	 * Trabsform element to canvas
2222
	 *
2223
	 * @param    {Image|HTMLVideoElement}   el
2224
	 * @returns  {Canvas}
2225
	 */
2226
	Image.toCanvas = function(el){
2227
		var canvas		= document.createElement('canvas');
2228
		canvas.width	= el.videoWidth || el.width;
2229
		canvas.height	= el.videoHeight || el.height;
2230
		canvas.getContext('2d').drawImage(el, 0, 0);
2231
		return	canvas;
2232
	};
2233
2234
2235
	/**
2236
	 * Create image from DataURL
2237
	 * @param  {String}  dataURL
2238
	 * @param  {Object}  size
2239
	 * @param  {Function}  callback
2240
	 */
2241
	Image.fromDataURL = function (dataURL, size, callback){
2242
		var img = api.newImage(dataURL);
2243
		api.extend(img, size);
2244
		callback(img);
2245
	};
2246
2247
2248
	/**
2249
	 * Apply filter (caman.js)
2250
	 *
2251
	 * @param  {Canvas|Image}   canvas
2252
	 * @param  {String|Function}  filter
2253
	 * @param  {Function}  doneFn
2254
	 */
2255
	Image.applyFilter = function (canvas, filter, doneFn){
2256
		if( typeof filter == 'function' ){
2257
			filter(canvas, doneFn);
2258
		}
2259
		else if( window.Caman ){
2260
			// http://camanjs.com/guides/
2261
			window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2262
				if( typeof filter == 'string' ){
2263
					this[filter]();
2264
				}
2265
				else {
2266
					api.each(filter, function (val, method){
2267
						this[method](val);
2268
					}, this);
2269
				}
2270
				this.render(doneFn);
2271
			});
2272
		}
2273
	};
2274
2275
2276
	/**
2277
	 * For load-image-ios.js
2278
	 */
2279
	api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2280
		try {
2281
			return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2282
		} catch (ex) {
2283
			api.log('renderImageToCanvas failed');
2284
			throw ex;
2285
		}
2286
	};
2287
2288
2289
	// @export
2290
	api.support.canvas = api.support.transform = support;
2291
	api.Image = Image;
2292
})(FileAPI, document);
2293
2294
/*
2295
 * JavaScript Load Image iOS scaling fixes 1.0.3
2296
 * https://github.com/blueimp/JavaScript-Load-Image
2297
 *
2298
 * Copyright 2013, Sebastian Tschan
2299
 * https://blueimp.net
2300
 *
2301
 * iOS image scaling fixes based on
2302
 * https://github.com/stomita/ios-imagefile-megapixel
2303
 *
2304
 * Licensed under the MIT license:
2305
 * http://www.opensource.org/licenses/MIT
2306
 */
2307
2308
/*jslint nomen: true, bitwise: true */
2309
/*global FileAPI, window, document */
2310
2311
(function (factory) {
2312
	'use strict';
2313
	factory(FileAPI);
2314
}(function (loadImage) {
2315
    'use strict';
2316
2317
    // Only apply fixes on the iOS platform:
2318
    if (!window.navigator || !window.navigator.platform ||
2319
             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2320
        return;
2321
    }
2322
2323
    var originalRenderMethod = loadImage.renderImageToCanvas;
2324
2325
    // Detects subsampling in JPEG images:
2326
    loadImage.detectSubsampling = function (img) {
2327
        var canvas,
2328
            context;
2329
        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2330
            canvas = document.createElement('canvas');
2331
            canvas.width = canvas.height = 1;
2332
            context = canvas.getContext('2d');
2333
            context.drawImage(img, -img.width + 1, 0);
2334
            // subsampled image becomes half smaller in rendering size.
2335
            // check alpha channel value to confirm image is covering edge pixel or not.
2336
            // if alpha value is 0 image is not covering, hence subsampled.
2337
            return context.getImageData(0, 0, 1, 1).data[3] === 0;
2338
        }
2339
        return false;
2340
    };
2341
2342
    // Detects vertical squash in JPEG images:
2343
    loadImage.detectVerticalSquash = function (img, subsampled) {
2344
        var naturalHeight = img.naturalHeight || img.height,
2345
            canvas = document.createElement('canvas'),
2346
            context = canvas.getContext('2d'),
2347
            data,
2348
            sy,
2349
            ey,
2350
            py,
2351
            alpha;
2352
        if (subsampled) {
2353
            naturalHeight /= 2;
2354
        }
2355
        canvas.width = 1;
2356
        canvas.height = naturalHeight;
2357
        context.drawImage(img, 0, 0);
2358
        data = context.getImageData(0, 0, 1, naturalHeight).data;
2359
        // search image edge pixel position in case it is squashed vertically:
2360
        sy = 0;
2361
        ey = naturalHeight;
2362
        py = naturalHeight;
2363
        while (py > sy) {
2364
            alpha = data[(py - 1) * 4 + 3];
2365
            if (alpha === 0) {
2366
                ey = py;
2367
            } else {
2368
                sy = py;
2369
            }
2370
            py = (ey + sy) >> 1;
2371
        }
2372
        return (py / naturalHeight) || 1;
2373
    };
2374
2375
    // Renders image to canvas while working around iOS image scaling bugs:
2376
    // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2377
    loadImage.renderImageToCanvas = function (
2378
        canvas,
2379
        img,
2380
        sourceX,
2381
        sourceY,
2382
        sourceWidth,
2383
        sourceHeight,
2384
        destX,
2385
        destY,
2386
        destWidth,
2387
        destHeight
2388
    ) {
2389
        if (img._type === 'image/jpeg') {
2390
            var context = canvas.getContext('2d'),
2391
                tmpCanvas = document.createElement('canvas'),
2392
                tileSize = 1024,
2393
                tmpContext = tmpCanvas.getContext('2d'),
2394
                subsampled,
2395
                vertSquashRatio,
2396
                tileX,
2397
                tileY;
2398
            tmpCanvas.width = tileSize;
2399
            tmpCanvas.height = tileSize;
2400
            context.save();
2401
            subsampled = loadImage.detectSubsampling(img);
2402
            if (subsampled) {
2403
                sourceX /= 2;
2404
                sourceY /= 2;
2405
                sourceWidth /= 2;
2406
                sourceHeight /= 2;
2407
            }
2408
            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2409
            if (subsampled || vertSquashRatio !== 1) {
2410
                sourceY *= vertSquashRatio;
2411
                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2412
                destHeight = Math.ceil(
2413
                    tileSize * destHeight / sourceHeight / vertSquashRatio
2414
                );
2415
                destY = 0;
2416
                tileY = 0;
2417
                while (tileY < sourceHeight) {
2418
                    destX = 0;
2419
                    tileX = 0;
2420
                    while (tileX < sourceWidth) {
2421
                        tmpContext.clearRect(0, 0, tileSize, tileSize);
2422
                        tmpContext.drawImage(
2423
                            img,
2424
                            sourceX,
2425
                            sourceY,
2426
                            sourceWidth,
2427
                            sourceHeight,
2428
                            -tileX,
2429
                            -tileY,
2430
                            sourceWidth,
2431
                            sourceHeight
2432
                        );
2433
                        context.drawImage(
2434
                            tmpCanvas,
2435
                            0,
2436
                            0,
2437
                            tileSize,
2438
                            tileSize,
2439
                            destX,
2440
                            destY,
2441
                            destWidth,
2442
                            destHeight
2443
                        );
2444
                        tileX += tileSize;
2445
                        destX += destWidth;
2446
                    }
2447
                    tileY += tileSize;
2448
                    destY += destHeight;
2449
                }
2450
                context.restore();
2451
                return canvas;
2452
            }
2453
        }
2454
        return originalRenderMethod(
2455
            canvas,
2456
            img,
2457
            sourceX,
2458
            sourceY,
2459
            sourceWidth,
2460
            sourceHeight,
2461
            destX,
2462
            destY,
2463
            destWidth,
2464
            destHeight
2465
        );
2466
    };
2467
2468
}));
2469
2470
/*global window, FileAPI */
2471
2472
(function (api, window){
2473
	"use strict";
2474
2475
	var
2476
		  document = window.document
2477
		, FormData = window.FormData
2478
		, Form = function (){ this.items = []; }
2479
		, encodeURIComponent = window.encodeURIComponent
2480
	;
2481
2482
2483
	Form.prototype = {
2484
2485
		append: function (name, blob, file, type){
2486
			this.items.push({
2487
				  name: name
2488
				, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2489
				, file: blob && (file || blob.name)
2490
				, type:	blob && (type || blob.type)
2491
			});
2492
		},
2493
2494
		each: function (fn){
2495
			var i = 0, n = this.items.length;
2496
			for( ; i < n; i++ ){
2497
				fn.call(this, this.items[i]);
2498
			}
2499
		},
2500
2501
		toData: function (fn, options){
2502
		    // allow chunked transfer if we have only one file to send
2503
		    // flag is used below and in XHR._send
2504
		    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2505
2506
			if( !api.support.html5 ){
2507
				api.log('FileAPI.Form.toHtmlData');
2508
				this.toHtmlData(fn);
2509
			}
2510
			else if( !api.formData || this.multipart || !FormData ){
2511
				api.log('FileAPI.Form.toMultipartData');
2512
				this.toMultipartData(fn);
2513
			}
2514
			else if( options._chunked ){
2515
				api.log('FileAPI.Form.toPlainData');
2516
				this.toPlainData(fn);
2517
			}
2518
			else {
2519
				api.log('FileAPI.Form.toFormData');
2520
				this.toFormData(fn);
2521
			}
2522
		},
2523
2524
		_to: function (data, complete, next, arg){
2525
			var queue = api.queue(function (){
2526
				complete(data);
2527
			});
2528
2529
			this.each(function (file){
2530
				next(file, data, queue, arg);
2531
			});
2532
2533
			queue.check();
2534
		},
2535
2536
2537
		toHtmlData: function (fn){
2538
			this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2539
				var blob = file.blob, hidden;
2540
2541
				if( file.file ){
2542
					api.reset(blob, true);
2543
					// set new name
2544
					blob.name = file.name;
2545
					blob.disabled = false;
2546
					data.appendChild(blob);
2547
				}
2548
				else {
2549
					hidden = document.createElement('input');
2550
					hidden.name  = file.name;
2551
					hidden.type  = 'hidden';
2552
					hidden.value = blob;
2553
					data.appendChild(hidden);
2554
				}
2555
			});
2556
		},
2557
2558
		toPlainData: function (fn){
2559
			this._to({}, fn, function (file, data, queue){
2560
				if( file.file ){
2561
					data.type = file.file;
2562
				}
2563
2564
				if( file.blob.toBlob ){
2565
				    // canvas
2566
					queue.inc();
2567
					_convertFile(file, function (file, blob){
2568
						data.name = file.name;
2569
						data.file = blob;
2570
						data.size = blob.length;
2571
						data.type = file.type;
2572
						queue.next();
2573
					});
2574
				}
2575
				else if( file.file ){
2576
				    // file
2577
					data.name = file.blob.name;
2578
					data.file = file.blob;
2579
					data.size = file.blob.size;
2580
					data.type = file.type;
2581
				}
2582
				else {
2583
				    // additional data
2584
				    if( !data.params ){
2585
				        data.params = [];
2586
				    }
2587
				    data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2588
				}
2589
2590
				data.start = -1;
2591
				data.end = data.file && data.file.FileAPIReadPosition || -1;
2592
				data.retry = 0;
2593
			});
2594
		},
2595
2596
		toFormData: function (fn){
2597
			this._to(new FormData, fn, function (file, data, queue){
2598
				if( file.blob && file.blob.toBlob ){
2599
					queue.inc();
2600
					_convertFile(file, function (file, blob){
2601
						data.append(file.name, blob, file.file);
2602
						queue.next();
2603
					});
2604
				}
2605
				else if( file.file ){
2606
					data.append(file.name, file.blob, file.file);
2607
				}
2608
				else {
2609
					data.append(file.name, file.blob);
2610
				}
2611
2612
				if( file.file ){
2613
					data.append('_'+file.name, file.file);
2614
				}
2615
			});
2616
		},
2617
2618
2619
		toMultipartData: function (fn){
2620
			this._to([], fn, function (file, data, queue, boundary){
2621
				queue.inc();
2622
				_convertFile(file, function (file, blob){
2623
					data.push(
2624
						  '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2625
						+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2626
						+ '\r\n'
2627
						+ '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2628
						+ '\r\n')
2629
					);
2630
					queue.next();
2631
				}, true);
2632
			}, api.expando);
2633
		}
2634
	};
2635
2636
2637
	function _convertFile(file, fn, useBinaryString){
2638
		var blob = file.blob, filename = file.file;
2639
2640
		if( filename ){
2641
			if( !blob.toDataURL ){
2642
				// The Blob is not an image.
2643
				api.readAsBinaryString(blob, function (evt){
2644
					if( evt.type == 'load' ){
2645
						fn(file, evt.result);
2646
					}
2647
				});
2648
				return;
2649
			}
2650
2651
			var
2652
				  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2653
				, type = mime[file.type] ? file.type : 'image/png'
2654
				, ext  = mime[type] || '.png'
2655
				, quality = blob.quality || 1
2656
			;
2657
2658
			if( !filename.match(new RegExp(ext+'$', 'i')) ){
2659
				// Does not change the current extension, but add a new one.
2660
				filename += ext.replace('?', '');
2661
			}
2662
2663
			file.file = filename;
2664
			file.type = type;
2665
2666
			if( !useBinaryString && blob.toBlob ){
2667
				blob.toBlob(function (blob){
2668
					fn(file, blob);
2669
				}, type, quality);
2670
			}
2671
			else {
2672
				fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2673
			}
2674
		}
2675
		else {
2676
			fn(file, blob);
2677
		}
2678
	}
2679
2680
2681
	// @export
2682
	api.Form = Form;
2683
})(FileAPI, window);
2684
2685
/*global window, FileAPI, Uint8Array */
2686
2687
(function (window, api){
2688
	"use strict";
2689
2690
	var
2691
		  noop = function (){}
2692
		, document = window.document
2693
2694
		, XHR = function (options){
2695
			this.uid = api.uid();
2696
			this.xhr = {
2697
				  abort: noop
2698
				, getResponseHeader: noop
2699
				, getAllResponseHeaders: noop
2700
			};
2701
			this.options = options;
2702
		},
2703
2704
		_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2705
	;
2706
2707
2708
	XHR.prototype = {
2709
		status: 0,
2710
		statusText: '',
2711
		constructor: XHR,
2712
2713
		getResponseHeader: function (name){
2714
			return this.xhr.getResponseHeader(name);
2715
		},
2716
2717
		getAllResponseHeaders: function (){
2718
			return this.xhr.getAllResponseHeaders() || {};
2719
		},
2720
2721
		end: function (status, statusText){
2722
			var _this = this, options = _this.options;
2723
2724
			_this.end		=
2725
			_this.abort		= noop;
2726
			_this.status	= status;
2727
2728
			if( statusText ){
2729
				_this.statusText = statusText;
2730
			}
2731
2732
			api.log('xhr.end:', status, statusText);
2733
			options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2734
2735
			if( _this.xhr && _this.xhr.node ){
2736
				setTimeout(function (){
2737
					var node = _this.xhr.node;
2738
					try { node.parentNode.removeChild(node); } catch (e){}
2739
					try { delete window[_this.uid]; } catch (e){}
2740
					window[_this.uid] = _this.xhr.node = null;
2741
				}, 9);
2742
			}
2743
		},
2744
2745
		abort: function (){
2746
			this.end(0, 'abort');
2747
2748
			if( this.xhr ){
2749
				this.xhr.aborted = true;
2750
				this.xhr.abort();
2751
			}
2752
		},
2753
2754
		send: function (FormData){
2755
			var _this = this, options = this.options;
2756
2757
			FormData.toData(function (data){
2758
				// Start uploading
2759
				options.upload(options, _this);
2760
				_this._send.call(_this, options, data);
2761
			}, options);
2762
		},
2763
2764
		_send: function (options, data){
2765
			var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2766
2767
			api.log('XHR._send:', data);
2768
2769
			if( !options.cache ){
2770
				// No cache
2771
				url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2772
			}
2773
2774
			if( data.nodeName ){
2775
				var jsonp = options.jsonp;
2776
2777
				// prepare callback in GET
2778
				url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2779
2780
				// legacy
2781
				options.upload(options, _this);
2782
2783
				var
2784
					onPostMessage = function (evt){
2785
						if( ~url.indexOf(evt.origin) ){
2786
							try {
2787
								var result = api.parseJSON(evt.data);
2788
								if( result.id == uid ){
2789
									complete(result.status, result.statusText, result.response);
2790
								}
2791
							} catch( err ){
2792
								complete(0, err.message);
2793
							}
2794
						}
2795
					},
2796
2797
					// jsonp-callack
2798
					complete = window[uid] = function (status, statusText, response){
2799
						_this.readyState	= 4;
2800
						_this.responseText	= response;
2801
						_this.end(status, statusText);
2802
2803
						api.event.off(window, 'message', onPostMessage);
2804
						window[uid] = xhr = transport = window[onloadFuncName] = null;
2805
					}
2806
				;
2807
2808
				_this.xhr.abort = function (){
2809
					try {
2810
						if( transport.stop ){ transport.stop(); }
2811
						else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2812
						else { transport.contentWindow.document.execCommand('Stop'); }
2813
					}
2814
					catch (er) {}
2815
					complete(0, "abort");
2816
				};
2817
2818
				api.event.on(window, 'message', onPostMessage);
2819
2820
				window[onloadFuncName] = function (){
2821
					try {
2822
						var
2823
							  win = transport.contentWindow
2824
							, doc = win.document
2825
							, result = win.result || api.parseJSON(doc.body.innerHTML)
2826
						;
2827
						complete(result.status, result.statusText, result.response);
2828
					} catch (e){
2829
						api.log('[transport.onload]', e);
2830
					}
2831
				};
2832
2833
				xhr = document.createElement('div');
2834
				xhr.innerHTML = '<form target="'+ uid +'" action="../../../ng-file-upload-12.2.12/src/'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2835
							+ '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2836
							+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2837
							+ '</form>'
2838
				;
2839
2840
				// get form-data & transport
2841
				var
2842
					  form = xhr.getElementsByTagName('form')[0]
2843
					, transport = xhr.getElementsByTagName('iframe')[0]
2844
				;
2845
2846
				form.appendChild(data);
2847
2848
				api.log(form.parentNode.innerHTML);
2849
2850
				// append to DOM
2851
				document.body.appendChild(xhr);
2852
2853
				// keep a reference to node-transport
2854
				_this.xhr.node = xhr;
2855
2856
				// send
2857
				_this.readyState = 2; // loaded
2858
				form.submit();
2859
				form = null;
2860
			}
2861
			else {
2862
				// Clean url
2863
				url = url.replace(/([a-z]+)=(\?)&?/i, '');
2864
2865
				// html5
2866
				if (this.xhr && this.xhr.aborted) {
2867
					api.log("Error: already aborted");
2868
					return;
2869
				}
2870
				xhr = _this.xhr = api.getXHR();
2871
2872
				if (data.params) {
2873
					url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2874
				}
2875
2876
				xhr.open('POST', url, true);
2877
2878
				if( api.withCredentials ){
2879
					xhr.withCredentials = "true";
2880
				}
2881
2882
				if( !options.headers || !options.headers['X-Requested-With'] ){
2883
					xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2884
				}
2885
2886
				api.each(options.headers, function (val, key){
2887
					xhr.setRequestHeader(key, val);
2888
				});
2889
2890
2891
				if ( options._chunked ) {
2892
					// chunked upload
2893
					if( xhr.upload ){
2894
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2895
							if (!data.retry) {
2896
								// show progress only for correct chunk uploads
2897
								options.progress({
2898
									  type:			evt.type
2899
									, total:		data.size
2900
									, loaded:		data.start + evt.loaded
2901
									, totalSize:	data.size
2902
								}, _this, options);
2903
							}
2904
						}, 100), false);
2905
					}
2906
2907
					xhr.onreadystatechange = function (){
2908
						var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2909
2910
						_this.status     = xhr.status;
2911
						_this.statusText = xhr.statusText;
2912
						_this.readyState = xhr.readyState;
2913
2914
						if( xhr.readyState == 4 ){
2915
							try {
2916
								for( var k in _xhrResponsePostfix ){
2917
									_this['response'+k]  = xhr['response'+k];
2918
								}
2919
							}catch(_){}
2920
							xhr.onreadystatechange = null;
2921
2922
							if (!xhr.status || xhr.status - 201 > 0) {
2923
								api.log("Error: " + xhr.status);
2924
								// some kind of error
2925
								// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2926
								// up - server error
2927
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2928
									// let's try again the same chunk
2929
									// only applicable for recoverable error codes 500 && 416
2930
									var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2931
2932
									// inform about recoverable problems
2933
									options.pause(data.file, options);
2934
2935
									// smart restart if server reports about the last known byte
2936
									api.log("X-Last-Known-Byte: " + lkb);
2937
									if (lkb) {
2938
										data.end = lkb;
2939
									} else {
2940
										data.end = data.start - 1;
2941
										if (416 == xhr.status) {
2942
											data.end = data.end - options.chunkSize;
2943
										}
2944
									}
2945
2946
									setTimeout(function () {
2947
										_this._send(options, data);
2948
									}, delay);
2949
								} else {
2950
									// no mo retries
2951
									_this.end(xhr.status);
2952
								}
2953
							} else {
2954
								// success
2955
								data.retry = 0;
2956
2957
								if (data.end == data.size - 1) {
2958
									// finished
2959
									_this.end(xhr.status);
2960
								} else {
2961
									// next chunk
2962
2963
									// shift position if server reports about the last known byte
2964
									api.log("X-Last-Known-Byte: " + lkb);
2965
									if (lkb) {
2966
										data.end = lkb;
2967
									}
2968
									data.file.FileAPIReadPosition = data.end;
2969
2970
									setTimeout(function () {
2971
										_this._send(options, data);
2972
									}, 0);
2973
								}
2974
							}
2975
2976
							xhr = null;
2977
						}
2978
					};
2979
2980
					data.start = data.end + 1;
2981
					data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2982
2983
					// Retrieve a slice of file
2984
					var
2985
						  file = data.file
2986
						, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2987
					;
2988
2989
					if( data.size && !slice.size ){
2990
						setTimeout(function (){
2991
							_this.end(-1);
2992
						});
2993
					} else {
2994
						xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2995
						xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2996
						xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2997
2998
						xhr.send(slice);
2999
					}
3000
3001
					file = slice = null;
3002
				} else {
3003
					// single piece upload
3004
					if( xhr.upload ){
3005
						// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3006
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3007
							options.progress(evt, _this, options);
3008
						}, 100), false);
3009
					}
3010
3011
					xhr.onreadystatechange = function (){
3012
						_this.status     = xhr.status;
3013
						_this.statusText = xhr.statusText;
3014
						_this.readyState = xhr.readyState;
3015
3016
						if( xhr.readyState == 4 ){
3017
							for( var k in _xhrResponsePostfix ){
3018
								_this['response'+k]  = xhr['response'+k];
3019
							}
3020
							xhr.onreadystatechange = null;
3021
3022
							if (!xhr.status || xhr.status > 201) {
3023
								api.log("Error: " + xhr.status);
3024
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3025
									options.retry = (options.retry || 0) + 1;
3026
									var delay = api.networkDownRetryTimeout;
3027
3028
									// inform about recoverable problems
3029
									options.pause(options.file, options);
3030
3031
									setTimeout(function () {
3032
										_this._send(options, data);
3033
									}, delay);
3034
								} else {
3035
									//success
3036
									_this.end(xhr.status);
3037
								}
3038
							} else {
3039
								//success
3040
								_this.end(xhr.status);
3041
							}
3042
3043
							xhr = null;
3044
						}
3045
					};
3046
3047
					if( api.isArray(data) ){
3048
						// multipart
3049
						xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3050
						var rawData = data.join('') +'--_'+ api.expando +'--';
3051
3052
						/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3053
						if( xhr.sendAsBinary ){
3054
							xhr.sendAsBinary(rawData);
3055
						}
3056
						else {
3057
							var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3058
							xhr.send(new Uint8Array(bytes).buffer);
3059
3060
						}
3061
					} else {
3062
						// FormData
3063
						xhr.send(data);
3064
					}
3065
				}
3066
			}
3067
		}
3068
	};
3069
3070
3071
	// @export
3072
	api.XHR = XHR;
3073
})(window, FileAPI);
3074
3075
/**
3076
 * @class	FileAPI.Camera
3077
 * @author	RubaXa	<[email protected]>
3078
 * @support	Chrome 21+, FF 18+, Opera 12+
3079
 */
3080
3081
/*global window, FileAPI, jQuery */
3082
/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3083
(function (window, api){
3084
	"use strict";
3085
3086
	var
3087
		URL = window.URL || window.webkitURL,
3088
3089
		document = window.document,
3090
		navigator = window.navigator,
3091
3092
		getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3093
3094
		html5 = !!getMedia
3095
	;
3096
3097
3098
	// Support "media"
3099
	api.support.media = html5;
3100
3101
3102
	var Camera = function (video){
3103
		this.video = video;
3104
	};
3105
3106
3107
	Camera.prototype = {
3108
		isActive: function (){
3109
			return	!!this._active;
3110
		},
3111
3112
3113
		/**
3114
		 * Start camera streaming
3115
		 * @param	{Function}	callback
3116
		 */
3117
		start: function (callback){
3118
			var
3119
				  _this = this
3120
				, video = _this.video
3121
				, _successId
3122
				, _failId
3123
				, _complete = function (err){
3124
					_this._active = !err;
3125
					clearTimeout(_failId);
3126
					clearTimeout(_successId);
3127
//					api.event.off(video, 'loadedmetadata', _complete);
3128
					callback && callback(err, _this);
3129
				}
3130
			;
3131
3132
			getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3133
				// Success
3134
				_this.stream = stream;
3135
3136
//				api.event.on(video, 'loadedmetadata', function (){
3137
//					_complete(null);
3138
//				});
3139
3140
				// Set camera stream
3141
				video.src = URL.createObjectURL(stream);
3142
3143
				// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3144
				// See crbug.com/110938.
3145
				_successId = setInterval(function (){
3146
					if( _detectVideoSignal(video) ){
3147
						_complete(null);
3148
					}
3149
				}, 1000);
3150
3151
				_failId = setTimeout(function (){
3152
					_complete('timeout');
3153
				}, 5000);
3154
3155
				// Go-go-go!
3156
				video.play();
3157
			}, _complete/*error*/);
3158
		},
3159
3160
3161
		/**
3162
		 * Stop camera streaming
3163
		 */
3164
		stop: function (){
3165
			try {
3166
				this._active = false;
3167
				this.video.pause();
3168
				this.stream.stop();
3169
			} catch( err ){ }
3170
		},
3171
3172
3173
		/**
3174
		 * Create screenshot
3175
		 * @return {FileAPI.Camera.Shot}
3176
		 */
3177
		shot: function (){
3178
			return	new Shot(this.video);
3179
		}
3180
	};
3181
3182
3183
	/**
3184
	 * Get camera element from container
3185
	 *
3186
	 * @static
3187
	 * @param	{HTMLElement}	el
3188
	 * @return	{Camera}
3189
	 */
3190
	Camera.get = function (el){
3191
		return	new Camera(el.firstChild);
3192
	};
3193
3194
3195
	/**
3196
	 * Publish camera element into container
3197
	 *
3198
	 * @static
3199
	 * @param	{HTMLElement}	el
3200
	 * @param	{Object}		options
3201
	 * @param	{Function}		[callback]
3202
	 */
3203
	Camera.publish = function (el, options, callback){
3204
		if( typeof options == 'function' ){
3205
			callback = options;
3206
			options = {};
3207
		}
3208
3209
		// Dimensions of "camera"
3210
		options = api.extend({}, {
3211
			  width:	'100%'
3212
			, height:	'100%'
3213
			, start:	true
3214
		}, options);
3215
3216
3217
		if( el.jquery ){
3218
			// Extract first element, from jQuery collection
3219
			el = el[0];
3220
		}
3221
3222
3223
		var doneFn = function (err){
3224
			if( err ){
3225
				callback(err);
3226
			}
3227
			else {
3228
				// Get camera
3229
				var cam = Camera.get(el);
3230
				if( options.start ){
3231
					cam.start(callback);
3232
				}
3233
				else {
3234
					callback(null, cam);
3235
				}
3236
			}
3237
		};
3238
3239
3240
		el.style.width	= _px(options.width);
3241
		el.style.height	= _px(options.height);
3242
3243
3244
		if( api.html5 && html5 ){
3245
			// Create video element
3246
			var video = document.createElement('video');
3247
3248
			// Set dimensions
3249
			video.style.width	= _px(options.width);
3250
			video.style.height	= _px(options.height);
3251
3252
			// Clean container
3253
			if( window.jQuery ){
3254
				jQuery(el).empty();
3255
			} else {
3256
				el.innerHTML = '';
3257
			}
3258
3259
			// Add "camera" to container
3260
			el.appendChild(video);
3261
3262
			// end
3263
			doneFn();
3264
		}
3265
		else {
3266
			Camera.fallback(el, options, doneFn);
3267
		}
3268
	};
3269
3270
3271
	Camera.fallback = function (el, options, callback){
3272
		callback('not_support_camera');
3273
	};
3274
3275
3276
	/**
3277
	 * @class	FileAPI.Camera.Shot
3278
	 */
3279
	var Shot = function (video){
3280
		var canvas	= video.nodeName ? api.Image.toCanvas(video) : video;
3281
		var shot	= api.Image(canvas);
3282
		shot.type	= 'image/png';
3283
		shot.width	= canvas.width;
3284
		shot.height	= canvas.height;
3285
		shot.size	= canvas.width * canvas.height * 4;
3286
		return	shot;
3287
	};
3288
3289
3290
	/**
3291
	 * Add "px" postfix, if value is a number
3292
	 *
3293
	 * @private
3294
	 * @param	{*}  val
3295
	 * @return	{String}
3296
	 */
3297
	function _px(val){
3298
		return	val >= 0 ? val + 'px' : val;
3299
	}
3300
3301
3302
	/**
3303
	 * @private
3304
	 * @param	{HTMLVideoElement} video
3305
	 * @return	{Boolean}
3306
	 */
3307
	function _detectVideoSignal(video){
3308
		var canvas = document.createElement('canvas'), ctx, res = false;
3309
		try {
3310
			ctx = canvas.getContext('2d');
3311
			ctx.drawImage(video, 0, 0, 1, 1);
3312
			res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3313
		}
3314
		catch( e ){}
3315
		return	res;
3316
	}
3317
3318
3319
	// @export
3320
	Camera.Shot	= Shot;
3321
	api.Camera	= Camera;
3322
})(window, FileAPI);
3323
3324
/**
3325
 * FileAPI fallback to Flash
3326
 *
3327
 * @flash-developer  "Vladimir Demidov" <[email protected]>
3328
 */
3329
3330
/*global window, ActiveXObject, FileAPI */
3331
(function (window, jQuery, api) {
3332
	"use strict";
3333
3334
	var
3335
		  document = window.document
3336
		, location = window.location
3337
		, navigator = window.navigator
3338
		, _each = api.each
3339
	;
3340
3341
3342
	api.support.flash = (function (){
3343
		var mime = navigator.mimeTypes, has = false;
3344
3345
		if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3346
			has	= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3347
		}
3348
		else {
3349
			try {
3350
				has	= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3351
			}
3352
			catch(er){
3353
				api.log('Flash -- does not supported.');
3354
			}
3355
		}
3356
3357
		if( has && /^file:/i.test(location) ){
3358
			api.log('[warn] Flash does not work on `file:` protocol.');
3359
		}
3360
3361
		return	has;
3362
	})();
3363
3364
3365
	   api.support.flash
3366
	&& (0
3367
		|| !api.html5 || !api.support.html5
3368
		|| (api.cors && !api.support.cors)
3369
		|| (api.media && !api.support.media)
3370
	)
3371
	&& (function (){
3372
		var
3373
			  _attr  = api.uid()
3374
			, _retry = 0
3375
			, _files = {}
3376
			, _rhttp = /^https?:/i
3377
3378
			, flash = {
3379
				_fn: {},
3380
3381
3382
				/**
3383
				 * Publish flash-object
3384
				 *
3385
				 * @param {HTMLElement} el
3386
				 * @param {String} id
3387
				 * @param {Object} [opts]
3388
				 */
3389
				publish: function (el, id, opts){
3390
					opts = opts || {};
3391
					el.innerHTML = _makeFlashHTML({
3392
						id: id
3393
						, src: _getUrl(api.flashUrl, 'r=' + api.version)
3394
//						, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3395
						, wmode: opts.camera ? '' : 'transparent'
3396
						, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3397
						+ '&flashId='+ id
3398
						+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3399
						+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3400
						+ '&timeout='+api.flashAbortTimeout
3401
						+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3402
						+ '&debug='+(api.debug?"1":"")
3403
					}, opts);
3404
				},
3405
3406
3407
				/**
3408
				 * Initialization & preload flash object
3409
				 */
3410
				init: function (){
3411
					var child = document.body && document.body.firstChild;
3412
3413
					if( child ){
3414
						do {
3415
							if( child.nodeType == 1 ){
3416
								api.log('FlashAPI.state: awaiting');
3417
3418
								var dummy = document.createElement('div');
3419
3420
								dummy.id = '_' + _attr;
3421
3422
								_css(dummy, {
3423
									  top: 1
3424
									, right: 1
3425
									, width: 5
3426
									, height: 5
3427
									, position: 'absolute'
3428
									, zIndex: 1e6+'' // set max zIndex
3429
								});
3430
3431
								child.parentNode.insertBefore(dummy, child);
3432
								flash.publish(dummy, _attr);
3433
3434
								return;
3435
							}
3436
						}
3437
						while( child = child.nextSibling );
3438
					}
3439
3440
					if( _retry < 10 ){
3441
						setTimeout(flash.init, ++_retry*50);
3442
					}
3443
				},
3444
3445
3446
				ready: function (){
3447
					api.log('FlashAPI.state: ready');
3448
3449
					flash.ready = api.F;
3450
					flash.isReady = true;
3451
					flash.patch();
3452
					flash.patchCamera && flash.patchCamera();
3453
					api.event.on(document, 'mouseover', flash.mouseover);
3454
					api.event.on(document, 'click', function (evt){
3455
						if( flash.mouseover(evt) ){
3456
							evt.preventDefault
3457
								? evt.preventDefault()
3458
								: (evt.returnValue = true)
3459
							;
3460
						}
3461
					});
3462
				},
3463
3464
3465
				getEl: function (){
3466
					return	document.getElementById('_'+_attr);
3467
				},
3468
3469
3470
				getWrapper: function (node){
3471
					do {
3472
						if( /js-fileapi-wrapper/.test(node.className) ){
3473
							return	node;
3474
						}
3475
					}
3476
					while( (node = node.parentNode) && (node !== document.body) );
3477
				},
3478
				
3479
				disableMouseover: false,
3480
3481
				mouseover: function (evt){
3482
					if (!flash.disableMouseover) {
3483
						var target = api.event.fix(evt).target;
3484
	
3485
						if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3486
							var
3487
								  state = target.getAttribute(_attr)
3488
								, wrapper = flash.getWrapper(target)
3489
							;
3490
	
3491
							if( api.multiFlash ){
3492
								// check state:
3493
								//   i — published
3494
								//   i — initialization
3495
								//   r — ready
3496
								if( state == 'i' || state == 'r' ){
3497
									// publish fail
3498
									return	false;
3499
								}
3500
								else if( state != 'p' ){
3501
									// set "init" state
3502
									target.setAttribute(_attr, 'i');
3503
	
3504
									var dummy = document.createElement('div');
3505
	
3506
									if( !wrapper ){
3507
										api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3508
										return;
3509
									}
3510
	
3511
									_css(dummy, {
3512
										  top:    0
3513
										, left:   0
3514
										, width:  target.offsetWidth
3515
										, height: target.offsetHeight
3516
										, zIndex: 1e6+'' // set max zIndex
3517
										, position: 'absolute'
3518
									});
3519
	
3520
									wrapper.appendChild(dummy);
3521
									flash.publish(dummy, api.uid());
3522
	
3523
									// set "publish" state
3524
									target.setAttribute(_attr, 'p');
3525
								}
3526
	
3527
								return	true;
3528
							}
3529
							else if( wrapper ){
3530
								// Use one flash element
3531
								var box = _getDimensions(wrapper);
3532
								_css(flash.getEl(), box);
3533
	
3534
								// Set current input
3535
								flash.curInp = target;
3536
							}
3537
						}
3538
						else if( !/object|embed/i.test(target.nodeName) ){
3539
							_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3540
						}
3541
					}
3542
				},
3543
3544
				onEvent: function (evt){
3545
					var type = evt.type;
3546
					
3547
					if( type == 'ready' ){
3548
						try {
3549
							// set "ready" state
3550
							flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3551
						} catch (e){
3552
						}
3553
3554
						flash.ready();
3555
						setTimeout(function (){ flash.mouseenter(evt); }, 50);
3556
						return	true;
3557
					}
3558
					else if( type === 'ping' ){
3559
						api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3560
					}
3561
					else if( type === 'log' ){
3562
						api.log('(flash -> js).log:', evt.target);
3563
					}
3564
					else if( type in flash ){
3565
						setTimeout(function (){
3566
							api.log('FlashAPI.event.'+evt.type+':', evt);
3567
							flash[type](evt);
3568
						}, 1);
3569
					}
3570
				},
3571
				mouseDown: function(evt) {
3572
					flash.disableMouseover = true;
3573
				},
3574
				cancel: function(evt) {
3575
					flash.disableMouseover = false;
3576
				},
3577
				mouseenter: function (evt){
3578
					var node = flash.getInput(evt.flashId);
3579
3580
					if( node ){
3581
						// Set multiple mode
3582
						flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3583
3584
3585
						// Set files filter
3586
						var accept = [], exts = {};
3587
3588
						_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3589
							api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3590
								exts[ext] = 1;
3591
							});
3592
						});
3593
3594
						_each(exts, function (i, ext){
3595
							accept.push( ext );
3596
						});
3597
3598
						flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3599
					}
3600
				},
3601
3602
3603
				get: function (id){
3604
					return	document[id] || window[id] || document.embeds[id];
3605
				},
3606
3607
3608
				getInput: function (id){
3609
					if( api.multiFlash ){
3610
						try {
3611
							var node = flash.getWrapper(flash.get(id));
3612
							if( node ){
3613
								return node.getElementsByTagName('input')[0];
3614
							}
3615
						} catch (e){
3616
							api.log('[err] Can not find "input" by flashId:', id, e);
3617
						}
3618
					} else {
3619
						return	flash.curInp;
3620
					}
3621
				},
3622
3623
3624
				select: function (evt){
3625
					try {
3626
						var
3627
							  inp = flash.getInput(evt.flashId)
3628
							, uid = api.uid(inp)
3629
							, files = evt.target.files
3630
							, event
3631
						;
3632
						_each(files, function (file){
3633
							api.checkFileObj(file);
3634
						});
3635
	
3636
						_files[uid] = files;
3637
	
3638
						if( document.createEvent ){
3639
							event = document.createEvent('Event');
3640
							event.files = files;
3641
							event.initEvent('change', true, true);
3642
							inp.dispatchEvent(event);
3643
						}
3644
						else if( jQuery ){
3645
							jQuery(inp).trigger({ type: 'change', files: files });
3646
						}
3647
						else {
3648
							event = document.createEventObject();
3649
							event.files = files;
3650
							inp.fireEvent('onchange', event);
3651
						}
3652
					} finally {
3653
						flash.disableMouseover = false;
3654
					}
3655
				},
3656
3657
				interval: null,
3658
				cmd: function (id, name, data, last) {
3659
					if (flash.uploadInProgress && flash.readInProgress) {
3660
						setTimeout(function() {
3661
							flash.cmd(id, name, data, last);
3662
						}, 100);
3663
					} else {
3664
						this.cmdFn(id, name, data, last);
3665
					}
3666
				},
3667
				
3668
				cmdFn: function(id, name, data, last) {
3669
					try {
3670
						api.log('(js -> flash).'+name+':', data);
3671
						return flash.get(id.flashId || id).cmd(name, data);
3672
					} catch (e){
3673
						api.log('(js -> flash).onError:', e);
3674
						if( !last ){
3675
							// try again
3676
							setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3677
						}
3678
					}
3679
				},
3680
3681
				patch: function (){
3682
					api.flashEngine = true;
3683
3684
					// FileAPI
3685
					_inherit(api, {
3686
						readAsDataURL: function (file, callback){
3687
							if( _isHtmlFile(file) ){
3688
								this.parent.apply(this, arguments);
3689
							}
3690
							else {
3691
								api.log('FlashAPI.readAsBase64');
3692
								flash.readInProgress = true;
3693
								flash.cmd(file, 'readAsBase64', {
3694
									id: file.id,
3695
									callback: _wrap(function _(err, base64){
3696
										flash.readInProgress = false;
3697
										_unwrap(_);
3698
3699
										api.log('FlashAPI.readAsBase64:', err);
3700
3701
										callback({
3702
											  type: err ? 'error' : 'load'
3703
											, error: err
3704
											, result: 'data:'+ file.type +';base64,'+ base64
3705
										});
3706
									})
3707
								});
3708
							}
3709
						},
3710
3711
						readAsText: function (file, encoding, callback){
3712
							if( callback ){
3713
								api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3714
							} else {
3715
								callback = encoding;
3716
							}
3717
3718
							api.readAsDataURL(file, function (evt){
3719
								if( evt.type == 'load' ){
3720
									try {
3721
										evt.result = window.atob(evt.result.split(';base64,')[1]);
3722
									} catch( err ){
3723
										evt.type = 'error';
3724
										evt.error = err.toString();
3725
									}
3726
								}
3727
								callback(evt);
3728
							});
3729
						},
3730
						
3731
						getFiles: function (input, filter, callback){
3732
							if( callback ){
3733
								api.filterFiles(api.getFiles(input), filter, callback);
3734
								return null;
3735
							}
3736
3737
							var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3738
3739
3740
							if( !files ){
3741
								// Файлов нету, вызываем родительский метод
3742
								return	this.parent.apply(this, arguments);
3743
							}
3744
3745
3746
							if( filter ){
3747
								filter	= api.getFilesFilter(filter);
3748
								files	= api.filter(files, function (file){ return filter.test(file.name); });
3749
							}
3750
3751
							return	files;
3752
						},
3753
3754
3755
						getInfo: function (file, fn){
3756
							if( _isHtmlFile(file) ){
3757
								this.parent.apply(this, arguments);
3758
							}
3759
							else if( file.isShot ){
3760
								fn(null, file.info = {
3761
									width: file.width,
3762
									height: file.height
3763
								});
3764
							}
3765
							else {
3766
								if( !file.__info ){
3767
									var defer = file.__info = api.defer();
3768
3769
//									flash.cmd(file, 'getFileInfo', {
3770
//										  id: file.id
3771
//										, callback: _wrap(function _(err, info){
3772
//											_unwrap(_);
3773
//											defer.resolve(err, file.info = info);
3774
//										})
3775
//									});
3776
									defer.resolve(null, file.info = null);
3777
3778
								}
3779
3780
								file.__info.then(fn);
3781
							}
3782
						}
3783
					});
3784
3785
3786
					// FileAPI.Image
3787
					api.support.transform = true;
3788
					api.Image && _inherit(api.Image.prototype, {
3789
						get: function (fn, scaleMode){
3790
							this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3791
							return this.parent(fn);
3792
						},
3793
3794
						_load: function (file, fn){
3795
							api.log('FlashAPI.Image._load:', file);
3796
3797
							if( _isHtmlFile(file) ){
3798
								this.parent.apply(this, arguments);
3799
							}
3800
							else {
3801
								var _this = this;
3802
								api.getInfo(file, function (err){
3803
									fn.call(_this, err, file);
3804
								});
3805
							}
3806
						},
3807
3808
						_apply: function (file, fn){
3809
							api.log('FlashAPI.Image._apply:', file);
3810
3811
							if( _isHtmlFile(file) ){
3812
								this.parent.apply(this, arguments);
3813
							}
3814
							else {
3815
								var m = this.getMatrix(file.info), doneFn = fn;
3816
3817
								flash.cmd(file, 'imageTransform', {
3818
									  id: file.id
3819
									, matrix: m
3820
									, callback: _wrap(function _(err, base64){
3821
										api.log('FlashAPI.Image._apply.callback:', err);
3822
										_unwrap(_);
3823
3824
										if( err ){
3825
											doneFn(err);
3826
										}
3827
										else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3828
											_makeFlashImage({
3829
												  width:	(m.deg % 180) ? m.dh : m.dw
3830
												, height:	(m.deg % 180) ? m.dw : m.dh
3831
												, scale:	m.scaleMode
3832
											}, base64, doneFn);
3833
										}
3834
										else {
3835
											if( m.filter ){
3836
												doneFn = function (err, img){
3837
													if( err ){
3838
														fn(err);
3839
													}
3840
													else {
3841
														api.Image.applyFilter(img, m.filter, function (){
3842
															fn(err, this.canvas);
3843
														});
3844
													}
3845
												};
3846
											}
3847
3848
											api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3849
										}
3850
									})
3851
								});
3852
							}
3853
						},
3854
3855
						toData: function (fn){
3856
							var
3857
								  file = this.file
3858
								, info = file.info
3859
								, matrix = this.getMatrix(info)
3860
							;
3861
							api.log('FlashAPI.Image.toData');
3862
3863
							if( _isHtmlFile(file) ){
3864
								this.parent.apply(this, arguments);
3865
							}
3866
							else {
3867
								if( matrix.deg == 'auto' ){
3868
									matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3869
								}
3870
3871
								fn.call(this, !file.info, {
3872
									  id:		file.id
3873
									, flashId:	file.flashId
3874
									, name:		file.name
3875
									, type:		file.type
3876
									, matrix:	matrix
3877
								});
3878
							}
3879
						}
3880
					});
3881
3882
3883
					api.Image && _inherit(api.Image, {
3884
						fromDataURL: function (dataURL, size, callback){
3885
							if( !api.support.dataURI || dataURL.length > 3e4 ){
3886
								_makeFlashImage(
3887
									  api.extend({ scale: 'exactFit' }, size)
3888
									, dataURL.replace(/^data:[^,]+,/, '')
3889
									, function (err, el){ callback(el); }
3890
								);
3891
							}
3892
							else {
3893
								this.parent(dataURL, size, callback);
3894
							}
3895
						}
3896
					});
3897
3898
					// FileAPI.Form
3899
					_inherit(api.Form.prototype, {
3900
						toData: function (fn){
3901
							var items = this.items, i = items.length;
3902
3903
							for( ; i--; ){
3904
								if( items[i].file && _isHtmlFile(items[i].blob) ){
3905
									return this.parent.apply(this, arguments);
3906
								}
3907
							}
3908
3909
							api.log('FlashAPI.Form.toData');
3910
							fn(items);
3911
						}
3912
					});
3913
3914
3915
					// FileAPI.XHR
3916
					_inherit(api.XHR.prototype, {
3917
						_send: function (options, formData){
3918
							if(
3919
								   formData.nodeName
3920
								|| formData.append && api.support.html5
3921
								|| api.isArray(formData) && (typeof formData[0] === 'string')
3922
							){
3923
								// HTML5, Multipart or IFrame
3924
								return	this.parent.apply(this, arguments);
3925
							}
3926
3927
3928
							var
3929
								  data = {}
3930
								, files = {}
3931
								, _this = this
3932
								, flashId
3933
								, fileId
3934
							;
3935
3936
							_each(formData, function (item){
3937
								if( item.file ){
3938
									files[item.name] = item = _getFileDescr(item.blob);
3939
									fileId  = item.id;
3940
									flashId = item.flashId;
3941
								}
3942
								else {
3943
									data[item.name] = item.blob;
3944
								}
3945
							});
3946
3947
							if( !fileId ){
3948
								flashId = _attr;
3949
							}
3950
3951
							if( !flashId ){
3952
								api.log('[err] FlashAPI._send: flashId -- undefined');
3953
								return this.parent.apply(this, arguments);
3954
							}
3955
							else {
3956
								api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3957
							}
3958
3959
							_this.xhr = {
3960
								headers: {},
3961
								abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3962
								getResponseHeader: function (name){ return this.headers[name]; },
3963
								getAllResponseHeaders: function (){ return this.headers; }
3964
							};
3965
3966
							var queue = api.queue(function (){
3967
								flash.uploadInProgress = true;
3968
								flash.cmd(flashId, 'upload', {
3969
									  url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3970
									, data: data
3971
									, files: fileId ? files : null
3972
									, headers: options.headers || {}
3973
									, callback: _wrap(function upload(evt){
3974
										var type = evt.type, result = evt.result;
3975
3976
										api.log('FlashAPI.upload.'+type);
3977
3978
										if( type == 'progress' ){
3979
											evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3980
											evt.lengthComputable = true;
3981
											options.progress(evt);
3982
										}
3983
										else if( type == 'complete' ){
3984
											flash.uploadInProgress = false;
3985
											_unwrap(upload);
3986
3987
											if( typeof result == 'string' ){
3988
												_this.responseText	= result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3989
											}
3990
3991
											_this.end(evt.status || 200);
3992
										}
3993
										else if( type == 'abort' || type == 'error' ){
3994
											flash.uploadInProgress = false;
3995
											_this.end(evt.status || 0, evt.message);
3996
											_unwrap(upload);
3997
										}
3998
									})
3999
								});
4000
							});
4001
4002
4003
							// #2174: FileReference.load() call while FileReference.upload() or vice versa
4004
							_each(files, function (file){
4005
								queue.inc();
4006
								api.getInfo(file, queue.next);
4007
							});
4008
4009
							queue.check();
4010
						}
4011
					});
4012
				}
4013
			}
4014
		;
4015
4016
4017
		function _makeFlashHTML(opts){
4018
			return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4019
				+ '<param name="movie" value="#src#" />'
4020
				+ '<param name="flashvars" value="#flashvars#" />'
4021
				+ '<param name="swliveconnect" value="true" />'
4022
				+ '<param name="allowscriptaccess" value="always" />'
4023
				+ '<param name="allownetworking" value="all" />'
4024
				+ '<param name="menu" value="false" />'
4025
				+ '<param name="wmode" value="#wmode#" />'
4026
				+ '<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>'
4027
				+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4028
			;
4029
		}
4030
4031
4032
		function _css(el, css){
4033
			if( el && el.style ){
4034
				var key, val;
4035
				for( key in css ){
4036
					val = css[key];
4037
					if( typeof val == 'number' ){
4038
						val += 'px';
4039
					}
4040
					try { el.style[key] = val; } catch (e) {}
4041
				}
4042
				
4043
			}
4044
		}
4045
4046
4047
		function _inherit(obj, methods){
4048
			_each(methods, function (fn, name){
4049
				var prev = obj[name];
4050
				obj[name] = function (){
4051
					this.parent = prev;
4052
					return fn.apply(this, arguments);
4053
				};
4054
			});
4055
		}
4056
4057
		function _isHtmlFile(file){
4058
			return	file && !file.flashId;
4059
		}
4060
4061
		function _wrap(fn){
4062
			var id = fn.wid = api.uid();
4063
			flash._fn[id] = fn;
4064
			return	'FileAPI.Flash._fn.'+id;
4065
		}
4066
4067
4068
		function _unwrap(fn){
4069
			try {
4070
				flash._fn[fn.wid] = null;
4071
				delete	flash._fn[fn.wid];
4072
			}
4073
			catch(e){}
4074
		}
4075
4076
4077
		function _getUrl(url, params){
4078
			if( !_rhttp.test(url) ){
4079
				if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4080
					var path = location.pathname;
4081
					path = path.substr(0, path.lastIndexOf('/'));
4082
					url = (path +'/'+ url).replace('/./', '/');
4083
				}
4084
4085
				if( '//' != url.substr(0, 2) ){
4086
					url = '//' + location.host + url;
4087
				}
4088
4089
				if( !_rhttp.test(url) ){
4090
					url = location.protocol + url;
4091
				}
4092
			}
4093
4094
			if( params ){
4095
				url += (/\?/.test(url) ? '&' : '?') + params;
4096
			}
4097
4098
			return	url;
4099
		}
4100
4101
4102
		function _makeFlashImage(opts, base64, fn){
4103
			var
4104
				  key
4105
				, flashId = api.uid()
4106
				, el = document.createElement('div')
4107
				, attempts = 10
4108
			;
4109
4110
			for( key in opts ){
4111
				el.setAttribute(key, opts[key]);
4112
				el[key] = opts[key];
4113
			}
4114
4115
			_css(el, opts);
4116
4117
			opts.width	= '100%';
4118
			opts.height	= '100%';
4119
4120
			el.innerHTML = _makeFlashHTML(api.extend({
4121
				  id: flashId
4122
				, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4123
				, wmode: 'opaque'
4124
				, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4125
					_unwrap(_);
4126
					if( --attempts > 0 ){
4127
						_setImage();
4128
					}
4129
					return true;
4130
				})
4131
			}, opts));
4132
4133
			function _setImage(){
4134
				try {
4135
					// Get flash-object by id
4136
					var img = flash.get(flashId);
4137
					img.setImage(base64);
4138
				} catch (e){
4139
					api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4140
				}
4141
			}
4142
4143
			fn(false, el);
4144
			el = null;
4145
		}
4146
4147
4148
		function _getFileDescr(file){
4149
			return	{
4150
				  id: file.id
4151
				, name: file.name
4152
				, matrix: file.matrix
4153
				, flashId: file.flashId
4154
			};
4155
		}
4156
4157
4158
		function _getDimensions(el){
4159
			var
4160
				  box = el.getBoundingClientRect()
4161
				, body = document.body
4162
				, docEl = (el && el.ownerDocument).documentElement
4163
			;
4164
			
4165
			function getOffset(obj) {
4166
			    var left, top;
4167
			    left = top = 0;
4168
			    if (obj.offsetParent) {
4169
			        do {
4170
			            left += obj.offsetLeft;
4171
			            top  += obj.offsetTop;
4172
			        } while (obj = obj.offsetParent);
4173
			    }
4174
			    return {
4175
			        left : left,
4176
			        top : top
4177
			    };
4178
			};
4179
			
4180
			return {
4181
				  top:		getOffset(el).top
4182
				, left:		getOffset(el).left
4183
				, width:	el.offsetWidth
4184
				, height:	el.offsetHeight
4185
			};
4186
		}
4187
4188
		// @export
4189
		api.Flash = flash;
4190
4191
4192
		// Check dataURI support
4193
		api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4194
			api.support.dataURI = !(img.width != 1 || img.height != 1);
4195
			flash.init();
4196
		});
4197
	})();
4198
})(window, window.jQuery, FileAPI);
4199
4200
/**
4201
 * FileAPI fallback to Flash
4202
 *
4203
 * @flash-developer  "Vladimir Demidov" <[email protected]>
4204
 */
4205
4206
/*global window, FileAPI */
4207
(function (window, jQuery, api) {
4208
    "use strict";
4209
4210
    var _each = api.each,
4211
        _cameraQueue = [];
4212
4213
4214
    if (api.support.flash && (api.media && !api.support.media)) {
4215
        (function () {
4216
4217
            function _wrap(fn) {
4218
                var id = fn.wid = api.uid();
4219
                api.Flash._fn[id] = fn;
4220
                return 'FileAPI.Flash._fn.' + id;
4221
            }
4222
4223
4224
            function _unwrap(fn) {
4225
                try {
4226
                    api.Flash._fn[fn.wid] = null;
4227
                    delete api.Flash._fn[fn.wid];
4228
                } catch (e) {
4229
                }
4230
            }
4231
4232
            var flash = api.Flash;
4233
            api.extend(api.Flash, {
4234
4235
                patchCamera: function () {
4236
                    api.Camera.fallback = function (el, options, callback) {
4237
                        var camId = api.uid();
4238
                        api.log('FlashAPI.Camera.publish: ' + camId);
4239
                        flash.publish(el, camId, api.extend(options, {
4240
                            camera: true,
4241
                            onEvent: _wrap(function _(evt) {
4242
                                if (evt.type === 'camera') {
4243
                                    _unwrap(_);
4244
4245
                                    if (evt.error) {
4246
                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4247
                                        callback(evt.error);
4248
                                    } else {
4249
                                        api.log('FlashAPI.Camera.publish.success: ' + camId);
4250
                                        callback(null);
4251
                                    }
4252
                                }
4253
                            })
4254
                        }));
4255
                    };
4256
                    // Run
4257
                    _each(_cameraQueue, function (args) {
4258
                        api.Camera.fallback.apply(api.Camera, args);
4259
                    });
4260
                    _cameraQueue = [];
4261
4262
4263
                    // FileAPI.Camera:proto
4264
                    api.extend(api.Camera.prototype, {
4265
                        _id: function () {
4266
                            return this.video.id;
4267
                        },
4268
4269
                        start: function (callback) {
4270
                            var _this = this;
4271
                            flash.cmd(this._id(), 'camera.on', {
4272
                                callback: _wrap(function _(evt) {
4273
                                    _unwrap(_);
4274
4275
                                    if (evt.error) {
4276
                                        api.log('FlashAPI.camera.on.error: ' + evt.error);
4277
                                        callback(evt.error, _this);
4278
                                    } else {
4279
                                        api.log('FlashAPI.camera.on.success: ' + _this._id());
4280
                                        _this._active = true;
4281
                                        callback(null, _this);
4282
                                    }
4283
                                })
4284
                            });
4285
                        },
4286
4287
                        stop: function () {
4288
                            this._active = false;
4289
                            flash.cmd(this._id(), 'camera.off');
4290
                        },
4291
4292
                        shot: function () {
4293
                            api.log('FlashAPI.Camera.shot:', this._id());
4294
4295
                            var shot = api.Flash.cmd(this._id(), 'shot', {});
4296
                            shot.type = 'image/png';
4297
                            shot.flashId = this._id();
4298
                            shot.isShot = true;
4299
4300
                            return new api.Camera.Shot(shot);
4301
                        }
4302
                    });
4303
                }
4304
            });
4305
4306
            api.Camera.fallback = function () {
4307
                _cameraQueue.push(arguments);
4308
            };
4309
4310
        }());
4311
    }
4312
}(window, window.jQuery, FileAPI));
4313
if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
4314

third-party/angularjs-modules-plugins/ng-file-upload-12.2.12/demo/src/main/webapp/js/FileAPI.js 1 location

@@ 22-4313 (lines=4292) @@
19
/*jslint nomen: true, regexp: true */
20
/*global window, atob, Blob, ArrayBuffer, Uint8Array */
21
22
(function (window) {
23
    'use strict';
24
    var CanvasPrototype = window.HTMLCanvasElement &&
25
            window.HTMLCanvasElement.prototype,
26
        hasBlobConstructor = window.Blob && (function () {
27
            try {
28
                return Boolean(new Blob());
29
            } catch (e) {
30
                return false;
31
            }
32
        }()),
33
        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
34
            (function () {
35
                try {
36
                    return new Blob([new Uint8Array(100)]).size === 100;
37
                } catch (e) {
38
                    return false;
39
                }
40
            }()),
41
        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
42
            window.MozBlobBuilder || window.MSBlobBuilder,
43
        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
44
            window.ArrayBuffer && window.Uint8Array && function (dataURI) {
45
                var byteString,
46
                    arrayBuffer,
47
                    intArray,
48
                    i,
49
                    mimeString,
50
                    bb;
51
                if (dataURI.split(',')[0].indexOf('base64') >= 0) {
52
                    // Convert base64 to raw binary data held in a string:
53
                    byteString = atob(dataURI.split(',')[1]);
54
                } else {
55
                    // Convert base64/URLEncoded data component to raw binary data:
56
                    byteString = decodeURIComponent(dataURI.split(',')[1]);
57
                }
58
                // Write the bytes of the string to an ArrayBuffer:
59
                arrayBuffer = new ArrayBuffer(byteString.length);
60
                intArray = new Uint8Array(arrayBuffer);
61
                for (i = 0; i < byteString.length; i += 1) {
62
                    intArray[i] = byteString.charCodeAt(i);
63
                }
64
                // Separate out the mime component:
65
                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
66
                // Write the ArrayBuffer (or ArrayBufferView) to a blob:
67
                if (hasBlobConstructor) {
68
                    return new Blob(
69
                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],
70
                        {type: mimeString}
71
                    );
72
                }
73
                bb = new BlobBuilder();
74
                bb.append(arrayBuffer);
75
                return bb.getBlob(mimeString);
76
            };
77
    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
78
        if (CanvasPrototype.mozGetAsFile) {
79
            CanvasPrototype.toBlob = function (callback, type, quality) {
80
                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
81
                    callback(dataURLtoBlob(this.toDataURL(type, quality)));
82
                } else {
83
                    callback(this.mozGetAsFile('blob', type));
84
                }
85
            };
86
        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
87
            CanvasPrototype.toBlob = function (callback, type, quality) {
88
                callback(dataURLtoBlob(this.toDataURL(type, quality)));
89
            };
90
        }
91
    }
92
    window.dataURLtoBlob = dataURLtoBlob;
93
})(window);
94
95
/*jslint evil: true */
96
/*global window, URL, webkitURL, ActiveXObject */
97
98
(function (window, undef){
99
	'use strict';
100
101
	var
102
		gid = 1,
103
		noop = function (){},
104
105
		document = window.document,
106
		doctype = document.doctype || {},
107
		userAgent = window.navigator.userAgent,
108
109
		// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
110
		apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
111
112
		Blob = window.Blob,
113
		File = window.File,
114
		FileReader = window.FileReader,
115
		FormData = window.FormData,
116
117
118
		XMLHttpRequest = window.XMLHttpRequest,
119
		jQuery = window.jQuery,
120
121
		html5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
122
				&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
123
124
		cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
125
126
		chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
127
128
		// https://github.com/blueimp/JavaScript-Canvas-to-Blob
129
		dataURLtoBlob = window.dataURLtoBlob,
130
131
132
		_rimg = /img/i,
133
		_rcanvas = /canvas/i,
134
		_rimgcanvas = /img|canvas/i,
135
		_rinput = /input/i,
136
		_rdata = /^data:[^,]+,/,
137
138
		_toString = {}.toString,
139
140
141
		Math = window.Math,
142
143
		_SIZE_CONST = function (pow){
144
			pow = new window.Number(Math.pow(1024, pow));
145
			pow.from = function (sz){ return Math.round(sz * this); };
146
			return	pow;
147
		},
148
149
		_elEvents = {}, // element event listeners
150
		_infoReader = [], // list of file info processors
151
152
		_readerEvents = 'abort progress error load loadend',
153
		_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
154
155
		currentTarget = 'currentTarget', // for minimize
156
		preventDefault = 'preventDefault', // and this too
157
158
		_isArray = function (ar) {
159
			return	ar && ('length' in ar);
160
		},
161
162
		/**
163
		 * Iterate over a object or array
164
		 */
165
		_each = function (obj, fn, ctx){
166
			if( obj ){
167
				if( _isArray(obj) ){
168
					for( var i = 0, n = obj.length; i < n; i++ ){
169
						if( i in obj ){
170
							fn.call(ctx, obj[i], i, obj);
171
						}
172
					}
173
				}
174
				else {
175
					for( var key in obj ){
176
						if( obj.hasOwnProperty(key) ){
177
							fn.call(ctx, obj[key], key, obj);
178
						}
179
					}
180
				}
181
			}
182
		},
183
184
		/**
185
		 * Merge the contents of two or more objects together into the first object
186
		 */
187
		_extend = function (dst){
188
			var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
189
			for( ; i < args.length; i++ ){
190
				_each(args[i], _ext);
191
			}
192
			return  dst;
193
		},
194
195
		/**
196
		 * Add event listener
197
		 */
198
		_on = function (el, type, fn){
199
			if( el ){
200
				var uid = api.uid(el);
201
202
				if( !_elEvents[uid] ){
203
					_elEvents[uid] = {};
204
				}
205
206
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
207
				_each(type.split(/\s+/), function (type){
208
					if( jQuery && !isFileReader){
209
						jQuery.event.add(el, type, fn);
210
					} else {
211
						if( !_elEvents[uid][type] ){
212
							_elEvents[uid][type] = [];
213
						}
214
215
						_elEvents[uid][type].push(fn);
216
217
						if( el.addEventListener ){ el.addEventListener(type, fn, false); }
218
						else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
219
						else { el['on'+type] = fn; }
220
					}
221
				});
222
			}
223
		},
224
225
226
		/**
227
		 * Remove event listener
228
		 */
229
		_off = function (el, type, fn){
230
			if( el ){
231
				var uid = api.uid(el), events = _elEvents[uid] || {};
232
233
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
234
				_each(type.split(/\s+/), function (type){
235
					if( jQuery && !isFileReader){
236
						jQuery.event.remove(el, type, fn);
237
					}
238
					else {
239
						var fns = events[type] || [], i = fns.length;
240
241
						while( i-- ){
242
							if( fns[i] === fn ){
243
								fns.splice(i, 1);
244
								break;
245
							}
246
						}
247
248
						if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
249
						else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
250
						else { el['on'+type] = null; }
251
					}
252
				});
253
			}
254
		},
255
256
257
		_one = function(el, type, fn){
258
			_on(el, type, function _(evt){
259
				_off(el, type, _);
260
				fn(evt);
261
			});
262
		},
263
264
265
		_fixEvent = function (evt){
266
			if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
267
			if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
268
			return  evt;
269
		},
270
271
272
		_supportInputAttr = function (attr){
273
			var input = document.createElement('input');
274
			input.setAttribute('type', "file");
275
			return attr in input;
276
		},
277
278
		/**
279
		 * FileAPI (core object)
280
		 */
281
		api = {
282
			version: '2.0.7',
283
284
			cors: false,
285
			html5: true,
286
			media: false,
287
			formData: true,
288
			multiPassResize: true,
289
290
			debug: false,
291
			pingUrl: false,
292
			multiFlash: false,
293
			flashAbortTimeout: 0,
294
			withCredentials: true,
295
296
			staticPath: './dist/',
297
298
			flashUrl: 0, // @default: './FileAPI.flash.swf'
299
			flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
300
301
			postNameConcat: function (name, idx){
302
				return	name + (idx != null ? '['+ idx +']' : '');
303
			},
304
305
			ext2mime: {
306
				  jpg:	'image/jpeg'
307
				, tif:	'image/tiff'
308
				, txt:	'text/plain'
309
			},
310
311
			// Fallback for flash
312
			accept: {
313
				  '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'
314
				, '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'
315
				, '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'
316
			},
317
318
			uploadRetry : 0,
319
			networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
320
321
			chunkSize : 0,
322
			chunkUploadRetry : 0,
323
			chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
324
325
			KB: _SIZE_CONST(1),
326
			MB: _SIZE_CONST(2),
327
			GB: _SIZE_CONST(3),
328
			TB: _SIZE_CONST(4),
329
330
			EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
331
332
			expando: 'fileapi' + (new Date).getTime(),
333
334
			uid: function (obj){
335
				return	obj
336
					? (obj[api.expando] = obj[api.expando] || api.uid())
337
					: (++gid, api.expando + gid)
338
				;
339
			},
340
341
			log: function (){
342
				// ngf fix for IE8 #1071
343
				if( api.debug && api._supportConsoleLog ){
344
					if( api._supportConsoleLogApply ){
345
						console.log.apply(console, arguments);
346
					}
347
					else {
348
						console.log([].join.call(arguments, ' '));
349
					}
350
				}
351
			},
352
353
			/**
354
			 * Create new image
355
			 *
356
			 * @param {String} [src]
357
			 * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element
358
			 * @returns {HTMLElement}
359
			 */
360
			newImage: function (src, fn){
361
				var img = document.createElement('img');
362
				if( fn ){
363
					api.event.one(img, 'error load', function (evt){
364
						fn(evt.type == 'error', img);
365
						img = null;
366
					});
367
				}
368
				img.src = src;
369
				return	img;
370
			},
371
372
			/**
373
			 * Get XHR
374
			 * @returns {XMLHttpRequest}
375
			 */
376
			getXHR: function (){
377
				var xhr;
378
379
				if( XMLHttpRequest ){
380
					xhr = new XMLHttpRequest;
381
				}
382
				else if( window.ActiveXObject ){
383
					try {
384
						xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
385
					} catch (e) {
386
						xhr = new ActiveXObject('Microsoft.XMLHTTP');
387
					}
388
				}
389
390
				return  xhr;
391
			},
392
393
			isArray: _isArray,
394
395
			support: {
396
				dnd:     cors && ('ondrop' in document.createElement('div')),
397
				cors:    cors,
398
				html5:   html5,
399
				chunked: chunked,
400
				dataURI: true,
401
				accept:   _supportInputAttr('accept'),
402
				multiple: _supportInputAttr('multiple')
403
			},
404
405
			event: {
406
				  on: _on
407
				, off: _off
408
				, one: _one
409
				, fix: _fixEvent
410
			},
411
412
413
			throttle: function(fn, delay) {
414
				var id, args;
415
416
				return function _throttle(){
417
					args = arguments;
418
419
					if( !id ){
420
						fn.apply(window, args);
421
						id = setTimeout(function (){
422
							id = 0;
423
							fn.apply(window, args);
424
						}, delay);
425
					}
426
				};
427
			},
428
429
430
			F: function (){},
431
432
433
			parseJSON: function (str){
434
				var json;
435
				if( window.JSON && JSON.parse ){
436
					json = JSON.parse(str);
437
				}
438
				else {
439
					json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
440
				}
441
				return json;
442
			},
443
444
445
			trim: function (str){
446
				str = String(str);
447
				return	str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
448
			},
449
450
			/**
451
			 * Simple Defer
452
			 * @return	{Object}
453
			 */
454
			defer: function (){
455
				var
456
					  list = []
457
					, result
458
					, error
459
					, defer = {
460
						resolve: function (err, res){
461
							defer.resolve = noop;
462
							error	= err || false;
463
							result	= res;
464
465
							while( res = list.shift() ){
466
								res(error, result);
467
							}
468
						},
469
470
						then: function (fn){
471
							if( error !== undef ){
472
								fn(error, result);
473
							} else {
474
								list.push(fn);
475
							}
476
						}
477
				};
478
479
				return	defer;
480
			},
481
482
			queue: function (fn){
483
				var
484
					  _idx = 0
485
					, _length = 0
486
					, _fail = false
487
					, _end = false
488
					, queue = {
489
						inc: function (){
490
							_length++;
491
						},
492
493
						next: function (){
494
							_idx++;
495
							setTimeout(queue.check, 0);
496
						},
497
498
						check: function (){
499
							(_idx >= _length) && !_fail && queue.end();
500
						},
501
502
						isFail: function (){
503
							return _fail;
504
						},
505
506
						fail: function (){
507
							!_fail && fn(_fail = true);
508
						},
509
510
						end: function (){
511
							if( !_end ){
512
								_end = true;
513
								fn();
514
							}
515
						}
516
					}
517
				;
518
				return queue;
519
			},
520
521
522
			/**
523
			 * For each object
524
			 *
525
			 * @param	{Object|Array}	obj
526
			 * @param	{Function}		fn
527
			 * @param	{*}				[ctx]
528
			 */
529
			each: _each,
530
531
532
			/**
533
			 * Async for
534
			 * @param {Array} array
535
			 * @param {Function} callback
536
			 */
537
			afor: function (array, callback){
538
				var i = 0, n = array.length;
539
540
				if( _isArray(array) && n-- ){
541
					(function _next(){
542
						callback(n != i && _next, array[i], i++);
543
					})();
544
				}
545
				else {
546
					callback(false);
547
				}
548
			},
549
550
551
			/**
552
			 * Merge the contents of two or more objects together into the first object
553
			 *
554
			 * @param	{Object}	dst
555
			 * @return	{Object}
556
			 */
557
			extend: _extend,
558
559
560
			/**
561
			 * Is file?
562
			 * @param  {File}  file
563
			 * @return {Boolean}
564
			 */
565
			isFile: function (file){
566
				return _toString.call(file) === '[object File]';
567
			},
568
569
570
			/**
571
			 * Is blob?
572
			 * @param   {Blob}  blob
573
			 * @returns {Boolean}
574
			 */
575
			isBlob: function (blob) {
576
				return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
577
			},
578
579
580
			/**
581
			 * Is canvas element
582
			 *
583
			 * @param	{HTMLElement}	el
584
			 * @return	{Boolean}
585
			 */
586
			isCanvas: function (el){
587
				return	el && _rcanvas.test(el.nodeName);
588
			},
589
590
591
			getFilesFilter: function (filter){
592
				filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
593
				return	filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
594
			},
595
596
597
598
			/**
599
			 * Read as DataURL
600
			 *
601
			 * @param {File|Element} file
602
			 * @param {Function} fn
603
			 */
604
			readAsDataURL: function (file, fn){
605
				if( api.isCanvas(file) ){
606
					_emit(file, fn, 'load', api.toDataURL(file));
607
				}
608
				else {
609
					_readAs(file, fn, 'DataURL');
610
				}
611
			},
612
613
614
			/**
615
			 * Read as Binary string
616
			 *
617
			 * @param {File} file
618
			 * @param {Function} fn
619
			 */
620
			readAsBinaryString: function (file, fn){
621
				if( _hasSupportReadAs('BinaryString') ){
622
					_readAs(file, fn, 'BinaryString');
623
				} else {
624
					// Hello IE10!
625
					_readAs(file, function (evt){
626
						if( evt.type == 'load' ){
627
							try {
628
								// dataURL -> binaryString
629
								evt.result = api.toBinaryString(evt.result);
630
							} catch (e){
631
								evt.type = 'error';
632
								evt.message = e.toString();
633
							}
634
						}
635
						fn(evt);
636
					}, 'DataURL');
637
				}
638
			},
639
640
641
			/**
642
			 * Read as ArrayBuffer
643
			 *
644
			 * @param {File} file
645
			 * @param {Function} fn
646
			 */
647
			readAsArrayBuffer: function(file, fn){
648
				_readAs(file, fn, 'ArrayBuffer');
649
			},
650
651
652
			/**
653
			 * Read as text
654
			 *
655
			 * @param {File} file
656
			 * @param {String} encoding
657
			 * @param {Function} [fn]
658
			 */
659
			readAsText: function(file, encoding, fn){
660
				if( !fn ){
661
					fn	= encoding;
662
					encoding = 'utf-8';
663
				}
664
665
				_readAs(file, fn, 'Text', encoding);
666
			},
667
668
669
			/**
670
			 * Convert image or canvas to DataURL
671
			 *
672
			 * @param   {Element}  el      Image or Canvas element
673
			 * @param   {String}   [type]  mime-type
674
			 * @return  {String}
675
			 */
676
			toDataURL: function (el, type){
677
				if( typeof el == 'string' ){
678
					return  el;
679
				}
680
				else if( el.toDataURL ){
681
					return  el.toDataURL(type || 'image/png');
682
				}
683
			},
684
685
686
			/**
687
			 * Canvert string, image or canvas to binary string
688
			 *
689
			 * @param   {String|Element} val
690
			 * @return  {String}
691
			 */
692
			toBinaryString: function (val){
693
				return  window.atob(api.toDataURL(val).replace(_rdata, ''));
694
			},
695
696
697
			/**
698
			 * Read file or DataURL as ImageElement
699
			 *
700
			 * @param	{File|String}	file
701
			 * @param	{Function}		fn
702
			 * @param	{Boolean}		[progress]
703
			 */
704
			readAsImage: function (file, fn, progress){
705
				if( api.isFile(file) ){
706
					if( apiURL ){
707
						/** @namespace apiURL.createObjectURL */
708
						var data = apiURL.createObjectURL(file);
709
						if( data === undef ){
710
							_emit(file, fn, 'error');
711
						}
712
						else {
713
							api.readAsImage(data, fn, progress);
714
						}
715
					}
716
					else {
717
						api.readAsDataURL(file, function (evt){
718
							if( evt.type == 'load' ){
719
								api.readAsImage(evt.result, fn, progress);
720
							}
721
							else if( progress || evt.type == 'error' ){
722
								_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
723
							}
724
						});
725
					}
726
				}
727
				else if( api.isCanvas(file) ){
728
					_emit(file, fn, 'load', file);
729
				}
730
				else if( _rimg.test(file.nodeName) ){
731
					if( file.complete ){
732
						_emit(file, fn, 'load', file);
733
					}
734
					else {
735
						var events = 'error abort load';
736
						_one(file, events, function _fn(evt){
737
							if( evt.type == 'load' && apiURL ){
738
								/** @namespace apiURL.revokeObjectURL */
739
								apiURL.revokeObjectURL(file.src);
740
							}
741
742
							_off(file, events, _fn);
743
							_emit(file, fn, evt, file);
744
						});
745
					}
746
				}
747
				else if( file.iframe ){
748
					_emit(file, fn, { type: 'error' });
749
				}
750
				else {
751
					// Created image
752
					var img = api.newImage(file.dataURL || file);
753
					api.readAsImage(img, fn, progress);
754
				}
755
			},
756
757
758
			/**
759
			 * Make file by name
760
			 *
761
			 * @param	{String}	name
762
			 * @return	{Array}
763
			 */
764
			checkFileObj: function (name){
765
				var file = {}, accept = api.accept;
766
767
				if( typeof name == 'object' ){
768
					file = name;
769
				}
770
				else {
771
					file.name = (name + '').split(/\\|\//g).pop();
772
				}
773
774
				if( file.type == null ){
775
					file.type = file.name.split('.').pop();
776
				}
777
778
				_each(accept, function (ext, type){
779
					ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
780
					if( ext.test(file.type) || api.ext2mime[file.type] ){
781
						file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
782
					}
783
				});
784
785
				return	file;
786
			},
787
788
789
			/**
790
			 * Get drop files
791
			 *
792
			 * @param	{Event}	evt
793
			 * @param	{Function} callback
794
			 */
795
			getDropFiles: function (evt, callback){
796
				var
797
					  files = []
798
					, dataTransfer = _getDataTransfer(evt)
799
					, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
800
					, queue = api.queue(function (){ callback(files); })
801
				;
802
803
				_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
804
					queue.inc();
805
806
					try {
807
						if( entrySupport ){
808
							_readEntryAsFiles(item, function (err, entryFiles){
809
								if( err ){
810
									api.log('[err] getDropFiles:', err);
811
								} else {
812
									files.push.apply(files, entryFiles);
813
								}
814
								queue.next();
815
							});
816
						}
817
						else {
818
							_isRegularFile(item, function (yes){
819
								yes && files.push(item);
820
								queue.next();
821
							});
822
						}
823
					}
824
					catch( err ){
825
						queue.next();
826
						api.log('[err] getDropFiles: ', err);
827
					}
828
				});
829
830
				queue.check();
831
			},
832
833
834
			/**
835
			 * Get file list
836
			 *
837
			 * @param	{HTMLInputElement|Event}	input
838
			 * @param	{String|Function}	[filter]
839
			 * @param	{Function}			[callback]
840
			 * @return	{Array|Null}
841
			 */
842
			getFiles: function (input, filter, callback){
843
				var files = [];
844
845
				if( callback ){
846
					api.filterFiles(api.getFiles(input), filter, callback);
847
					return null;
848
				}
849
850
				if( input.jquery ){
851
					// jQuery object
852
					input.each(function (){
853
						files = files.concat(api.getFiles(this));
854
					});
855
					input	= files;
856
					files	= [];
857
				}
858
859
				if( typeof filter == 'string' ){
860
					filter	= api.getFilesFilter(filter);
861
				}
862
863
				if( input.originalEvent ){
864
					// jQuery event
865
					input = _fixEvent(input.originalEvent);
866
				}
867
				else if( input.srcElement ){
868
					// IE Event
869
					input = _fixEvent(input);
870
				}
871
872
873
				if( input.dataTransfer ){
874
					// Drag'n'Drop
875
					input = input.dataTransfer;
876
				}
877
				else if( input.target ){
878
					// Event
879
					input = input.target;
880
				}
881
882
				if( input.files ){
883
					// Input[type="file"]
884
					files = input.files;
885
886
					if( !html5 ){
887
						// Partial support for file api
888
						files[0].blob	= input;
889
						files[0].iframe	= true;
890
					}
891
				}
892
				else if( !html5 && isInputFile(input) ){
893
					if( api.trim(input.value) ){
894
						files = [api.checkFileObj(input.value)];
895
						files[0].blob   = input;
896
						files[0].iframe = true;
897
					}
898
				}
899
				else if( _isArray(input) ){
900
					files	= input;
901
				}
902
903
				return	api.filter(files, function (file){ return !filter || filter.test(file.name); });
904
			},
905
906
907
			/**
908
			 * Get total file size
909
			 * @param	{Array}	files
910
			 * @return	{Number}
911
			 */
912
			getTotalSize: function (files){
913
				var size = 0, i = files && files.length;
914
				while( i-- ){
915
					size += files[i].size;
916
				}
917
				return	size;
918
			},
919
920
921
			/**
922
			 * Get image information
923
			 *
924
			 * @param	{File}		file
925
			 * @param	{Function}	fn
926
			 */
927
			getInfo: function (file, fn){
928
				var info = {}, readers = _infoReader.concat();
929
930
				if( api.isFile(file) ){
931
					(function _next(){
932
						var reader = readers.shift();
933
						if( reader ){
934
							if( reader.test(file.type) ){
935
								reader(file, function (err, res){
936
									if( err ){
937
										fn(err);
938
									}
939
									else {
940
										_extend(info, res);
941
										_next();
942
									}
943
								});
944
							}
945
							else {
946
								_next();
947
							}
948
						}
949
						else {
950
							fn(false, info);
951
						}
952
					})();
953
				}
954
				else {
955
					fn('not_support_info', info);
956
				}
957
			},
958
959
960
			/**
961
			 * Add information reader
962
			 *
963
			 * @param {RegExp} mime
964
			 * @param {Function} fn
965
			 */
966
			addInfoReader: function (mime, fn){
967
				fn.test = function (type){ return mime.test(type); };
968
				_infoReader.push(fn);
969
			},
970
971
972
			/**
973
			 * Filter of array
974
			 *
975
			 * @param	{Array}		input
976
			 * @param	{Function}	fn
977
			 * @return	{Array}
978
			 */
979
			filter: function (input, fn){
980
				var result = [], i = 0, n = input.length, val;
981
982
				for( ; i < n; i++ ){
983
					if( i in input ){
984
						val = input[i];
985
						if( fn.call(val, val, i, input) ){
986
							result.push(val);
987
						}
988
					}
989
				}
990
991
				return	result;
992
			},
993
994
995
			/**
996
			 * Filter files
997
			 *
998
			 * @param	{Array}		files
999
			 * @param	{Function}	eachFn
1000
			 * @param	{Function}	resultFn
1001
			 */
1002
			filterFiles: function (files, eachFn, resultFn){
1003
				if( files.length ){
1004
					// HTML5 or Flash
1005
					var queue = files.concat(), file, result = [], deleted = [];
1006
1007
					(function _next(){
1008
						if( queue.length ){
1009
							file = queue.shift();
1010
							api.getInfo(file, function (err, info){
1011
								(eachFn(file, err ? false : info) ? result : deleted).push(file);
1012
								_next();
1013
							});
1014
						}
1015
						else {
1016
							resultFn(result, deleted);
1017
						}
1018
					})();
1019
				}
1020
				else {
1021
					resultFn([], files);
1022
				}
1023
			},
1024
1025
1026
			upload: function (options){
1027
				options = _extend({
1028
					  jsonp: 'callback'
1029
					, prepare: api.F
1030
					, beforeupload: api.F
1031
					, upload: api.F
1032
					, fileupload: api.F
1033
					, fileprogress: api.F
1034
					, filecomplete: api.F
1035
					, progress: api.F
1036
					, complete: api.F
1037
					, pause: api.F
1038
					, imageOriginal: true
1039
					, chunkSize: api.chunkSize
1040
					, chunkUploadRetry: api.chunkUploadRetry
1041
					, uploadRetry: api.uploadRetry
1042
				}, options);
1043
1044
1045
				if( options.imageAutoOrientation && !options.imageTransform ){
1046
					options.imageTransform = { rotate: 'auto' };
1047
				}
1048
1049
1050
				var
1051
					  proxyXHR = new api.XHR(options)
1052
					, dataArray = this._getFilesDataArray(options.files)
1053
					, _this = this
1054
					, _total = 0
1055
					, _loaded = 0
1056
					, _nextFile
1057
					, _complete = false
1058
				;
1059
1060
1061
				// calc total size
1062
				_each(dataArray, function (data){
1063
					_total += data.size;
1064
				});
1065
1066
				// Array of files
1067
				proxyXHR.files = [];
1068
				_each(dataArray, function (data){
1069
					proxyXHR.files.push(data.file);
1070
				});
1071
1072
				// Set upload status props
1073
				proxyXHR.total	= _total;
1074
				proxyXHR.loaded	= 0;
1075
				proxyXHR.filesLeft = dataArray.length;
1076
1077
				// emit "beforeupload"  event
1078
				options.beforeupload(proxyXHR, options);
1079
1080
				// Upload by file
1081
				_nextFile = function (){
1082
					var
1083
						  data = dataArray.shift()
1084
						, _file = data && data.file
1085
						, _fileLoaded = false
1086
						, _fileOptions = _simpleClone(options)
1087
					;
1088
1089
					proxyXHR.filesLeft = dataArray.length;
1090
1091
					if( _file && _file.name === api.expando ){
1092
						_file = null;
1093
						api.log('[warn] FileAPI.upload() — called without files');
1094
					}
1095
1096
					if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1097
						// Mark active job
1098
						_complete = false;
1099
1100
						// Set current upload file
1101
						proxyXHR.currentFile = _file;
1102
1103
						// Prepare file options
1104
						if (_file && options.prepare(_file, _fileOptions) === false) {
1105
							_nextFile.call(_this);
1106
							return;
1107
						}
1108
						_fileOptions.file = _file;
1109
1110
						_this._getFormData(_fileOptions, data, function (form){
1111
							if( !_loaded ){
1112
								// emit "upload" event
1113
								options.upload(proxyXHR, options);
1114
							}
1115
1116
							var xhr = new api.XHR(_extend({}, _fileOptions, {
1117
1118
								upload: _file ? function (){
1119
									// emit "fileupload" event
1120
									options.fileupload(_file, xhr, _fileOptions);
1121
								} : noop,
1122
1123
								progress: _file ? function (evt){
1124
									if( !_fileLoaded ){
1125
										// For ignore the double calls.
1126
										_fileLoaded = (evt.loaded === evt.total);
1127
1128
										// emit "fileprogress" event
1129
										options.fileprogress({
1130
											  type:   'progress'
1131
											, total:  data.total = evt.total
1132
											, loaded: data.loaded = evt.loaded
1133
										}, _file, xhr, _fileOptions);
1134
1135
										// emit "progress" event
1136
										options.progress({
1137
											  type:   'progress'
1138
											, total:  _total
1139
											, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1140
										}, _file, xhr, _fileOptions);
1141
									}
1142
								} : noop,
1143
1144
								complete: function (err){
1145
									_each(_xhrPropsExport, function (name){
1146
										proxyXHR[name] = xhr[name];
1147
									});
1148
1149
									if( _file ){
1150
										data.total = (data.total || data.size);
1151
										data.loaded	= data.total;
1152
1153
										if( !err ) {
1154
											// emulate 100% "progress"
1155
											this.progress(data);
1156
1157
											// fixed throttle event
1158
											_fileLoaded = true;
1159
1160
											// bytes loaded
1161
											_loaded += data.size; // data.size != data.total, it's desirable fix this
1162
											proxyXHR.loaded = _loaded;
1163
										}
1164
1165
										// emit "filecomplete" event
1166
										options.filecomplete(err, xhr, _file, _fileOptions);
1167
									}
1168
1169
									// upload next file
1170
									setTimeout(function () {_nextFile.call(_this);}, 0);
1171
								}
1172
							})); // xhr
1173
1174
1175
							// ...
1176
							proxyXHR.abort = function (current){
1177
								if (!current) { dataArray.length = 0; }
1178
								this.current = current;
1179
								xhr.abort();
1180
							};
1181
1182
							// Start upload
1183
							xhr.send(form);
1184
						});
1185
					}
1186
					else {
1187
						var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1188
						options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1189
						// Mark done state
1190
						_complete = true;
1191
					}
1192
				};
1193
1194
1195
				// Next tick
1196
				setTimeout(_nextFile, 0);
1197
1198
1199
				// Append more files to the existing request
1200
				// first - add them to the queue head/tail
1201
				proxyXHR.append = function (files, first) {
1202
					files = api._getFilesDataArray([].concat(files));
1203
1204
					_each(files, function (data) {
1205
						_total += data.size;
1206
						proxyXHR.files.push(data.file);
1207
						if (first) {
1208
							dataArray.unshift(data);
1209
						} else {
1210
							dataArray.push(data);
1211
						}
1212
					});
1213
1214
					proxyXHR.statusText = "";
1215
1216
					if( _complete ){
1217
						_nextFile.call(_this);
1218
					}
1219
				};
1220
1221
1222
				// Removes file from queue by file reference and returns it
1223
				proxyXHR.remove = function (file) {
1224
				    var i = dataArray.length, _file;
1225
				    while( i-- ){
1226
						if( dataArray[i].file == file ){
1227
							_file = dataArray.splice(i, 1);
1228
							_total -= _file.size;
1229
						}
1230
					}
1231
					return	_file;
1232
				};
1233
1234
				return proxyXHR;
1235
			},
1236
1237
1238
			_getFilesDataArray: function (data){
1239
				var files = [], oFiles = {};
1240
1241
				if( isInputFile(data) ){
1242
					var tmp = api.getFiles(data);
1243
					oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1244
				}
1245
				else if( _isArray(data) && isInputFile(data[0]) ){
1246
					_each(data, function (input){
1247
						oFiles[input.name || 'file'] = api.getFiles(input);
1248
					});
1249
				}
1250
				else {
1251
					oFiles = data;
1252
				}
1253
1254
				_each(oFiles, function add(file, name){
1255
					if( _isArray(file) ){
1256
						_each(file, function (file){
1257
							add(file, name);
1258
						});
1259
					}
1260
					else if( file && (file.name || file.image) ){
1261
						files.push({
1262
							  name: name
1263
							, file: file
1264
							, size: file.size
1265
							, total: file.size
1266
							, loaded: 0
1267
						});
1268
					}
1269
				});
1270
1271
				if( !files.length ){
1272
					// Create fake `file` object
1273
					files.push({ file: { name: api.expando } });
1274
				}
1275
1276
				return	files;
1277
			},
1278
1279
1280
			_getFormData: function (options, data, fn){
1281
				var
1282
					  file = data.file
1283
					, name = data.name
1284
					, filename = file.name
1285
					, filetype = file.type
1286
					, trans = api.support.transform && options.imageTransform
1287
					, Form = new api.Form
1288
					, queue = api.queue(function (){ fn(Form); })
1289
					, isOrignTrans = trans && _isOriginTransform(trans)
1290
					, postNameConcat = api.postNameConcat
1291
				;
1292
1293
				// Append data
1294
				_each(options.data, function add(val, name){
1295
					if( typeof val == 'object' ){
1296
						_each(val, function (v, i){
1297
							add(v, postNameConcat(name, i));
1298
						});
1299
					}
1300
					else {
1301
						Form.append(name, val);
1302
					}
1303
				});
1304
1305
				(function _addFile(file/**Object*/){
1306
					if( file.image ){ // This is a FileAPI.Image
1307
						queue.inc();
1308
1309
						file.toData(function (err, image){
1310
							// @todo: error
1311
							filename = filename || (new Date).getTime()+'.png';
1312
1313
							_addFile(image);
1314
							queue.next();
1315
						});
1316
					}
1317
					else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1318
						queue.inc();
1319
1320
						if( isOrignTrans ){
1321
							// Convert to array for transform function
1322
							trans = [trans];
1323
						}
1324
1325
						api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1326
							if( isOrignTrans && !err ){
1327
								if( !dataURLtoBlob && !api.flashEngine ){
1328
									// Canvas.toBlob or Flash not supported, use multipart
1329
									Form.multipart = true;
1330
								}
1331
1332
								Form.append(name, images[0], filename,  trans[0].type || filetype);
1333
							}
1334
							else {
1335
								var addOrigin = 0;
1336
1337
								if( !err ){
1338
									_each(images, function (image, idx){
1339
										if( !dataURLtoBlob && !api.flashEngine ){
1340
											Form.multipart = true;
1341
										}
1342
1343
										if( !trans[idx].postName ){
1344
											addOrigin = 1;
1345
										}
1346
1347
										Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1348
									});
1349
								}
1350
1351
								if( err || options.imageOriginal ){
1352
									Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1353
								}
1354
							}
1355
1356
							queue.next();
1357
						});
1358
					}
1359
					else if( filename !== api.expando ){
1360
						Form.append(name, file, filename);
1361
					}
1362
				})(file);
1363
1364
				queue.check();
1365
			},
1366
1367
1368
			reset: function (inp, notRemove){
1369
				var parent, clone;
1370
1371
				if( jQuery ){
1372
					clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1373
					if( !notRemove ){
1374
						jQuery(inp).remove();
1375
					}
1376
				} else {
1377
					parent  = inp.parentNode;
1378
					clone   = parent.insertBefore(inp.cloneNode(true), inp);
1379
					clone.value = '';
1380
1381
					if( !notRemove ){
1382
						parent.removeChild(inp);
1383
					}
1384
1385
					_each(_elEvents[api.uid(inp)], function (fns, type){
1386
						_each(fns, function (fn){
1387
							_off(inp, type, fn);
1388
							_on(clone, type, fn);
1389
						});
1390
					});
1391
				}
1392
1393
				return  clone;
1394
			},
1395
1396
1397
			/**
1398
			 * Load remote file
1399
			 *
1400
			 * @param   {String}    url
1401
			 * @param   {Function}  fn
1402
			 * @return  {XMLHttpRequest}
1403
			 */
1404
			load: function (url, fn){
1405
				var xhr = api.getXHR();
1406
				if( xhr ){
1407
					xhr.open('GET', url, true);
1408
1409
					if( xhr.overrideMimeType ){
1410
				        xhr.overrideMimeType('text/plain; charset=x-user-defined');
1411
					}
1412
1413
					_on(xhr, 'progress', function (/**Event*/evt){
1414
						/** @namespace evt.lengthComputable */
1415
						if( evt.lengthComputable ){
1416
							fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1417
						}
1418
					});
1419
1420
					xhr.onreadystatechange = function(){
1421
						if( xhr.readyState == 4 ){
1422
							xhr.onreadystatechange = null;
1423
							if( xhr.status == 200 ){
1424
								url = url.split('/');
1425
								/** @namespace xhr.responseBody */
1426
								var file = {
1427
								      name: url[url.length-1]
1428
									, size: xhr.getResponseHeader('Content-Length')
1429
									, type: xhr.getResponseHeader('Content-Type')
1430
								};
1431
								file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1432
								fn({ type: 'load', result: file }, xhr);
1433
							}
1434
							else {
1435
								fn({ type: 'error' }, xhr);
1436
							}
1437
					    }
1438
					};
1439
				    xhr.send(null);
1440
				} else {
1441
					fn({ type: 'error' });
1442
				}
1443
1444
				return  xhr;
1445
			},
1446
1447
			encode64: function (str){
1448
				var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1449
1450
				if( typeof str !== 'string' ){
1451
					str	= String(str);
1452
				}
1453
1454
				while( i < str.length ){
1455
					//all three "& 0xff" added below are there to fix a known bug
1456
					//with bytes returned by xhr.responseText
1457
					var
1458
						  byte1 = str.charCodeAt(i++) & 0xff
1459
						, byte2 = str.charCodeAt(i++) & 0xff
1460
						, byte3 = str.charCodeAt(i++) & 0xff
1461
						, enc1 = byte1 >> 2
1462
						, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1463
						, enc3, enc4
1464
					;
1465
1466
					if( isNaN(byte2) ){
1467
						enc3 = enc4 = 64;
1468
					} else {
1469
						enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1470
						enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1471
					}
1472
1473
					outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1474
				}
1475
1476
				return  outStr;
1477
			}
1478
1479
		} // api
1480
	;
1481
1482
1483
	function _emit(target, fn, name, res, ext){
1484
		var evt = {
1485
			  type:		name.type || name
1486
			, target:	target
1487
			, result:	res
1488
		};
1489
		_extend(evt, ext);
1490
		fn(evt);
1491
	}
1492
1493
1494
	function _hasSupportReadAs(as){
1495
		return	FileReader && !!FileReader.prototype['readAs'+as];
1496
	}
1497
1498
1499
	function _readAs(file, fn, as, encoding){
1500
		if( api.isBlob(file) && _hasSupportReadAs(as) ){
1501
			var Reader = new FileReader;
1502
1503
			// Add event listener
1504
			_on(Reader, _readerEvents, function _fn(evt){
1505
				var type = evt.type;
1506
				if( type == 'progress' ){
1507
					_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1508
				}
1509
				else if( type == 'loadend' ){
1510
					_off(Reader, _readerEvents, _fn);
1511
					Reader = null;
1512
				}
1513
				else {
1514
					_emit(file, fn, evt, evt.target.result);
1515
				}
1516
			});
1517
1518
1519
			try {
1520
				// ReadAs ...
1521
				if( encoding ){
1522
					Reader['readAs'+as](file, encoding);
1523
				}
1524
				else {
1525
					Reader['readAs'+as](file);
1526
				}
1527
			}
1528
			catch (err){
1529
				_emit(file, fn, 'error', undef, { error: err.toString() });
1530
			}
1531
		}
1532
		else {
1533
			_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });
1534
		}
1535
	}
1536
1537
1538
	function _isRegularFile(file, callback){
1539
		// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1540
		if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1541
			if( FileReader ){
1542
				try {
1543
					var Reader = new FileReader();
1544
1545
					_one(Reader, _readerEvents, function (evt){
1546
						var isFile = evt.type != 'error';
1547
						callback(isFile);
1548
						if( isFile ){
1549
							Reader.abort();
1550
						}
1551
					});
1552
1553
					Reader.readAsDataURL(file);
1554
				} catch( err ){
1555
					callback(false);
1556
				}
1557
			}
1558
			else {
1559
				callback(null);
1560
			}
1561
		}
1562
		else {
1563
			callback(true);
1564
		}
1565
	}
1566
1567
1568
	function _getAsEntry(item){
1569
		var entry;
1570
		if( item.getAsEntry ){ entry = item.getAsEntry(); }
1571
		else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1572
		return	entry;
1573
	}
1574
1575
1576
	function _readEntryAsFiles(entry, callback){
1577
		if( !entry ){
1578
			// error
1579
			callback('invalid entry');
1580
		}
1581
		else if( entry.isFile ){
1582
			// Read as file
1583
			entry.file(function(file){
1584
				// success
1585
				file.fullPath = entry.fullPath;
1586
				callback(false, [file]);
1587
			}, function (err){
1588
				// error
1589
				callback('FileError.code: '+err.code);
1590
			});
1591
		}
1592
		else if( entry.isDirectory ){
1593
			var reader = entry.createReader(), result = [];
1594
1595
			reader.readEntries(function(entries){
1596
				// success
1597
				api.afor(entries, function (next, entry){
1598
					_readEntryAsFiles(entry, function (err, files){
1599
						if( err ){
1600
							api.log(err);
1601
						}
1602
						else {
1603
							result = result.concat(files);
1604
						}
1605
1606
						if( next ){
1607
							next();
1608
						}
1609
						else {
1610
							callback(false, result);
1611
						}
1612
					});
1613
				});
1614
			}, function (err){
1615
				// error
1616
				callback('directory_reader: ' + err);
1617
			});
1618
		}
1619
		else {
1620
			_readEntryAsFiles(_getAsEntry(entry), callback);
1621
		}
1622
	}
1623
1624
1625
	function _simpleClone(obj){
1626
		var copy = {};
1627
		_each(obj, function (val, key){
1628
			if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1629
				val = _extend({}, val);
1630
			}
1631
			copy[key] = val;
1632
		});
1633
		return	copy;
1634
	}
1635
1636
1637
	function isInputFile(el){
1638
		return	_rinput.test(el && el.tagName);
1639
	}
1640
1641
1642
	function _getDataTransfer(evt){
1643
		return	(evt.originalEvent || evt || '').dataTransfer || {};
1644
	}
1645
1646
1647
	function _isOriginTransform(trans){
1648
		var key;
1649
		for( key in trans ){
1650
			if( trans.hasOwnProperty(key) ){
1651
				if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1652
					return	true;
1653
				}
1654
			}
1655
		}
1656
		return	false;
1657
	}
1658
1659
1660
	// Add default image info reader
1661
	api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1662
		if( !file.__dimensions ){
1663
			var defer = file.__dimensions = api.defer();
1664
1665
			api.readAsImage(file, function (evt){
1666
				var img = evt.target;
1667
				defer.resolve(evt.type == 'load' ? false : 'error', {
1668
					  width:  img.width
1669
					, height: img.height
1670
				});
1671
                img.src = api.EMPTY_PNG;
1672
				img = null;
1673
			});
1674
		}
1675
1676
		file.__dimensions.then(callback);
1677
	});
1678
1679
1680
	/**
1681
	 * Drag'n'Drop special event
1682
	 *
1683
	 * @param	{HTMLElement}	el
1684
	 * @param	{Function}		onHover
1685
	 * @param	{Function}		onDrop
1686
	 */
1687
	api.event.dnd = function (el, onHover, onDrop){
1688
		var _id, _type;
1689
1690
		if( !onDrop ){
1691
			onDrop = onHover;
1692
			onHover = api.F;
1693
		}
1694
1695
		if( FileReader ){
1696
			// Hover
1697
			_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1698
				var
1699
					  types = _getDataTransfer(evt).types
1700
					, i = types && types.length
1701
					, debounceTrigger = false
1702
				;
1703
1704
				while( i-- ){
1705
					if( ~types[i].indexOf('File') ){
1706
						evt[preventDefault]();
1707
1708
						if( _type !== evt.type ){
1709
							_type = evt.type; // Store current type of event
1710
1711
							if( _type != 'dragleave' ){
1712
								onHover.call(evt[currentTarget], true, evt);
1713
							}
1714
1715
							debounceTrigger = true;
1716
						}
1717
1718
						break; // exit from "while"
1719
					}
1720
				}
1721
1722
				if( debounceTrigger ){
1723
					clearTimeout(_id);
1724
					_id = setTimeout(function (){
1725
						onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1726
					}, 50);
1727
				}
1728
			});
1729
1730
1731
			// Drop
1732
			_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1733
				evt[preventDefault]();
1734
1735
				_type = 0;
1736
				onHover.call(evt[currentTarget], false, evt);
1737
1738
				api.getDropFiles(evt, function (files){
1739
					onDrop.call(evt[currentTarget], files, evt);
1740
				});
1741
			});
1742
		}
1743
		else {
1744
			api.log("Drag'n'Drop -- not supported");
1745
		}
1746
	};
1747
1748
1749
	/**
1750
	 * Remove drag'n'drop
1751
	 * @param	{HTMLElement}	el
1752
	 * @param	{Function}		onHover
1753
	 * @param	{Function}		onDrop
1754
	 */
1755
	api.event.dnd.off = function (el, onHover, onDrop){
1756
		_off(el, 'dragenter dragleave dragover', onHover.ff);
1757
		_off(el, 'drop', onDrop.ff);
1758
	};
1759
1760
1761
	// Support jQuery
1762
	if( jQuery && !jQuery.fn.dnd ){
1763
		jQuery.fn.dnd = function (onHover, onDrop){
1764
			return this.each(function (){
1765
				api.event.dnd(this, onHover, onDrop);
1766
			});
1767
		};
1768
1769
		jQuery.fn.offdnd = function (onHover, onDrop){
1770
			return this.each(function (){
1771
				api.event.dnd.off(this, onHover, onDrop);
1772
			});
1773
		};
1774
	}
1775
1776
	// @export
1777
	window.FileAPI  = _extend(api, window.FileAPI);
1778
1779
1780
	// Debug info
1781
	api.log('FileAPI: ' + api.version);
1782
	api.log('protocol: ' + window.location.protocol);
1783
	api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1784
1785
1786
	// @detect 'x-ua-compatible'
1787
	_each(document.getElementsByTagName('meta'), function (meta){
1788
		if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1789
			api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1790
		}
1791
	});
1792
1793
1794
	// configuration
1795
	try {
1796
		api._supportConsoleLog = !!console.log;
1797
		api._supportConsoleLogApply = !!console.log.apply;
1798
	} catch (err) {}
1799
1800
	if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1801
	if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1802
	if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1803
})(window, void 0);
1804
1805
/*global window, FileAPI, document */
1806
1807
(function (api, document, undef) {
1808
	'use strict';
1809
1810
	var
1811
		min = Math.min,
1812
		round = Math.round,
1813
		getCanvas = function () { return document.createElement('canvas'); },
1814
		support = false,
1815
		exifOrientation = {
1816
			  8:	270
1817
			, 3:	180
1818
			, 6:	90
1819
			, 7:	270
1820
			, 4:	180
1821
			, 5:	90
1822
		}
1823
	;
1824
1825
	try {
1826
		support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1827
	}
1828
	catch (e){}
1829
1830
1831
	function Image(file){
1832
		if( file instanceof Image ){
1833
			var img = new Image(file.file);
1834
			api.extend(img.matrix, file.matrix);
1835
			return	img;
1836
		}
1837
		else if( !(this instanceof Image) ){
1838
			return	new Image(file);
1839
		}
1840
1841
		this.file   = file;
1842
		this.size   = file.size || 100;
1843
1844
		this.matrix	= {
1845
			sx: 0,
1846
			sy: 0,
1847
			sw: 0,
1848
			sh: 0,
1849
			dx: 0,
1850
			dy: 0,
1851
			dw: 0,
1852
			dh: 0,
1853
			resize: 0, // min, max OR preview
1854
			deg: 0,
1855
			quality: 1, // jpeg quality
1856
			filter: 0
1857
		};
1858
	}
1859
1860
1861
	Image.prototype = {
1862
		image: true,
1863
		constructor: Image,
1864
1865
		set: function (attrs){
1866
			api.extend(this.matrix, attrs);
1867
			return	this;
1868
		},
1869
1870
		crop: function (x, y, w, h){
1871
			if( w === undef ){
1872
				w	= x;
1873
				h	= y;
1874
				x = y = 0;
1875
			}
1876
			return	this.set({ sx: x, sy: y, sw: w, sh: h || w });
1877
		},
1878
1879
		resize: function (w, h, strategy){
1880
			if( /min|max/.test(h) ){
1881
				strategy = h;
1882
				h = w;
1883
			}
1884
1885
			return	this.set({ dw: w, dh: h || w, resize: strategy });
1886
		},
1887
1888
		preview: function (w, h){
1889
			return	this.resize(w, h || w, 'preview');
1890
		},
1891
1892
		rotate: function (deg){
1893
			return	this.set({ deg: deg });
1894
		},
1895
1896
		filter: function (filter){
1897
			return	this.set({ filter: filter });
1898
		},
1899
1900
		overlay: function (images){
1901
			return	this.set({ overlay: images });
1902
		},
1903
1904
		clone: function (){
1905
			return	new Image(this);
1906
		},
1907
1908
		_load: function (image, fn){
1909
			var self = this;
1910
1911
			if( /img|video/i.test(image.nodeName) ){
1912
				fn.call(self, null, image);
1913
			}
1914
			else {
1915
				api.readAsImage(image, function (evt){
1916
					fn.call(self, evt.type != 'load', evt.result);
1917
				});
1918
			}
1919
		},
1920
1921
		_apply: function (image, fn){
1922
			var
1923
				  canvas = getCanvas()
1924
				, m = this.getMatrix(image)
1925
				, ctx = canvas.getContext('2d')
1926
				, width = image.videoWidth || image.width
1927
				, height = image.videoHeight || image.height
1928
				, deg = m.deg
1929
				, dw = m.dw
1930
				, dh = m.dh
1931
				, w = width
1932
				, h = height
1933
				, filter = m.filter
1934
				, copy // canvas copy
1935
				, buffer = image
1936
				, overlay = m.overlay
1937
				, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1938
				, renderImageToCanvas = api.renderImageToCanvas
1939
			;
1940
1941
			// Normalize angle
1942
			deg = deg - Math.floor(deg/360)*360;
1943
1944
			// For `renderImageToCanvas`
1945
			image._type = this.file.type;
1946
1947
			while(m.multipass && min(w/dw, h/dh) > 2 ){
1948
				w = (w/2 + 0.5)|0;
1949
				h = (h/2 + 0.5)|0;
1950
1951
				copy = getCanvas();
1952
				copy.width  = w;
1953
				copy.height = h;
1954
1955
				if( buffer !== image ){
1956
					renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1957
					buffer = copy;
1958
				}
1959
				else {
1960
					buffer = copy;
1961
					renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1962
					m.sx = m.sy = m.sw = m.sh = 0;
1963
				}
1964
			}
1965
1966
1967
			canvas.width  = (deg % 180) ? dh : dw;
1968
			canvas.height = (deg % 180) ? dw : dh;
1969
1970
			canvas.type = m.type;
1971
			canvas.quality = m.quality;
1972
1973
			ctx.rotate(deg * Math.PI / 180);
1974
			renderImageToCanvas(ctx.canvas, buffer
1975
				, m.sx, m.sy
1976
				, m.sw || buffer.width
1977
				, m.sh || buffer.height
1978
				, (deg == 180 || deg == 270 ? -dw : 0)
1979
				, (deg == 90 || deg == 180 ? -dh : 0)
1980
				, dw, dh
1981
			);
1982
			dw = canvas.width;
1983
			dh = canvas.height;
1984
1985
			// Apply overlay
1986
			overlay && api.each([].concat(overlay), function (over){
1987
				queue.inc();
1988
				// preload
1989
				var img = new window.Image, fn = function (){
1990
					var
1991
						  x = over.x|0
1992
						, y = over.y|0
1993
						, w = over.w || img.width
1994
						, h = over.h || img.height
1995
						, rel = over.rel
1996
					;
1997
1998
					// center  |  right  |  left
1999
					x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
2000
2001
					// center  |  bottom  |  top
2002
					y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
2003
2004
					api.event.off(img, 'error load abort', fn);
2005
2006
					try {
2007
						ctx.globalAlpha = over.opacity || 1;
2008
						ctx.drawImage(img, x, y, w, h);
2009
					}
2010
					catch (er){}
2011
2012
					queue.next();
2013
				};
2014
2015
				api.event.on(img, 'error load abort', fn);
2016
				img.src = over.src;
2017
2018
				if( img.complete ){
2019
					fn();
2020
				}
2021
			});
2022
2023
			if( filter ){
2024
				queue.inc();
2025
				Image.applyFilter(canvas, filter, queue.next);
2026
			}
2027
2028
			queue.check();
2029
		},
2030
2031
		getMatrix: function (image){
2032
			var
2033
				  m  = api.extend({}, this.matrix)
2034
				, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width
2035
				, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2036
				, dw = m.dw = m.dw || sw
2037
				, dh = m.dh = m.dh || sh
2038
				, sf = sw/sh, df = dw/dh
2039
				, strategy = m.resize
2040
			;
2041
2042
			if( strategy == 'preview' ){
2043
				if( dw != sw || dh != sh ){
2044
					// Make preview
2045
					var w, h;
2046
2047
					if( df >= sf ){
2048
						w	= sw;
2049
						h	= w / df;
2050
					} else {
2051
						h	= sh;
2052
						w	= h * df;
2053
					}
2054
2055
					if( w != sw || h != sh ){
2056
						m.sx	= ~~((sw - w)/2);
2057
						m.sy	= ~~((sh - h)/2);
2058
						sw		= w;
2059
						sh		= h;
2060
					}
2061
				}
2062
			}
2063
			else if( strategy ){
2064
				if( !(sw > dw || sh > dh) ){
2065
					dw = sw;
2066
					dh = sh;
2067
				}
2068
				else if( strategy == 'min' ){
2069
					dw = round(sf < df ? min(sw, dw) : dh*sf);
2070
					dh = round(sf < df ? dw/sf : min(sh, dh));
2071
				}
2072
				else {
2073
					dw = round(sf >= df ? min(sw, dw) : dh*sf);
2074
					dh = round(sf >= df ? dw/sf : min(sh, dh));
2075
				}
2076
			}
2077
2078
			m.sw = sw;
2079
			m.sh = sh;
2080
			m.dw = dw;
2081
			m.dh = dh;
2082
			m.multipass = api.multiPassResize;
2083
			return	m;
2084
		},
2085
2086
		_trans: function (fn){
2087
			this._load(this.file, function (err, image){
2088
				if( err ){
2089
					fn(err);
2090
				}
2091
				else {
2092
					try {
2093
						this._apply(image, fn);
2094
					} catch (err){
2095
						api.log('[err] FileAPI.Image.fn._apply:', err);
2096
						fn(err);
2097
					}
2098
				}
2099
			});
2100
		},
2101
2102
2103
		get: function (fn){
2104
			if( api.support.transform ){
2105
				var _this = this, matrix = _this.matrix;
2106
2107
				if( matrix.deg == 'auto' ){
2108
					api.getInfo(_this.file, function (err, info){
2109
						// rotate by exif orientation
2110
						matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2111
						_this._trans(fn);
2112
					});
2113
				}
2114
				else {
2115
					_this._trans(fn);
2116
				}
2117
			}
2118
			else {
2119
				fn('not_support_transform');
2120
			}
2121
2122
			return this;
2123
		},
2124
2125
2126
		toData: function (fn){
2127
			return this.get(fn);
2128
		}
2129
2130
	};
2131
2132
2133
	Image.exifOrientation = exifOrientation;
2134
2135
2136
	Image.transform = function (file, transform, autoOrientation, fn){
2137
		function _transform(err, img){
2138
			// img -- info object
2139
			var
2140
				  images = {}
2141
				, queue = api.queue(function (err){
2142
					fn(err, images);
2143
				})
2144
			;
2145
2146
			if( !err ){
2147
				api.each(transform, function (params, name){
2148
					if( !queue.isFail() ){
2149
						var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2150
2151
						if( isFn ){
2152
							params(img, ImgTrans);
2153
						}
2154
						else if( params.width ){
2155
							ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2156
						}
2157
						else {
2158
							if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2159
								ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2160
							}
2161
						}
2162
2163
						if( params.crop ){
2164
							var crop = params.crop;
2165
							ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2166
						}
2167
2168
						if( params.rotate === undef && autoOrientation ){
2169
							params.rotate = 'auto';
2170
						}
2171
2172
						ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2173
2174
						if( !isFn ){
2175
							ImgTrans.set({
2176
								  deg: params.rotate
2177
								, overlay: params.overlay
2178
								, filter: params.filter
2179
								, quality: params.quality || 1
2180
							});
2181
						}
2182
2183
						queue.inc();
2184
						ImgTrans.toData(function (err, image){
2185
							if( err ){
2186
								queue.fail();
2187
							}
2188
							else {
2189
								images[name] = image;
2190
								queue.next();
2191
							}
2192
						});
2193
					}
2194
				});
2195
			}
2196
			else {
2197
				queue.fail();
2198
			}
2199
		}
2200
2201
2202
		// @todo: Оло-ло, нужно рефакторить это место
2203
		if( file.width ){
2204
			_transform(false, file);
2205
		} else {
2206
			api.getInfo(file, _transform);
2207
		}
2208
	};
2209
2210
2211
	// @const
2212
	api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2213
		api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2214
			Image[x+'_'+y] = i*3 + j;
2215
			Image[y+'_'+x] = i*3 + j;
2216
		});
2217
	});
2218
2219
2220
	/**
2221
	 * Trabsform element to canvas
2222
	 *
2223
	 * @param    {Image|HTMLVideoElement}   el
2224
	 * @returns  {Canvas}
2225
	 */
2226
	Image.toCanvas = function(el){
2227
		var canvas		= document.createElement('canvas');
2228
		canvas.width	= el.videoWidth || el.width;
2229
		canvas.height	= el.videoHeight || el.height;
2230
		canvas.getContext('2d').drawImage(el, 0, 0);
2231
		return	canvas;
2232
	};
2233
2234
2235
	/**
2236
	 * Create image from DataURL
2237
	 * @param  {String}  dataURL
2238
	 * @param  {Object}  size
2239
	 * @param  {Function}  callback
2240
	 */
2241
	Image.fromDataURL = function (dataURL, size, callback){
2242
		var img = api.newImage(dataURL);
2243
		api.extend(img, size);
2244
		callback(img);
2245
	};
2246
2247
2248
	/**
2249
	 * Apply filter (caman.js)
2250
	 *
2251
	 * @param  {Canvas|Image}   canvas
2252
	 * @param  {String|Function}  filter
2253
	 * @param  {Function}  doneFn
2254
	 */
2255
	Image.applyFilter = function (canvas, filter, doneFn){
2256
		if( typeof filter == 'function' ){
2257
			filter(canvas, doneFn);
2258
		}
2259
		else if( window.Caman ){
2260
			// http://camanjs.com/guides/
2261
			window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2262
				if( typeof filter == 'string' ){
2263
					this[filter]();
2264
				}
2265
				else {
2266
					api.each(filter, function (val, method){
2267
						this[method](val);
2268
					}, this);
2269
				}
2270
				this.render(doneFn);
2271
			});
2272
		}
2273
	};
2274
2275
2276
	/**
2277
	 * For load-image-ios.js
2278
	 */
2279
	api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2280
		try {
2281
			return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2282
		} catch (ex) {
2283
			api.log('renderImageToCanvas failed');
2284
			throw ex;
2285
		}
2286
	};
2287
2288
2289
	// @export
2290
	api.support.canvas = api.support.transform = support;
2291
	api.Image = Image;
2292
})(FileAPI, document);
2293
2294
/*
2295
 * JavaScript Load Image iOS scaling fixes 1.0.3
2296
 * https://github.com/blueimp/JavaScript-Load-Image
2297
 *
2298
 * Copyright 2013, Sebastian Tschan
2299
 * https://blueimp.net
2300
 *
2301
 * iOS image scaling fixes based on
2302
 * https://github.com/stomita/ios-imagefile-megapixel
2303
 *
2304
 * Licensed under the MIT license:
2305
 * http://www.opensource.org/licenses/MIT
2306
 */
2307
2308
/*jslint nomen: true, bitwise: true */
2309
/*global FileAPI, window, document */
2310
2311
(function (factory) {
2312
	'use strict';
2313
	factory(FileAPI);
2314
}(function (loadImage) {
2315
    'use strict';
2316
2317
    // Only apply fixes on the iOS platform:
2318
    if (!window.navigator || !window.navigator.platform ||
2319
             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2320
        return;
2321
    }
2322
2323
    var originalRenderMethod = loadImage.renderImageToCanvas;
2324
2325
    // Detects subsampling in JPEG images:
2326
    loadImage.detectSubsampling = function (img) {
2327
        var canvas,
2328
            context;
2329
        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2330
            canvas = document.createElement('canvas');
2331
            canvas.width = canvas.height = 1;
2332
            context = canvas.getContext('2d');
2333
            context.drawImage(img, -img.width + 1, 0);
2334
            // subsampled image becomes half smaller in rendering size.
2335
            // check alpha channel value to confirm image is covering edge pixel or not.
2336
            // if alpha value is 0 image is not covering, hence subsampled.
2337
            return context.getImageData(0, 0, 1, 1).data[3] === 0;
2338
        }
2339
        return false;
2340
    };
2341
2342
    // Detects vertical squash in JPEG images:
2343
    loadImage.detectVerticalSquash = function (img, subsampled) {
2344
        var naturalHeight = img.naturalHeight || img.height,
2345
            canvas = document.createElement('canvas'),
2346
            context = canvas.getContext('2d'),
2347
            data,
2348
            sy,
2349
            ey,
2350
            py,
2351
            alpha;
2352
        if (subsampled) {
2353
            naturalHeight /= 2;
2354
        }
2355
        canvas.width = 1;
2356
        canvas.height = naturalHeight;
2357
        context.drawImage(img, 0, 0);
2358
        data = context.getImageData(0, 0, 1, naturalHeight).data;
2359
        // search image edge pixel position in case it is squashed vertically:
2360
        sy = 0;
2361
        ey = naturalHeight;
2362
        py = naturalHeight;
2363
        while (py > sy) {
2364
            alpha = data[(py - 1) * 4 + 3];
2365
            if (alpha === 0) {
2366
                ey = py;
2367
            } else {
2368
                sy = py;
2369
            }
2370
            py = (ey + sy) >> 1;
2371
        }
2372
        return (py / naturalHeight) || 1;
2373
    };
2374
2375
    // Renders image to canvas while working around iOS image scaling bugs:
2376
    // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2377
    loadImage.renderImageToCanvas = function (
2378
        canvas,
2379
        img,
2380
        sourceX,
2381
        sourceY,
2382
        sourceWidth,
2383
        sourceHeight,
2384
        destX,
2385
        destY,
2386
        destWidth,
2387
        destHeight
2388
    ) {
2389
        if (img._type === 'image/jpeg') {
2390
            var context = canvas.getContext('2d'),
2391
                tmpCanvas = document.createElement('canvas'),
2392
                tileSize = 1024,
2393
                tmpContext = tmpCanvas.getContext('2d'),
2394
                subsampled,
2395
                vertSquashRatio,
2396
                tileX,
2397
                tileY;
2398
            tmpCanvas.width = tileSize;
2399
            tmpCanvas.height = tileSize;
2400
            context.save();
2401
            subsampled = loadImage.detectSubsampling(img);
2402
            if (subsampled) {
2403
                sourceX /= 2;
2404
                sourceY /= 2;
2405
                sourceWidth /= 2;
2406
                sourceHeight /= 2;
2407
            }
2408
            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2409
            if (subsampled || vertSquashRatio !== 1) {
2410
                sourceY *= vertSquashRatio;
2411
                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2412
                destHeight = Math.ceil(
2413
                    tileSize * destHeight / sourceHeight / vertSquashRatio
2414
                );
2415
                destY = 0;
2416
                tileY = 0;
2417
                while (tileY < sourceHeight) {
2418
                    destX = 0;
2419
                    tileX = 0;
2420
                    while (tileX < sourceWidth) {
2421
                        tmpContext.clearRect(0, 0, tileSize, tileSize);
2422
                        tmpContext.drawImage(
2423
                            img,
2424
                            sourceX,
2425
                            sourceY,
2426
                            sourceWidth,
2427
                            sourceHeight,
2428
                            -tileX,
2429
                            -tileY,
2430
                            sourceWidth,
2431
                            sourceHeight
2432
                        );
2433
                        context.drawImage(
2434
                            tmpCanvas,
2435
                            0,
2436
                            0,
2437
                            tileSize,
2438
                            tileSize,
2439
                            destX,
2440
                            destY,
2441
                            destWidth,
2442
                            destHeight
2443
                        );
2444
                        tileX += tileSize;
2445
                        destX += destWidth;
2446
                    }
2447
                    tileY += tileSize;
2448
                    destY += destHeight;
2449
                }
2450
                context.restore();
2451
                return canvas;
2452
            }
2453
        }
2454
        return originalRenderMethod(
2455
            canvas,
2456
            img,
2457
            sourceX,
2458
            sourceY,
2459
            sourceWidth,
2460
            sourceHeight,
2461
            destX,
2462
            destY,
2463
            destWidth,
2464
            destHeight
2465
        );
2466
    };
2467
2468
}));
2469
2470
/*global window, FileAPI */
2471
2472
(function (api, window){
2473
	"use strict";
2474
2475
	var
2476
		  document = window.document
2477
		, FormData = window.FormData
2478
		, Form = function (){ this.items = []; }
2479
		, encodeURIComponent = window.encodeURIComponent
2480
	;
2481
2482
2483
	Form.prototype = {
2484
2485
		append: function (name, blob, file, type){
2486
			this.items.push({
2487
				  name: name
2488
				, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2489
				, file: blob && (file || blob.name)
2490
				, type:	blob && (type || blob.type)
2491
			});
2492
		},
2493
2494
		each: function (fn){
2495
			var i = 0, n = this.items.length;
2496
			for( ; i < n; i++ ){
2497
				fn.call(this, this.items[i]);
2498
			}
2499
		},
2500
2501
		toData: function (fn, options){
2502
		    // allow chunked transfer if we have only one file to send
2503
		    // flag is used below and in XHR._send
2504
		    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2505
2506
			if( !api.support.html5 ){
2507
				api.log('FileAPI.Form.toHtmlData');
2508
				this.toHtmlData(fn);
2509
			}
2510
			else if( !api.formData || this.multipart || !FormData ){
2511
				api.log('FileAPI.Form.toMultipartData');
2512
				this.toMultipartData(fn);
2513
			}
2514
			else if( options._chunked ){
2515
				api.log('FileAPI.Form.toPlainData');
2516
				this.toPlainData(fn);
2517
			}
2518
			else {
2519
				api.log('FileAPI.Form.toFormData');
2520
				this.toFormData(fn);
2521
			}
2522
		},
2523
2524
		_to: function (data, complete, next, arg){
2525
			var queue = api.queue(function (){
2526
				complete(data);
2527
			});
2528
2529
			this.each(function (file){
2530
				next(file, data, queue, arg);
2531
			});
2532
2533
			queue.check();
2534
		},
2535
2536
2537
		toHtmlData: function (fn){
2538
			this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2539
				var blob = file.blob, hidden;
2540
2541
				if( file.file ){
2542
					api.reset(blob, true);
2543
					// set new name
2544
					blob.name = file.name;
2545
					blob.disabled = false;
2546
					data.appendChild(blob);
2547
				}
2548
				else {
2549
					hidden = document.createElement('input');
2550
					hidden.name  = file.name;
2551
					hidden.type  = 'hidden';
2552
					hidden.value = blob;
2553
					data.appendChild(hidden);
2554
				}
2555
			});
2556
		},
2557
2558
		toPlainData: function (fn){
2559
			this._to({}, fn, function (file, data, queue){
2560
				if( file.file ){
2561
					data.type = file.file;
2562
				}
2563
2564
				if( file.blob.toBlob ){
2565
				    // canvas
2566
					queue.inc();
2567
					_convertFile(file, function (file, blob){
2568
						data.name = file.name;
2569
						data.file = blob;
2570
						data.size = blob.length;
2571
						data.type = file.type;
2572
						queue.next();
2573
					});
2574
				}
2575
				else if( file.file ){
2576
				    // file
2577
					data.name = file.blob.name;
2578
					data.file = file.blob;
2579
					data.size = file.blob.size;
2580
					data.type = file.type;
2581
				}
2582
				else {
2583
				    // additional data
2584
				    if( !data.params ){
2585
				        data.params = [];
2586
				    }
2587
				    data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2588
				}
2589
2590
				data.start = -1;
2591
				data.end = data.file && data.file.FileAPIReadPosition || -1;
2592
				data.retry = 0;
2593
			});
2594
		},
2595
2596
		toFormData: function (fn){
2597
			this._to(new FormData, fn, function (file, data, queue){
2598
				if( file.blob && file.blob.toBlob ){
2599
					queue.inc();
2600
					_convertFile(file, function (file, blob){
2601
						data.append(file.name, blob, file.file);
2602
						queue.next();
2603
					});
2604
				}
2605
				else if( file.file ){
2606
					data.append(file.name, file.blob, file.file);
2607
				}
2608
				else {
2609
					data.append(file.name, file.blob);
2610
				}
2611
2612
				if( file.file ){
2613
					data.append('_'+file.name, file.file);
2614
				}
2615
			});
2616
		},
2617
2618
2619
		toMultipartData: function (fn){
2620
			this._to([], fn, function (file, data, queue, boundary){
2621
				queue.inc();
2622
				_convertFile(file, function (file, blob){
2623
					data.push(
2624
						  '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2625
						+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2626
						+ '\r\n'
2627
						+ '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2628
						+ '\r\n')
2629
					);
2630
					queue.next();
2631
				}, true);
2632
			}, api.expando);
2633
		}
2634
	};
2635
2636
2637
	function _convertFile(file, fn, useBinaryString){
2638
		var blob = file.blob, filename = file.file;
2639
2640
		if( filename ){
2641
			if( !blob.toDataURL ){
2642
				// The Blob is not an image.
2643
				api.readAsBinaryString(blob, function (evt){
2644
					if( evt.type == 'load' ){
2645
						fn(file, evt.result);
2646
					}
2647
				});
2648
				return;
2649
			}
2650
2651
			var
2652
				  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2653
				, type = mime[file.type] ? file.type : 'image/png'
2654
				, ext  = mime[type] || '.png'
2655
				, quality = blob.quality || 1
2656
			;
2657
2658
			if( !filename.match(new RegExp(ext+'$', 'i')) ){
2659
				// Does not change the current extension, but add a new one.
2660
				filename += ext.replace('?', '');
2661
			}
2662
2663
			file.file = filename;
2664
			file.type = type;
2665
2666
			if( !useBinaryString && blob.toBlob ){
2667
				blob.toBlob(function (blob){
2668
					fn(file, blob);
2669
				}, type, quality);
2670
			}
2671
			else {
2672
				fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2673
			}
2674
		}
2675
		else {
2676
			fn(file, blob);
2677
		}
2678
	}
2679
2680
2681
	// @export
2682
	api.Form = Form;
2683
})(FileAPI, window);
2684
2685
/*global window, FileAPI, Uint8Array */
2686
2687
(function (window, api){
2688
	"use strict";
2689
2690
	var
2691
		  noop = function (){}
2692
		, document = window.document
2693
2694
		, XHR = function (options){
2695
			this.uid = api.uid();
2696
			this.xhr = {
2697
				  abort: noop
2698
				, getResponseHeader: noop
2699
				, getAllResponseHeaders: noop
2700
			};
2701
			this.options = options;
2702
		},
2703
2704
		_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2705
	;
2706
2707
2708
	XHR.prototype = {
2709
		status: 0,
2710
		statusText: '',
2711
		constructor: XHR,
2712
2713
		getResponseHeader: function (name){
2714
			return this.xhr.getResponseHeader(name);
2715
		},
2716
2717
		getAllResponseHeaders: function (){
2718
			return this.xhr.getAllResponseHeaders() || {};
2719
		},
2720
2721
		end: function (status, statusText){
2722
			var _this = this, options = _this.options;
2723
2724
			_this.end		=
2725
			_this.abort		= noop;
2726
			_this.status	= status;
2727
2728
			if( statusText ){
2729
				_this.statusText = statusText;
2730
			}
2731
2732
			api.log('xhr.end:', status, statusText);
2733
			options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2734
2735
			if( _this.xhr && _this.xhr.node ){
2736
				setTimeout(function (){
2737
					var node = _this.xhr.node;
2738
					try { node.parentNode.removeChild(node); } catch (e){}
2739
					try { delete window[_this.uid]; } catch (e){}
2740
					window[_this.uid] = _this.xhr.node = null;
2741
				}, 9);
2742
			}
2743
		},
2744
2745
		abort: function (){
2746
			this.end(0, 'abort');
2747
2748
			if( this.xhr ){
2749
				this.xhr.aborted = true;
2750
				this.xhr.abort();
2751
			}
2752
		},
2753
2754
		send: function (FormData){
2755
			var _this = this, options = this.options;
2756
2757
			FormData.toData(function (data){
2758
				// Start uploading
2759
				options.upload(options, _this);
2760
				_this._send.call(_this, options, data);
2761
			}, options);
2762
		},
2763
2764
		_send: function (options, data){
2765
			var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2766
2767
			api.log('XHR._send:', data);
2768
2769
			if( !options.cache ){
2770
				// No cache
2771
				url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2772
			}
2773
2774
			if( data.nodeName ){
2775
				var jsonp = options.jsonp;
2776
2777
				// prepare callback in GET
2778
				url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2779
2780
				// legacy
2781
				options.upload(options, _this);
2782
2783
				var
2784
					onPostMessage = function (evt){
2785
						if( ~url.indexOf(evt.origin) ){
2786
							try {
2787
								var result = api.parseJSON(evt.data);
2788
								if( result.id == uid ){
2789
									complete(result.status, result.statusText, result.response);
2790
								}
2791
							} catch( err ){
2792
								complete(0, err.message);
2793
							}
2794
						}
2795
					},
2796
2797
					// jsonp-callack
2798
					complete = window[uid] = function (status, statusText, response){
2799
						_this.readyState	= 4;
2800
						_this.responseText	= response;
2801
						_this.end(status, statusText);
2802
2803
						api.event.off(window, 'message', onPostMessage);
2804
						window[uid] = xhr = transport = window[onloadFuncName] = null;
2805
					}
2806
				;
2807
2808
				_this.xhr.abort = function (){
2809
					try {
2810
						if( transport.stop ){ transport.stop(); }
2811
						else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2812
						else { transport.contentWindow.document.execCommand('Stop'); }
2813
					}
2814
					catch (er) {}
2815
					complete(0, "abort");
2816
				};
2817
2818
				api.event.on(window, 'message', onPostMessage);
2819
2820
				window[onloadFuncName] = function (){
2821
					try {
2822
						var
2823
							  win = transport.contentWindow
2824
							, doc = win.document
2825
							, result = win.result || api.parseJSON(doc.body.innerHTML)
2826
						;
2827
						complete(result.status, result.statusText, result.response);
2828
					} catch (e){
2829
						api.log('[transport.onload]', e);
2830
					}
2831
				};
2832
2833
				xhr = document.createElement('div');
2834
				xhr.innerHTML = '<form target="'+ uid +'" action="../../../../../../../ng-file-upload-12.2.12/demo/src/main/webapp/js/'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2835
							+ '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2836
							+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2837
							+ '</form>'
2838
				;
2839
2840
				// get form-data & transport
2841
				var
2842
					  form = xhr.getElementsByTagName('form')[0]
2843
					, transport = xhr.getElementsByTagName('iframe')[0]
2844
				;
2845
2846
				form.appendChild(data);
2847
2848
				api.log(form.parentNode.innerHTML);
2849
2850
				// append to DOM
2851
				document.body.appendChild(xhr);
2852
2853
				// keep a reference to node-transport
2854
				_this.xhr.node = xhr;
2855
2856
				// send
2857
				_this.readyState = 2; // loaded
2858
				form.submit();
2859
				form = null;
2860
			}
2861
			else {
2862
				// Clean url
2863
				url = url.replace(/([a-z]+)=(\?)&?/i, '');
2864
2865
				// html5
2866
				if (this.xhr && this.xhr.aborted) {
2867
					api.log("Error: already aborted");
2868
					return;
2869
				}
2870
				xhr = _this.xhr = api.getXHR();
2871
2872
				if (data.params) {
2873
					url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2874
				}
2875
2876
				xhr.open('POST', url, true);
2877
2878
				if( api.withCredentials ){
2879
					xhr.withCredentials = "true";
2880
				}
2881
2882
				if( !options.headers || !options.headers['X-Requested-With'] ){
2883
					xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2884
				}
2885
2886
				api.each(options.headers, function (val, key){
2887
					xhr.setRequestHeader(key, val);
2888
				});
2889
2890
2891
				if ( options._chunked ) {
2892
					// chunked upload
2893
					if( xhr.upload ){
2894
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2895
							if (!data.retry) {
2896
								// show progress only for correct chunk uploads
2897
								options.progress({
2898
									  type:			evt.type
2899
									, total:		data.size
2900
									, loaded:		data.start + evt.loaded
2901
									, totalSize:	data.size
2902
								}, _this, options);
2903
							}
2904
						}, 100), false);
2905
					}
2906
2907
					xhr.onreadystatechange = function (){
2908
						var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2909
2910
						_this.status     = xhr.status;
2911
						_this.statusText = xhr.statusText;
2912
						_this.readyState = xhr.readyState;
2913
2914
						if( xhr.readyState == 4 ){
2915
							try {
2916
								for( var k in _xhrResponsePostfix ){
2917
									_this['response'+k]  = xhr['response'+k];
2918
								}
2919
							}catch(_){}
2920
							xhr.onreadystatechange = null;
2921
2922
							if (!xhr.status || xhr.status - 201 > 0) {
2923
								api.log("Error: " + xhr.status);
2924
								// some kind of error
2925
								// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2926
								// up - server error
2927
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2928
									// let's try again the same chunk
2929
									// only applicable for recoverable error codes 500 && 416
2930
									var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2931
2932
									// inform about recoverable problems
2933
									options.pause(data.file, options);
2934
2935
									// smart restart if server reports about the last known byte
2936
									api.log("X-Last-Known-Byte: " + lkb);
2937
									if (lkb) {
2938
										data.end = lkb;
2939
									} else {
2940
										data.end = data.start - 1;
2941
										if (416 == xhr.status) {
2942
											data.end = data.end - options.chunkSize;
2943
										}
2944
									}
2945
2946
									setTimeout(function () {
2947
										_this._send(options, data);
2948
									}, delay);
2949
								} else {
2950
									// no mo retries
2951
									_this.end(xhr.status);
2952
								}
2953
							} else {
2954
								// success
2955
								data.retry = 0;
2956
2957
								if (data.end == data.size - 1) {
2958
									// finished
2959
									_this.end(xhr.status);
2960
								} else {
2961
									// next chunk
2962
2963
									// shift position if server reports about the last known byte
2964
									api.log("X-Last-Known-Byte: " + lkb);
2965
									if (lkb) {
2966
										data.end = lkb;
2967
									}
2968
									data.file.FileAPIReadPosition = data.end;
2969
2970
									setTimeout(function () {
2971
										_this._send(options, data);
2972
									}, 0);
2973
								}
2974
							}
2975
2976
							xhr = null;
2977
						}
2978
					};
2979
2980
					data.start = data.end + 1;
2981
					data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2982
2983
					// Retrieve a slice of file
2984
					var
2985
						  file = data.file
2986
						, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2987
					;
2988
2989
					if( data.size && !slice.size ){
2990
						setTimeout(function (){
2991
							_this.end(-1);
2992
						});
2993
					} else {
2994
						xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2995
						xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2996
						xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2997
2998
						xhr.send(slice);
2999
					}
3000
3001
					file = slice = null;
3002
				} else {
3003
					// single piece upload
3004
					if( xhr.upload ){
3005
						// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3006
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3007
							options.progress(evt, _this, options);
3008
						}, 100), false);
3009
					}
3010
3011
					xhr.onreadystatechange = function (){
3012
						_this.status     = xhr.status;
3013
						_this.statusText = xhr.statusText;
3014
						_this.readyState = xhr.readyState;
3015
3016
						if( xhr.readyState == 4 ){
3017
							for( var k in _xhrResponsePostfix ){
3018
								_this['response'+k]  = xhr['response'+k];
3019
							}
3020
							xhr.onreadystatechange = null;
3021
3022
							if (!xhr.status || xhr.status > 201) {
3023
								api.log("Error: " + xhr.status);
3024
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3025
									options.retry = (options.retry || 0) + 1;
3026
									var delay = api.networkDownRetryTimeout;
3027
3028
									// inform about recoverable problems
3029
									options.pause(options.file, options);
3030
3031
									setTimeout(function () {
3032
										_this._send(options, data);
3033
									}, delay);
3034
								} else {
3035
									//success
3036
									_this.end(xhr.status);
3037
								}
3038
							} else {
3039
								//success
3040
								_this.end(xhr.status);
3041
							}
3042
3043
							xhr = null;
3044
						}
3045
					};
3046
3047
					if( api.isArray(data) ){
3048
						// multipart
3049
						xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3050
						var rawData = data.join('') +'--_'+ api.expando +'--';
3051
3052
						/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3053
						if( xhr.sendAsBinary ){
3054
							xhr.sendAsBinary(rawData);
3055
						}
3056
						else {
3057
							var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3058
							xhr.send(new Uint8Array(bytes).buffer);
3059
3060
						}
3061
					} else {
3062
						// FormData
3063
						xhr.send(data);
3064
					}
3065
				}
3066
			}
3067
		}
3068
	};
3069
3070
3071
	// @export
3072
	api.XHR = XHR;
3073
})(window, FileAPI);
3074
3075
/**
3076
 * @class	FileAPI.Camera
3077
 * @author	RubaXa	<[email protected]>
3078
 * @support	Chrome 21+, FF 18+, Opera 12+
3079
 */
3080
3081
/*global window, FileAPI, jQuery */
3082
/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3083
(function (window, api){
3084
	"use strict";
3085
3086
	var
3087
		URL = window.URL || window.webkitURL,
3088
3089
		document = window.document,
3090
		navigator = window.navigator,
3091
3092
		getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3093
3094
		html5 = !!getMedia
3095
	;
3096
3097
3098
	// Support "media"
3099
	api.support.media = html5;
3100
3101
3102
	var Camera = function (video){
3103
		this.video = video;
3104
	};
3105
3106
3107
	Camera.prototype = {
3108
		isActive: function (){
3109
			return	!!this._active;
3110
		},
3111
3112
3113
		/**
3114
		 * Start camera streaming
3115
		 * @param	{Function}	callback
3116
		 */
3117
		start: function (callback){
3118
			var
3119
				  _this = this
3120
				, video = _this.video
3121
				, _successId
3122
				, _failId
3123
				, _complete = function (err){
3124
					_this._active = !err;
3125
					clearTimeout(_failId);
3126
					clearTimeout(_successId);
3127
//					api.event.off(video, 'loadedmetadata', _complete);
3128
					callback && callback(err, _this);
3129
				}
3130
			;
3131
3132
			getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3133
				// Success
3134
				_this.stream = stream;
3135
3136
//				api.event.on(video, 'loadedmetadata', function (){
3137
//					_complete(null);
3138
//				});
3139
3140
				// Set camera stream
3141
				video.src = URL.createObjectURL(stream);
3142
3143
				// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3144
				// See crbug.com/110938.
3145
				_successId = setInterval(function (){
3146
					if( _detectVideoSignal(video) ){
3147
						_complete(null);
3148
					}
3149
				}, 1000);
3150
3151
				_failId = setTimeout(function (){
3152
					_complete('timeout');
3153
				}, 5000);
3154
3155
				// Go-go-go!
3156
				video.play();
3157
			}, _complete/*error*/);
3158
		},
3159
3160
3161
		/**
3162
		 * Stop camera streaming
3163
		 */
3164
		stop: function (){
3165
			try {
3166
				this._active = false;
3167
				this.video.pause();
3168
				this.stream.stop();
3169
			} catch( err ){ }
3170
		},
3171
3172
3173
		/**
3174
		 * Create screenshot
3175
		 * @return {FileAPI.Camera.Shot}
3176
		 */
3177
		shot: function (){
3178
			return	new Shot(this.video);
3179
		}
3180
	};
3181
3182
3183
	/**
3184
	 * Get camera element from container
3185
	 *
3186
	 * @static
3187
	 * @param	{HTMLElement}	el
3188
	 * @return	{Camera}
3189
	 */
3190
	Camera.get = function (el){
3191
		return	new Camera(el.firstChild);
3192
	};
3193
3194
3195
	/**
3196
	 * Publish camera element into container
3197
	 *
3198
	 * @static
3199
	 * @param	{HTMLElement}	el
3200
	 * @param	{Object}		options
3201
	 * @param	{Function}		[callback]
3202
	 */
3203
	Camera.publish = function (el, options, callback){
3204
		if( typeof options == 'function' ){
3205
			callback = options;
3206
			options = {};
3207
		}
3208
3209
		// Dimensions of "camera"
3210
		options = api.extend({}, {
3211
			  width:	'100%'
3212
			, height:	'100%'
3213
			, start:	true
3214
		}, options);
3215
3216
3217
		if( el.jquery ){
3218
			// Extract first element, from jQuery collection
3219
			el = el[0];
3220
		}
3221
3222
3223
		var doneFn = function (err){
3224
			if( err ){
3225
				callback(err);
3226
			}
3227
			else {
3228
				// Get camera
3229
				var cam = Camera.get(el);
3230
				if( options.start ){
3231
					cam.start(callback);
3232
				}
3233
				else {
3234
					callback(null, cam);
3235
				}
3236
			}
3237
		};
3238
3239
3240
		el.style.width	= _px(options.width);
3241
		el.style.height	= _px(options.height);
3242
3243
3244
		if( api.html5 && html5 ){
3245
			// Create video element
3246
			var video = document.createElement('video');
3247
3248
			// Set dimensions
3249
			video.style.width	= _px(options.width);
3250
			video.style.height	= _px(options.height);
3251
3252
			// Clean container
3253
			if( window.jQuery ){
3254
				jQuery(el).empty();
3255
			} else {
3256
				el.innerHTML = '';
3257
			}
3258
3259
			// Add "camera" to container
3260
			el.appendChild(video);
3261
3262
			// end
3263
			doneFn();
3264
		}
3265
		else {
3266
			Camera.fallback(el, options, doneFn);
3267
		}
3268
	};
3269
3270
3271
	Camera.fallback = function (el, options, callback){
3272
		callback('not_support_camera');
3273
	};
3274
3275
3276
	/**
3277
	 * @class	FileAPI.Camera.Shot
3278
	 */
3279
	var Shot = function (video){
3280
		var canvas	= video.nodeName ? api.Image.toCanvas(video) : video;
3281
		var shot	= api.Image(canvas);
3282
		shot.type	= 'image/png';
3283
		shot.width	= canvas.width;
3284
		shot.height	= canvas.height;
3285
		shot.size	= canvas.width * canvas.height * 4;
3286
		return	shot;
3287
	};
3288
3289
3290
	/**
3291
	 * Add "px" postfix, if value is a number
3292
	 *
3293
	 * @private
3294
	 * @param	{*}  val
3295
	 * @return	{String}
3296
	 */
3297
	function _px(val){
3298
		return	val >= 0 ? val + 'px' : val;
3299
	}
3300
3301
3302
	/**
3303
	 * @private
3304
	 * @param	{HTMLVideoElement} video
3305
	 * @return	{Boolean}
3306
	 */
3307
	function _detectVideoSignal(video){
3308
		var canvas = document.createElement('canvas'), ctx, res = false;
3309
		try {
3310
			ctx = canvas.getContext('2d');
3311
			ctx.drawImage(video, 0, 0, 1, 1);
3312
			res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3313
		}
3314
		catch( e ){}
3315
		return	res;
3316
	}
3317
3318
3319
	// @export
3320
	Camera.Shot	= Shot;
3321
	api.Camera	= Camera;
3322
})(window, FileAPI);
3323
3324
/**
3325
 * FileAPI fallback to Flash
3326
 *
3327
 * @flash-developer  "Vladimir Demidov" <[email protected]>
3328
 */
3329
3330
/*global window, ActiveXObject, FileAPI */
3331
(function (window, jQuery, api) {
3332
	"use strict";
3333
3334
	var
3335
		  document = window.document
3336
		, location = window.location
3337
		, navigator = window.navigator
3338
		, _each = api.each
3339
	;
3340
3341
3342
	api.support.flash = (function (){
3343
		var mime = navigator.mimeTypes, has = false;
3344
3345
		if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3346
			has	= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3347
		}
3348
		else {
3349
			try {
3350
				has	= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3351
			}
3352
			catch(er){
3353
				api.log('Flash -- does not supported.');
3354
			}
3355
		}
3356
3357
		if( has && /^file:/i.test(location) ){
3358
			api.log('[warn] Flash does not work on `file:` protocol.');
3359
		}
3360
3361
		return	has;
3362
	})();
3363
3364
3365
	   api.support.flash
3366
	&& (0
3367
		|| !api.html5 || !api.support.html5
3368
		|| (api.cors && !api.support.cors)
3369
		|| (api.media && !api.support.media)
3370
	)
3371
	&& (function (){
3372
		var
3373
			  _attr  = api.uid()
3374
			, _retry = 0
3375
			, _files = {}
3376
			, _rhttp = /^https?:/i
3377
3378
			, flash = {
3379
				_fn: {},
3380
3381
3382
				/**
3383
				 * Publish flash-object
3384
				 *
3385
				 * @param {HTMLElement} el
3386
				 * @param {String} id
3387
				 * @param {Object} [opts]
3388
				 */
3389
				publish: function (el, id, opts){
3390
					opts = opts || {};
3391
					el.innerHTML = _makeFlashHTML({
3392
						id: id
3393
						, src: _getUrl(api.flashUrl, 'r=' + api.version)
3394
//						, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3395
						, wmode: opts.camera ? '' : 'transparent'
3396
						, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3397
						+ '&flashId='+ id
3398
						+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3399
						+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3400
						+ '&timeout='+api.flashAbortTimeout
3401
						+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3402
						+ '&debug='+(api.debug?"1":"")
3403
					}, opts);
3404
				},
3405
3406
3407
				/**
3408
				 * Initialization & preload flash object
3409
				 */
3410
				init: function (){
3411
					var child = document.body && document.body.firstChild;
3412
3413
					if( child ){
3414
						do {
3415
							if( child.nodeType == 1 ){
3416
								api.log('FlashAPI.state: awaiting');
3417
3418
								var dummy = document.createElement('div');
3419
3420
								dummy.id = '_' + _attr;
3421
3422
								_css(dummy, {
3423
									  top: 1
3424
									, right: 1
3425
									, width: 5
3426
									, height: 5
3427
									, position: 'absolute'
3428
									, zIndex: 1e6+'' // set max zIndex
3429
								});
3430
3431
								child.parentNode.insertBefore(dummy, child);
3432
								flash.publish(dummy, _attr);
3433
3434
								return;
3435
							}
3436
						}
3437
						while( child = child.nextSibling );
3438
					}
3439
3440
					if( _retry < 10 ){
3441
						setTimeout(flash.init, ++_retry*50);
3442
					}
3443
				},
3444
3445
3446
				ready: function (){
3447
					api.log('FlashAPI.state: ready');
3448
3449
					flash.ready = api.F;
3450
					flash.isReady = true;
3451
					flash.patch();
3452
					flash.patchCamera && flash.patchCamera();
3453
					api.event.on(document, 'mouseover', flash.mouseover);
3454
					api.event.on(document, 'click', function (evt){
3455
						if( flash.mouseover(evt) ){
3456
							evt.preventDefault
3457
								? evt.preventDefault()
3458
								: (evt.returnValue = true)
3459
							;
3460
						}
3461
					});
3462
				},
3463
3464
3465
				getEl: function (){
3466
					return	document.getElementById('_'+_attr);
3467
				},
3468
3469
3470
				getWrapper: function (node){
3471
					do {
3472
						if( /js-fileapi-wrapper/.test(node.className) ){
3473
							return	node;
3474
						}
3475
					}
3476
					while( (node = node.parentNode) && (node !== document.body) );
3477
				},
3478
				
3479
				disableMouseover: false,
3480
3481
				mouseover: function (evt){
3482
					if (!flash.disableMouseover) {
3483
						var target = api.event.fix(evt).target;
3484
	
3485
						if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3486
							var
3487
								  state = target.getAttribute(_attr)
3488
								, wrapper = flash.getWrapper(target)
3489
							;
3490
	
3491
							if( api.multiFlash ){
3492
								// check state:
3493
								//   i — published
3494
								//   i — initialization
3495
								//   r — ready
3496
								if( state == 'i' || state == 'r' ){
3497
									// publish fail
3498
									return	false;
3499
								}
3500
								else if( state != 'p' ){
3501
									// set "init" state
3502
									target.setAttribute(_attr, 'i');
3503
	
3504
									var dummy = document.createElement('div');
3505
	
3506
									if( !wrapper ){
3507
										api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3508
										return;
3509
									}
3510
	
3511
									_css(dummy, {
3512
										  top:    0
3513
										, left:   0
3514
										, width:  target.offsetWidth
3515
										, height: target.offsetHeight
3516
										, zIndex: 1e6+'' // set max zIndex
3517
										, position: 'absolute'
3518
									});
3519
	
3520
									wrapper.appendChild(dummy);
3521
									flash.publish(dummy, api.uid());
3522
	
3523
									// set "publish" state
3524
									target.setAttribute(_attr, 'p');
3525
								}
3526
	
3527
								return	true;
3528
							}
3529
							else if( wrapper ){
3530
								// Use one flash element
3531
								var box = _getDimensions(wrapper);
3532
								_css(flash.getEl(), box);
3533
	
3534
								// Set current input
3535
								flash.curInp = target;
3536
							}
3537
						}
3538
						else if( !/object|embed/i.test(target.nodeName) ){
3539
							_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3540
						}
3541
					}
3542
				},
3543
3544
				onEvent: function (evt){
3545
					var type = evt.type;
3546
					
3547
					if( type == 'ready' ){
3548
						try {
3549
							// set "ready" state
3550
							flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3551
						} catch (e){
3552
						}
3553
3554
						flash.ready();
3555
						setTimeout(function (){ flash.mouseenter(evt); }, 50);
3556
						return	true;
3557
					}
3558
					else if( type === 'ping' ){
3559
						api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3560
					}
3561
					else if( type === 'log' ){
3562
						api.log('(flash -> js).log:', evt.target);
3563
					}
3564
					else if( type in flash ){
3565
						setTimeout(function (){
3566
							api.log('FlashAPI.event.'+evt.type+':', evt);
3567
							flash[type](evt);
3568
						}, 1);
3569
					}
3570
				},
3571
				mouseDown: function(evt) {
3572
					flash.disableMouseover = true;
3573
				},
3574
				cancel: function(evt) {
3575
					flash.disableMouseover = false;
3576
				},
3577
				mouseenter: function (evt){
3578
					var node = flash.getInput(evt.flashId);
3579
3580
					if( node ){
3581
						// Set multiple mode
3582
						flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3583
3584
3585
						// Set files filter
3586
						var accept = [], exts = {};
3587
3588
						_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3589
							api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3590
								exts[ext] = 1;
3591
							});
3592
						});
3593
3594
						_each(exts, function (i, ext){
3595
							accept.push( ext );
3596
						});
3597
3598
						flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3599
					}
3600
				},
3601
3602
3603
				get: function (id){
3604
					return	document[id] || window[id] || document.embeds[id];
3605
				},
3606
3607
3608
				getInput: function (id){
3609
					if( api.multiFlash ){
3610
						try {
3611
							var node = flash.getWrapper(flash.get(id));
3612
							if( node ){
3613
								return node.getElementsByTagName('input')[0];
3614
							}
3615
						} catch (e){
3616
							api.log('[err] Can not find "input" by flashId:', id, e);
3617
						}
3618
					} else {
3619
						return	flash.curInp;
3620
					}
3621
				},
3622
3623
3624
				select: function (evt){
3625
					try {
3626
						var
3627
							  inp = flash.getInput(evt.flashId)
3628
							, uid = api.uid(inp)
3629
							, files = evt.target.files
3630
							, event
3631
						;
3632
						_each(files, function (file){
3633
							api.checkFileObj(file);
3634
						});
3635
	
3636
						_files[uid] = files;
3637
	
3638
						if( document.createEvent ){
3639
							event = document.createEvent('Event');
3640
							event.files = files;
3641
							event.initEvent('change', true, true);
3642
							inp.dispatchEvent(event);
3643
						}
3644
						else if( jQuery ){
3645
							jQuery(inp).trigger({ type: 'change', files: files });
3646
						}
3647
						else {
3648
							event = document.createEventObject();
3649
							event.files = files;
3650
							inp.fireEvent('onchange', event);
3651
						}
3652
					} finally {
3653
						flash.disableMouseover = false;
3654
					}
3655
				},
3656
3657
				interval: null,
3658
				cmd: function (id, name, data, last) {
3659
					if (flash.uploadInProgress && flash.readInProgress) {
3660
						setTimeout(function() {
3661
							flash.cmd(id, name, data, last);
3662
						}, 100);
3663
					} else {
3664
						this.cmdFn(id, name, data, last);
3665
					}
3666
				},
3667
				
3668
				cmdFn: function(id, name, data, last) {
3669
					try {
3670
						api.log('(js -> flash).'+name+':', data);
3671
						return flash.get(id.flashId || id).cmd(name, data);
3672
					} catch (e){
3673
						api.log('(js -> flash).onError:', e);
3674
						if( !last ){
3675
							// try again
3676
							setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3677
						}
3678
					}
3679
				},
3680
3681
				patch: function (){
3682
					api.flashEngine = true;
3683
3684
					// FileAPI
3685
					_inherit(api, {
3686
						readAsDataURL: function (file, callback){
3687
							if( _isHtmlFile(file) ){
3688
								this.parent.apply(this, arguments);
3689
							}
3690
							else {
3691
								api.log('FlashAPI.readAsBase64');
3692
								flash.readInProgress = true;
3693
								flash.cmd(file, 'readAsBase64', {
3694
									id: file.id,
3695
									callback: _wrap(function _(err, base64){
3696
										flash.readInProgress = false;
3697
										_unwrap(_);
3698
3699
										api.log('FlashAPI.readAsBase64:', err);
3700
3701
										callback({
3702
											  type: err ? 'error' : 'load'
3703
											, error: err
3704
											, result: 'data:'+ file.type +';base64,'+ base64
3705
										});
3706
									})
3707
								});
3708
							}
3709
						},
3710
3711
						readAsText: function (file, encoding, callback){
3712
							if( callback ){
3713
								api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3714
							} else {
3715
								callback = encoding;
3716
							}
3717
3718
							api.readAsDataURL(file, function (evt){
3719
								if( evt.type == 'load' ){
3720
									try {
3721
										evt.result = window.atob(evt.result.split(';base64,')[1]);
3722
									} catch( err ){
3723
										evt.type = 'error';
3724
										evt.error = err.toString();
3725
									}
3726
								}
3727
								callback(evt);
3728
							});
3729
						},
3730
						
3731
						getFiles: function (input, filter, callback){
3732
							if( callback ){
3733
								api.filterFiles(api.getFiles(input), filter, callback);
3734
								return null;
3735
							}
3736
3737
							var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3738
3739
3740
							if( !files ){
3741
								// Файлов нету, вызываем родительский метод
3742
								return	this.parent.apply(this, arguments);
3743
							}
3744
3745
3746
							if( filter ){
3747
								filter	= api.getFilesFilter(filter);
3748
								files	= api.filter(files, function (file){ return filter.test(file.name); });
3749
							}
3750
3751
							return	files;
3752
						},
3753
3754
3755
						getInfo: function (file, fn){
3756
							if( _isHtmlFile(file) ){
3757
								this.parent.apply(this, arguments);
3758
							}
3759
							else if( file.isShot ){
3760
								fn(null, file.info = {
3761
									width: file.width,
3762
									height: file.height
3763
								});
3764
							}
3765
							else {
3766
								if( !file.__info ){
3767
									var defer = file.__info = api.defer();
3768
3769
//									flash.cmd(file, 'getFileInfo', {
3770
//										  id: file.id
3771
//										, callback: _wrap(function _(err, info){
3772
//											_unwrap(_);
3773
//											defer.resolve(err, file.info = info);
3774
//										})
3775
//									});
3776
									defer.resolve(null, file.info = null);
3777
3778
								}
3779
3780
								file.__info.then(fn);
3781
							}
3782
						}
3783
					});
3784
3785
3786
					// FileAPI.Image
3787
					api.support.transform = true;
3788
					api.Image && _inherit(api.Image.prototype, {
3789
						get: function (fn, scaleMode){
3790
							this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3791
							return this.parent(fn);
3792
						},
3793
3794
						_load: function (file, fn){
3795
							api.log('FlashAPI.Image._load:', file);
3796
3797
							if( _isHtmlFile(file) ){
3798
								this.parent.apply(this, arguments);
3799
							}
3800
							else {
3801
								var _this = this;
3802
								api.getInfo(file, function (err){
3803
									fn.call(_this, err, file);
3804
								});
3805
							}
3806
						},
3807
3808
						_apply: function (file, fn){
3809
							api.log('FlashAPI.Image._apply:', file);
3810
3811
							if( _isHtmlFile(file) ){
3812
								this.parent.apply(this, arguments);
3813
							}
3814
							else {
3815
								var m = this.getMatrix(file.info), doneFn = fn;
3816
3817
								flash.cmd(file, 'imageTransform', {
3818
									  id: file.id
3819
									, matrix: m
3820
									, callback: _wrap(function _(err, base64){
3821
										api.log('FlashAPI.Image._apply.callback:', err);
3822
										_unwrap(_);
3823
3824
										if( err ){
3825
											doneFn(err);
3826
										}
3827
										else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3828
											_makeFlashImage({
3829
												  width:	(m.deg % 180) ? m.dh : m.dw
3830
												, height:	(m.deg % 180) ? m.dw : m.dh
3831
												, scale:	m.scaleMode
3832
											}, base64, doneFn);
3833
										}
3834
										else {
3835
											if( m.filter ){
3836
												doneFn = function (err, img){
3837
													if( err ){
3838
														fn(err);
3839
													}
3840
													else {
3841
														api.Image.applyFilter(img, m.filter, function (){
3842
															fn(err, this.canvas);
3843
														});
3844
													}
3845
												};
3846
											}
3847
3848
											api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3849
										}
3850
									})
3851
								});
3852
							}
3853
						},
3854
3855
						toData: function (fn){
3856
							var
3857
								  file = this.file
3858
								, info = file.info
3859
								, matrix = this.getMatrix(info)
3860
							;
3861
							api.log('FlashAPI.Image.toData');
3862
3863
							if( _isHtmlFile(file) ){
3864
								this.parent.apply(this, arguments);
3865
							}
3866
							else {
3867
								if( matrix.deg == 'auto' ){
3868
									matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3869
								}
3870
3871
								fn.call(this, !file.info, {
3872
									  id:		file.id
3873
									, flashId:	file.flashId
3874
									, name:		file.name
3875
									, type:		file.type
3876
									, matrix:	matrix
3877
								});
3878
							}
3879
						}
3880
					});
3881
3882
3883
					api.Image && _inherit(api.Image, {
3884
						fromDataURL: function (dataURL, size, callback){
3885
							if( !api.support.dataURI || dataURL.length > 3e4 ){
3886
								_makeFlashImage(
3887
									  api.extend({ scale: 'exactFit' }, size)
3888
									, dataURL.replace(/^data:[^,]+,/, '')
3889
									, function (err, el){ callback(el); }
3890
								);
3891
							}
3892
							else {
3893
								this.parent(dataURL, size, callback);
3894
							}
3895
						}
3896
					});
3897
3898
					// FileAPI.Form
3899
					_inherit(api.Form.prototype, {
3900
						toData: function (fn){
3901
							var items = this.items, i = items.length;
3902
3903
							for( ; i--; ){
3904
								if( items[i].file && _isHtmlFile(items[i].blob) ){
3905
									return this.parent.apply(this, arguments);
3906
								}
3907
							}
3908
3909
							api.log('FlashAPI.Form.toData');
3910
							fn(items);
3911
						}
3912
					});
3913
3914
3915
					// FileAPI.XHR
3916
					_inherit(api.XHR.prototype, {
3917
						_send: function (options, formData){
3918
							if(
3919
								   formData.nodeName
3920
								|| formData.append && api.support.html5
3921
								|| api.isArray(formData) && (typeof formData[0] === 'string')
3922
							){
3923
								// HTML5, Multipart or IFrame
3924
								return	this.parent.apply(this, arguments);
3925
							}
3926
3927
3928
							var
3929
								  data = {}
3930
								, files = {}
3931
								, _this = this
3932
								, flashId
3933
								, fileId
3934
							;
3935
3936
							_each(formData, function (item){
3937
								if( item.file ){
3938
									files[item.name] = item = _getFileDescr(item.blob);
3939
									fileId  = item.id;
3940
									flashId = item.flashId;
3941
								}
3942
								else {
3943
									data[item.name] = item.blob;
3944
								}
3945
							});
3946
3947
							if( !fileId ){
3948
								flashId = _attr;
3949
							}
3950
3951
							if( !flashId ){
3952
								api.log('[err] FlashAPI._send: flashId -- undefined');
3953
								return this.parent.apply(this, arguments);
3954
							}
3955
							else {
3956
								api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3957
							}
3958
3959
							_this.xhr = {
3960
								headers: {},
3961
								abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3962
								getResponseHeader: function (name){ return this.headers[name]; },
3963
								getAllResponseHeaders: function (){ return this.headers; }
3964
							};
3965
3966
							var queue = api.queue(function (){
3967
								flash.uploadInProgress = true;
3968
								flash.cmd(flashId, 'upload', {
3969
									  url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3970
									, data: data
3971
									, files: fileId ? files : null
3972
									, headers: options.headers || {}
3973
									, callback: _wrap(function upload(evt){
3974
										var type = evt.type, result = evt.result;
3975
3976
										api.log('FlashAPI.upload.'+type);
3977
3978
										if( type == 'progress' ){
3979
											evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3980
											evt.lengthComputable = true;
3981
											options.progress(evt);
3982
										}
3983
										else if( type == 'complete' ){
3984
											flash.uploadInProgress = false;
3985
											_unwrap(upload);
3986
3987
											if( typeof result == 'string' ){
3988
												_this.responseText	= result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3989
											}
3990
3991
											_this.end(evt.status || 200);
3992
										}
3993
										else if( type == 'abort' || type == 'error' ){
3994
											flash.uploadInProgress = false;
3995
											_this.end(evt.status || 0, evt.message);
3996
											_unwrap(upload);
3997
										}
3998
									})
3999
								});
4000
							});
4001
4002
4003
							// #2174: FileReference.load() call while FileReference.upload() or vice versa
4004
							_each(files, function (file){
4005
								queue.inc();
4006
								api.getInfo(file, queue.next);
4007
							});
4008
4009
							queue.check();
4010
						}
4011
					});
4012
				}
4013
			}
4014
		;
4015
4016
4017
		function _makeFlashHTML(opts){
4018
			return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4019
				+ '<param name="movie" value="#src#" />'
4020
				+ '<param name="flashvars" value="#flashvars#" />'
4021
				+ '<param name="swliveconnect" value="true" />'
4022
				+ '<param name="allowscriptaccess" value="always" />'
4023
				+ '<param name="allownetworking" value="all" />'
4024
				+ '<param name="menu" value="false" />'
4025
				+ '<param name="wmode" value="#wmode#" />'
4026
				+ '<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>'
4027
				+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4028
			;
4029
		}
4030
4031
4032
		function _css(el, css){
4033
			if( el && el.style ){
4034
				var key, val;
4035
				for( key in css ){
4036
					val = css[key];
4037
					if( typeof val == 'number' ){
4038
						val += 'px';
4039
					}
4040
					try { el.style[key] = val; } catch (e) {}
4041
				}
4042
				
4043
			}
4044
		}
4045
4046
4047
		function _inherit(obj, methods){
4048
			_each(methods, function (fn, name){
4049
				var prev = obj[name];
4050
				obj[name] = function (){
4051
					this.parent = prev;
4052
					return fn.apply(this, arguments);
4053
				};
4054
			});
4055
		}
4056
4057
		function _isHtmlFile(file){
4058
			return	file && !file.flashId;
4059
		}
4060
4061
		function _wrap(fn){
4062
			var id = fn.wid = api.uid();
4063
			flash._fn[id] = fn;
4064
			return	'FileAPI.Flash._fn.'+id;
4065
		}
4066
4067
4068
		function _unwrap(fn){
4069
			try {
4070
				flash._fn[fn.wid] = null;
4071
				delete	flash._fn[fn.wid];
4072
			}
4073
			catch(e){}
4074
		}
4075
4076
4077
		function _getUrl(url, params){
4078
			if( !_rhttp.test(url) ){
4079
				if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4080
					var path = location.pathname;
4081
					path = path.substr(0, path.lastIndexOf('/'));
4082
					url = (path +'/'+ url).replace('/./', '/');
4083
				}
4084
4085
				if( '//' != url.substr(0, 2) ){
4086
					url = '//' + location.host + url;
4087
				}
4088
4089
				if( !_rhttp.test(url) ){
4090
					url = location.protocol + url;
4091
				}
4092
			}
4093
4094
			if( params ){
4095
				url += (/\?/.test(url) ? '&' : '?') + params;
4096
			}
4097
4098
			return	url;
4099
		}
4100
4101
4102
		function _makeFlashImage(opts, base64, fn){
4103
			var
4104
				  key
4105
				, flashId = api.uid()
4106
				, el = document.createElement('div')
4107
				, attempts = 10
4108
			;
4109
4110
			for( key in opts ){
4111
				el.setAttribute(key, opts[key]);
4112
				el[key] = opts[key];
4113
			}
4114
4115
			_css(el, opts);
4116
4117
			opts.width	= '100%';
4118
			opts.height	= '100%';
4119
4120
			el.innerHTML = _makeFlashHTML(api.extend({
4121
				  id: flashId
4122
				, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4123
				, wmode: 'opaque'
4124
				, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4125
					_unwrap(_);
4126
					if( --attempts > 0 ){
4127
						_setImage();
4128
					}
4129
					return true;
4130
				})
4131
			}, opts));
4132
4133
			function _setImage(){
4134
				try {
4135
					// Get flash-object by id
4136
					var img = flash.get(flashId);
4137
					img.setImage(base64);
4138
				} catch (e){
4139
					api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4140
				}
4141
			}
4142
4143
			fn(false, el);
4144
			el = null;
4145
		}
4146
4147
4148
		function _getFileDescr(file){
4149
			return	{
4150
				  id: file.id
4151
				, name: file.name
4152
				, matrix: file.matrix
4153
				, flashId: file.flashId
4154
			};
4155
		}
4156
4157
4158
		function _getDimensions(el){
4159
			var
4160
				  box = el.getBoundingClientRect()
4161
				, body = document.body
4162
				, docEl = (el && el.ownerDocument).documentElement
4163
			;
4164
			
4165
			function getOffset(obj) {
4166
			    var left, top;
4167
			    left = top = 0;
4168
			    if (obj.offsetParent) {
4169
			        do {
4170
			            left += obj.offsetLeft;
4171
			            top  += obj.offsetTop;
4172
			        } while (obj = obj.offsetParent);
4173
			    }
4174
			    return {
4175
			        left : left,
4176
			        top : top
4177
			    };
4178
			};
4179
			
4180
			return {
4181
				  top:		getOffset(el).top
4182
				, left:		getOffset(el).left
4183
				, width:	el.offsetWidth
4184
				, height:	el.offsetHeight
4185
			};
4186
		}
4187
4188
		// @export
4189
		api.Flash = flash;
4190
4191
4192
		// Check dataURI support
4193
		api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4194
			api.support.dataURI = !(img.width != 1 || img.height != 1);
4195
			flash.init();
4196
		});
4197
	})();
4198
})(window, window.jQuery, FileAPI);
4199
4200
/**
4201
 * FileAPI fallback to Flash
4202
 *
4203
 * @flash-developer  "Vladimir Demidov" <[email protected]>
4204
 */
4205
4206
/*global window, FileAPI */
4207
(function (window, jQuery, api) {
4208
    "use strict";
4209
4210
    var _each = api.each,
4211
        _cameraQueue = [];
4212
4213
4214
    if (api.support.flash && (api.media && !api.support.media)) {
4215
        (function () {
4216
4217
            function _wrap(fn) {
4218
                var id = fn.wid = api.uid();
4219
                api.Flash._fn[id] = fn;
4220
                return 'FileAPI.Flash._fn.' + id;
4221
            }
4222
4223
4224
            function _unwrap(fn) {
4225
                try {
4226
                    api.Flash._fn[fn.wid] = null;
4227
                    delete api.Flash._fn[fn.wid];
4228
                } catch (e) {
4229
                }
4230
            }
4231
4232
            var flash = api.Flash;
4233
            api.extend(api.Flash, {
4234
4235
                patchCamera: function () {
4236
                    api.Camera.fallback = function (el, options, callback) {
4237
                        var camId = api.uid();
4238
                        api.log('FlashAPI.Camera.publish: ' + camId);
4239
                        flash.publish(el, camId, api.extend(options, {
4240
                            camera: true,
4241
                            onEvent: _wrap(function _(evt) {
4242
                                if (evt.type === 'camera') {
4243
                                    _unwrap(_);
4244
4245
                                    if (evt.error) {
4246
                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4247
                                        callback(evt.error);
4248
                                    } else {
4249
                                        api.log('FlashAPI.Camera.publish.success: ' + camId);
4250
                                        callback(null);
4251
                                    }
4252
                                }
4253
                            })
4254
                        }));
4255
                    };
4256
                    // Run
4257
                    _each(_cameraQueue, function (args) {
4258
                        api.Camera.fallback.apply(api.Camera, args);
4259
                    });
4260
                    _cameraQueue = [];
4261
4262
4263
                    // FileAPI.Camera:proto
4264
                    api.extend(api.Camera.prototype, {
4265
                        _id: function () {
4266
                            return this.video.id;
4267
                        },
4268
4269
                        start: function (callback) {
4270
                            var _this = this;
4271
                            flash.cmd(this._id(), 'camera.on', {
4272
                                callback: _wrap(function _(evt) {
4273
                                    _unwrap(_);
4274
4275
                                    if (evt.error) {
4276
                                        api.log('FlashAPI.camera.on.error: ' + evt.error);
4277
                                        callback(evt.error, _this);
4278
                                    } else {
4279
                                        api.log('FlashAPI.camera.on.success: ' + _this._id());
4280
                                        _this._active = true;
4281
                                        callback(null, _this);
4282
                                    }
4283
                                })
4284
                            });
4285
                        },
4286
4287
                        stop: function () {
4288
                            this._active = false;
4289
                            flash.cmd(this._id(), 'camera.off');
4290
                        },
4291
4292
                        shot: function () {
4293
                            api.log('FlashAPI.Camera.shot:', this._id());
4294
4295
                            var shot = api.Flash.cmd(this._id(), 'shot', {});
4296
                            shot.type = 'image/png';
4297
                            shot.flashId = this._id();
4298
                            shot.isShot = true;
4299
4300
                            return new api.Camera.Shot(shot);
4301
                        }
4302
                    });
4303
                }
4304
            });
4305
4306
            api.Camera.fallback = function () {
4307
                _cameraQueue.push(arguments);
4308
            };
4309
4310
        }());
4311
    }
4312
}(window, window.jQuery, FileAPI));
4313
if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
4314

third-party/angularjs-modules-plugins/ng-file-upload-5.0.9/demo/src/main/webapp/js/FileAPI.js 1 location

@@ 22-4308 (lines=4287) @@
19
/*jslint nomen: true, regexp: true */
20
/*global window, atob, Blob, ArrayBuffer, Uint8Array */
21
22
(function (window) {
23
    'use strict';
24
    var CanvasPrototype = window.HTMLCanvasElement &&
25
            window.HTMLCanvasElement.prototype,
26
        hasBlobConstructor = window.Blob && (function () {
27
            try {
28
                return Boolean(new Blob());
29
            } catch (e) {
30
                return false;
31
            }
32
        }()),
33
        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
34
            (function () {
35
                try {
36
                    return new Blob([new Uint8Array(100)]).size === 100;
37
                } catch (e) {
38
                    return false;
39
                }
40
            }()),
41
        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
42
            window.MozBlobBuilder || window.MSBlobBuilder,
43
        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
44
            window.ArrayBuffer && window.Uint8Array && function (dataURI) {
45
                var byteString,
46
                    arrayBuffer,
47
                    intArray,
48
                    i,
49
                    mimeString,
50
                    bb;
51
                if (dataURI.split(',')[0].indexOf('base64') >= 0) {
52
                    // Convert base64 to raw binary data held in a string:
53
                    byteString = atob(dataURI.split(',')[1]);
54
                } else {
55
                    // Convert base64/URLEncoded data component to raw binary data:
56
                    byteString = decodeURIComponent(dataURI.split(',')[1]);
57
                }
58
                // Write the bytes of the string to an ArrayBuffer:
59
                arrayBuffer = new ArrayBuffer(byteString.length);
60
                intArray = new Uint8Array(arrayBuffer);
61
                for (i = 0; i < byteString.length; i += 1) {
62
                    intArray[i] = byteString.charCodeAt(i);
63
                }
64
                // Separate out the mime component:
65
                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
66
                // Write the ArrayBuffer (or ArrayBufferView) to a blob:
67
                if (hasBlobConstructor) {
68
                    return new Blob(
69
                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],
70
                        {type: mimeString}
71
                    );
72
                }
73
                bb = new BlobBuilder();
74
                bb.append(arrayBuffer);
75
                return bb.getBlob(mimeString);
76
            };
77
    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
78
        if (CanvasPrototype.mozGetAsFile) {
79
            CanvasPrototype.toBlob = function (callback, type, quality) {
80
                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
81
                    callback(dataURLtoBlob(this.toDataURL(type, quality)));
82
                } else {
83
                    callback(this.mozGetAsFile('blob', type));
84
                }
85
            };
86
        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
87
            CanvasPrototype.toBlob = function (callback, type, quality) {
88
                callback(dataURLtoBlob(this.toDataURL(type, quality)));
89
            };
90
        }
91
    }
92
    window.dataURLtoBlob = dataURLtoBlob;
93
})(window);
94
95
/*jslint evil: true */
96
/*global window, URL, webkitURL, ActiveXObject */
97
98
(function (window, undef){
99
	'use strict';
100
101
	var
102
		gid = 1,
103
		noop = function (){},
104
105
		document = window.document,
106
		doctype = document.doctype || {},
107
		userAgent = window.navigator.userAgent,
108
109
		// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
110
		apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
111
112
		Blob = window.Blob,
113
		File = window.File,
114
		FileReader = window.FileReader,
115
		FormData = window.FormData,
116
117
118
		XMLHttpRequest = window.XMLHttpRequest,
119
		jQuery = window.jQuery,
120
121
		html5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
122
				&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
123
124
		cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
125
126
		chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
127
128
		// https://github.com/blueimp/JavaScript-Canvas-to-Blob
129
		dataURLtoBlob = window.dataURLtoBlob,
130
131
132
		_rimg = /img/i,
133
		_rcanvas = /canvas/i,
134
		_rimgcanvas = /img|canvas/i,
135
		_rinput = /input/i,
136
		_rdata = /^data:[^,]+,/,
137
138
		_toString = {}.toString,
139
140
141
		Math = window.Math,
142
143
		_SIZE_CONST = function (pow){
144
			pow = new window.Number(Math.pow(1024, pow));
145
			pow.from = function (sz){ return Math.round(sz * this); };
146
			return	pow;
147
		},
148
149
		_elEvents = {}, // element event listeners
150
		_infoReader = [], // list of file info processors
151
152
		_readerEvents = 'abort progress error load loadend',
153
		_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
154
155
		currentTarget = 'currentTarget', // for minimize
156
		preventDefault = 'preventDefault', // and this too
157
158
		_isArray = function (ar) {
159
			return	ar && ('length' in ar);
160
		},
161
162
		/**
163
		 * Iterate over a object or array
164
		 */
165
		_each = function (obj, fn, ctx){
166
			if( obj ){
167
				if( _isArray(obj) ){
168
					for( var i = 0, n = obj.length; i < n; i++ ){
169
						if( i in obj ){
170
							fn.call(ctx, obj[i], i, obj);
171
						}
172
					}
173
				}
174
				else {
175
					for( var key in obj ){
176
						if( obj.hasOwnProperty(key) ){
177
							fn.call(ctx, obj[key], key, obj);
178
						}
179
					}
180
				}
181
			}
182
		},
183
184
		/**
185
		 * Merge the contents of two or more objects together into the first object
186
		 */
187
		_extend = function (dst){
188
			var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
189
			for( ; i < args.length; i++ ){
190
				_each(args[i], _ext);
191
			}
192
			return  dst;
193
		},
194
195
		/**
196
		 * Add event listener
197
		 */
198
		_on = function (el, type, fn){
199
			if( el ){
200
				var uid = api.uid(el);
201
202
				if( !_elEvents[uid] ){
203
					_elEvents[uid] = {};
204
				}
205
206
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
207
				_each(type.split(/\s+/), function (type){
208
					if( jQuery && !isFileReader){
209
						jQuery.event.add(el, type, fn);
210
					} else {
211
						if( !_elEvents[uid][type] ){
212
							_elEvents[uid][type] = [];
213
						}
214
215
						_elEvents[uid][type].push(fn);
216
217
						if( el.addEventListener ){ el.addEventListener(type, fn, false); }
218
						else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
219
						else { el['on'+type] = fn; }
220
					}
221
				});
222
			}
223
		},
224
225
226
		/**
227
		 * Remove event listener
228
		 */
229
		_off = function (el, type, fn){
230
			if( el ){
231
				var uid = api.uid(el), events = _elEvents[uid] || {};
232
233
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
234
				_each(type.split(/\s+/), function (type){
235
					if( jQuery && !isFileReader){
236
						jQuery.event.remove(el, type, fn);
237
					}
238
					else {
239
						var fns = events[type] || [], i = fns.length;
240
241
						while( i-- ){
242
							if( fns[i] === fn ){
243
								fns.splice(i, 1);
244
								break;
245
							}
246
						}
247
248
						if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
249
						else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
250
						else { el['on'+type] = null; }
251
					}
252
				});
253
			}
254
		},
255
256
257
		_one = function(el, type, fn){
258
			_on(el, type, function _(evt){
259
				_off(el, type, _);
260
				fn(evt);
261
			});
262
		},
263
264
265
		_fixEvent = function (evt){
266
			if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
267
			if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
268
			return  evt;
269
		},
270
271
272
		_supportInputAttr = function (attr){
273
			var input = document.createElement('input');
274
			input.setAttribute('type', "file");
275
			return attr in input;
276
		},
277
278
279
		/**
280
		 * FileAPI (core object)
281
		 */
282
		api = {
283
			version: '2.0.7',
284
285
			cors: false,
286
			html5: true,
287
			media: false,
288
			formData: true,
289
			multiPassResize: true,
290
291
			debug: false,
292
			pingUrl: false,
293
			multiFlash: false,
294
			flashAbortTimeout: 0,
295
			withCredentials: true,
296
297
			staticPath: './dist/',
298
299
			flashUrl: 0, // @default: './FileAPI.flash.swf'
300
			flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
301
302
			postNameConcat: function (name, idx){
303
				return	name + (idx != null ? '['+ idx +']' : '');
304
			},
305
306
			ext2mime: {
307
				  jpg:	'image/jpeg'
308
				, tif:	'image/tiff'
309
				, txt:	'text/plain'
310
			},
311
312
			// Fallback for flash
313
			accept: {
314
				  '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'
315
				, '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'
316
				, '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'
317
			},
318
319
			uploadRetry : 0,
320
			networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
321
322
			chunkSize : 0,
323
			chunkUploadRetry : 0,
324
			chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
325
326
			KB: _SIZE_CONST(1),
327
			MB: _SIZE_CONST(2),
328
			GB: _SIZE_CONST(3),
329
			TB: _SIZE_CONST(4),
330
331
			EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
332
333
			expando: 'fileapi' + (new Date).getTime(),
334
335
			uid: function (obj){
336
				return	obj
337
					? (obj[api.expando] = obj[api.expando] || api.uid())
338
					: (++gid, api.expando + gid)
339
				;
340
			},
341
342
			log: function (){
343
				if( api.debug && window.console && console.log ){
344
					if( console.log.apply ){
345
						console.log.apply(console, arguments);
346
					}
347
					else {
348
						console.log([].join.call(arguments, ' '));
349
					}
350
				}
351
			},
352
353
			/**
354
			 * Create new image
355
			 *
356
			 * @param {String} [src]
357
			 * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element
358
			 * @returns {HTMLElement}
359
			 */
360
			newImage: function (src, fn){
361
				var img = document.createElement('img');
362
				if( fn ){
363
					api.event.one(img, 'error load', function (evt){
364
						fn(evt.type == 'error', img);
365
						img = null;
366
					});
367
				}
368
				img.src = src;
369
				return	img;
370
			},
371
372
			/**
373
			 * Get XHR
374
			 * @returns {XMLHttpRequest}
375
			 */
376
			getXHR: function (){
377
				var xhr;
378
379
				if( XMLHttpRequest ){
380
					xhr = new XMLHttpRequest;
381
				}
382
				else if( window.ActiveXObject ){
383
					try {
384
						xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
385
					} catch (e) {
386
						xhr = new ActiveXObject('Microsoft.XMLHTTP');
387
					}
388
				}
389
390
				return  xhr;
391
			},
392
393
			isArray: _isArray,
394
395
			support: {
396
				dnd:     cors && ('ondrop' in document.createElement('div')),
397
				cors:    cors,
398
				html5:   html5,
399
				chunked: chunked,
400
				dataURI: true,
401
				accept:   _supportInputAttr('accept'),
402
				multiple: _supportInputAttr('multiple')
403
			},
404
405
			event: {
406
				  on: _on
407
				, off: _off
408
				, one: _one
409
				, fix: _fixEvent
410
			},
411
412
413
			throttle: function(fn, delay) {
414
				var id, args;
415
416
				return function _throttle(){
417
					args = arguments;
418
419
					if( !id ){
420
						fn.apply(window, args);
421
						id = setTimeout(function (){
422
							id = 0;
423
							fn.apply(window, args);
424
						}, delay);
425
					}
426
				};
427
			},
428
429
430
			F: function (){},
431
432
433
			parseJSON: function (str){
434
				var json;
435
				if( window.JSON && JSON.parse ){
436
					json = JSON.parse(str);
437
				}
438
				else {
439
					json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
440
				}
441
				return json;
442
			},
443
444
445
			trim: function (str){
446
				str = String(str);
447
				return	str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
448
			},
449
450
			/**
451
			 * Simple Defer
452
			 * @return	{Object}
453
			 */
454
			defer: function (){
455
				var
456
					  list = []
457
					, result
458
					, error
459
					, defer = {
460
						resolve: function (err, res){
461
							defer.resolve = noop;
462
							error	= err || false;
463
							result	= res;
464
465
							while( res = list.shift() ){
466
								res(error, result);
467
							}
468
						},
469
470
						then: function (fn){
471
							if( error !== undef ){
472
								fn(error, result);
473
							} else {
474
								list.push(fn);
475
							}
476
						}
477
				};
478
479
				return	defer;
480
			},
481
482
			queue: function (fn){
483
				var
484
					  _idx = 0
485
					, _length = 0
486
					, _fail = false
487
					, _end = false
488
					, queue = {
489
						inc: function (){
490
							_length++;
491
						},
492
493
						next: function (){
494
							_idx++;
495
							setTimeout(queue.check, 0);
496
						},
497
498
						check: function (){
499
							(_idx >= _length) && !_fail && queue.end();
500
						},
501
502
						isFail: function (){
503
							return _fail;
504
						},
505
506
						fail: function (){
507
							!_fail && fn(_fail = true);
508
						},
509
510
						end: function (){
511
							if( !_end ){
512
								_end = true;
513
								fn();
514
							}
515
						}
516
					}
517
				;
518
				return queue;
519
			},
520
521
522
			/**
523
			 * For each object
524
			 *
525
			 * @param	{Object|Array}	obj
526
			 * @param	{Function}		fn
527
			 * @param	{*}				[ctx]
528
			 */
529
			each: _each,
530
531
532
			/**
533
			 * Async for
534
			 * @param {Array} array
535
			 * @param {Function} callback
536
			 */
537
			afor: function (array, callback){
538
				var i = 0, n = array.length;
539
540
				if( _isArray(array) && n-- ){
541
					(function _next(){
542
						callback(n != i && _next, array[i], i++);
543
					})();
544
				}
545
				else {
546
					callback(false);
547
				}
548
			},
549
550
551
			/**
552
			 * Merge the contents of two or more objects together into the first object
553
			 *
554
			 * @param	{Object}	dst
555
			 * @return	{Object}
556
			 */
557
			extend: _extend,
558
559
560
			/**
561
			 * Is file?
562
			 * @param  {File}  file
563
			 * @return {Boolean}
564
			 */
565
			isFile: function (file){
566
				return _toString.call(file) === '[object File]';
567
			},
568
569
570
			/**
571
			 * Is blob?
572
			 * @param   {Blob}  blob
573
			 * @returns {Boolean}
574
			 */
575
			isBlob: function (blob) {
576
				return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
577
			},
578
579
580
			/**
581
			 * Is canvas element
582
			 *
583
			 * @param	{HTMLElement}	el
584
			 * @return	{Boolean}
585
			 */
586
			isCanvas: function (el){
587
				return	el && _rcanvas.test(el.nodeName);
588
			},
589
590
591
			getFilesFilter: function (filter){
592
				filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
593
				return	filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
594
			},
595
596
597
598
			/**
599
			 * Read as DataURL
600
			 *
601
			 * @param {File|Element} file
602
			 * @param {Function} fn
603
			 */
604
			readAsDataURL: function (file, fn){
605
				if( api.isCanvas(file) ){
606
					_emit(file, fn, 'load', api.toDataURL(file));
607
				}
608
				else {
609
					_readAs(file, fn, 'DataURL');
610
				}
611
			},
612
613
614
			/**
615
			 * Read as Binary string
616
			 *
617
			 * @param {File} file
618
			 * @param {Function} fn
619
			 */
620
			readAsBinaryString: function (file, fn){
621
				if( _hasSupportReadAs('BinaryString') ){
622
					_readAs(file, fn, 'BinaryString');
623
				} else {
624
					// Hello IE10!
625
					_readAs(file, function (evt){
626
						if( evt.type == 'load' ){
627
							try {
628
								// dataURL -> binaryString
629
								evt.result = api.toBinaryString(evt.result);
630
							} catch (e){
631
								evt.type = 'error';
632
								evt.message = e.toString();
633
							}
634
						}
635
						fn(evt);
636
					}, 'DataURL');
637
				}
638
			},
639
640
641
			/**
642
			 * Read as ArrayBuffer
643
			 *
644
			 * @param {File} file
645
			 * @param {Function} fn
646
			 */
647
			readAsArrayBuffer: function(file, fn){
648
				_readAs(file, fn, 'ArrayBuffer');
649
			},
650
651
652
			/**
653
			 * Read as text
654
			 *
655
			 * @param {File} file
656
			 * @param {String} encoding
657
			 * @param {Function} [fn]
658
			 */
659
			readAsText: function(file, encoding, fn){
660
				if( !fn ){
661
					fn	= encoding;
662
					encoding = 'utf-8';
663
				}
664
665
				_readAs(file, fn, 'Text', encoding);
666
			},
667
668
669
			/**
670
			 * Convert image or canvas to DataURL
671
			 *
672
			 * @param   {Element}  el      Image or Canvas element
673
			 * @param   {String}   [type]  mime-type
674
			 * @return  {String}
675
			 */
676
			toDataURL: function (el, type){
677
				if( typeof el == 'string' ){
678
					return  el;
679
				}
680
				else if( el.toDataURL ){
681
					return  el.toDataURL(type || 'image/png');
682
				}
683
			},
684
685
686
			/**
687
			 * Canvert string, image or canvas to binary string
688
			 *
689
			 * @param   {String|Element} val
690
			 * @return  {String}
691
			 */
692
			toBinaryString: function (val){
693
				return  window.atob(api.toDataURL(val).replace(_rdata, ''));
694
			},
695
696
697
			/**
698
			 * Read file or DataURL as ImageElement
699
			 *
700
			 * @param	{File|String}	file
701
			 * @param	{Function}		fn
702
			 * @param	{Boolean}		[progress]
703
			 */
704
			readAsImage: function (file, fn, progress){
705
				if( api.isFile(file) ){
706
					if( apiURL ){
707
						/** @namespace apiURL.createObjectURL */
708
						var data = apiURL.createObjectURL(file);
709
						if( data === undef ){
710
							_emit(file, fn, 'error');
711
						}
712
						else {
713
							api.readAsImage(data, fn, progress);
714
						}
715
					}
716
					else {
717
						api.readAsDataURL(file, function (evt){
718
							if( evt.type == 'load' ){
719
								api.readAsImage(evt.result, fn, progress);
720
							}
721
							else if( progress || evt.type == 'error' ){
722
								_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
723
							}
724
						});
725
					}
726
				}
727
				else if( api.isCanvas(file) ){
728
					_emit(file, fn, 'load', file);
729
				}
730
				else if( _rimg.test(file.nodeName) ){
731
					if( file.complete ){
732
						_emit(file, fn, 'load', file);
733
					}
734
					else {
735
						var events = 'error abort load';
736
						_one(file, events, function _fn(evt){
737
							if( evt.type == 'load' && apiURL ){
738
								/** @namespace apiURL.revokeObjectURL */
739
								apiURL.revokeObjectURL(file.src);
740
							}
741
742
							_off(file, events, _fn);
743
							_emit(file, fn, evt, file);
744
						});
745
					}
746
				}
747
				else if( file.iframe ){
748
					_emit(file, fn, { type: 'error' });
749
				}
750
				else {
751
					// Created image
752
					var img = api.newImage(file.dataURL || file);
753
					api.readAsImage(img, fn, progress);
754
				}
755
			},
756
757
758
			/**
759
			 * Make file by name
760
			 *
761
			 * @param	{String}	name
762
			 * @return	{Array}
763
			 */
764
			checkFileObj: function (name){
765
				var file = {}, accept = api.accept;
766
767
				if( typeof name == 'object' ){
768
					file = name;
769
				}
770
				else {
771
					file.name = (name + '').split(/\\|\//g).pop();
772
				}
773
774
				if( file.type == null ){
775
					file.type = file.name.split('.').pop();
776
				}
777
778
				_each(accept, function (ext, type){
779
					ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
780
					if( ext.test(file.type) || api.ext2mime[file.type] ){
781
						file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
782
					}
783
				});
784
785
				return	file;
786
			},
787
788
789
			/**
790
			 * Get drop files
791
			 *
792
			 * @param	{Event}	evt
793
			 * @param	{Function} callback
794
			 */
795
			getDropFiles: function (evt, callback){
796
				var
797
					  files = []
798
					, dataTransfer = _getDataTransfer(evt)
799
					, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
800
					, queue = api.queue(function (){ callback(files); })
801
				;
802
803
				_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
804
					queue.inc();
805
806
					try {
807
						if( entrySupport ){
808
							_readEntryAsFiles(item, function (err, entryFiles){
809
								if( err ){
810
									api.log('[err] getDropFiles:', err);
811
								} else {
812
									files.push.apply(files, entryFiles);
813
								}
814
								queue.next();
815
							});
816
						}
817
						else {
818
							_isRegularFile(item, function (yes){
819
								yes && files.push(item);
820
								queue.next();
821
							});
822
						}
823
					}
824
					catch( err ){
825
						queue.next();
826
						api.log('[err] getDropFiles: ', err);
827
					}
828
				});
829
830
				queue.check();
831
			},
832
833
834
			/**
835
			 * Get file list
836
			 *
837
			 * @param	{HTMLInputElement|Event}	input
838
			 * @param	{String|Function}	[filter]
839
			 * @param	{Function}			[callback]
840
			 * @return	{Array|Null}
841
			 */
842
			getFiles: function (input, filter, callback){
843
				var files = [];
844
845
				if( callback ){
846
					api.filterFiles(api.getFiles(input), filter, callback);
847
					return null;
848
				}
849
850
				if( input.jquery ){
851
					// jQuery object
852
					input.each(function (){
853
						files = files.concat(api.getFiles(this));
854
					});
855
					input	= files;
856
					files	= [];
857
				}
858
859
				if( typeof filter == 'string' ){
860
					filter	= api.getFilesFilter(filter);
861
				}
862
863
				if( input.originalEvent ){
864
					// jQuery event
865
					input = _fixEvent(input.originalEvent);
866
				}
867
				else if( input.srcElement ){
868
					// IE Event
869
					input = _fixEvent(input);
870
				}
871
872
873
				if( input.dataTransfer ){
874
					// Drag'n'Drop
875
					input = input.dataTransfer;
876
				}
877
				else if( input.target ){
878
					// Event
879
					input = input.target;
880
				}
881
882
				if( input.files ){
883
					// Input[type="file"]
884
					files = input.files;
885
886
					if( !html5 ){
887
						// Partial support for file api
888
						files[0].blob	= input;
889
						files[0].iframe	= true;
890
					}
891
				}
892
				else if( !html5 && isInputFile(input) ){
893
					if( api.trim(input.value) ){
894
						files = [api.checkFileObj(input.value)];
895
						files[0].blob   = input;
896
						files[0].iframe = true;
897
					}
898
				}
899
				else if( _isArray(input) ){
900
					files	= input;
901
				}
902
903
				return	api.filter(files, function (file){ return !filter || filter.test(file.name); });
904
			},
905
906
907
			/**
908
			 * Get total file size
909
			 * @param	{Array}	files
910
			 * @return	{Number}
911
			 */
912
			getTotalSize: function (files){
913
				var size = 0, i = files && files.length;
914
				while( i-- ){
915
					size += files[i].size;
916
				}
917
				return	size;
918
			},
919
920
921
			/**
922
			 * Get image information
923
			 *
924
			 * @param	{File}		file
925
			 * @param	{Function}	fn
926
			 */
927
			getInfo: function (file, fn){
928
				var info = {}, readers = _infoReader.concat();
929
930
				if( api.isFile(file) ){
931
					(function _next(){
932
						var reader = readers.shift();
933
						if( reader ){
934
							if( reader.test(file.type) ){
935
								reader(file, function (err, res){
936
									if( err ){
937
										fn(err);
938
									}
939
									else {
940
										_extend(info, res);
941
										_next();
942
									}
943
								});
944
							}
945
							else {
946
								_next();
947
							}
948
						}
949
						else {
950
							fn(false, info);
951
						}
952
					})();
953
				}
954
				else {
955
					fn('not_support_info', info);
956
				}
957
			},
958
959
960
			/**
961
			 * Add information reader
962
			 *
963
			 * @param {RegExp} mime
964
			 * @param {Function} fn
965
			 */
966
			addInfoReader: function (mime, fn){
967
				fn.test = function (type){ return mime.test(type); };
968
				_infoReader.push(fn);
969
			},
970
971
972
			/**
973
			 * Filter of array
974
			 *
975
			 * @param	{Array}		input
976
			 * @param	{Function}	fn
977
			 * @return	{Array}
978
			 */
979
			filter: function (input, fn){
980
				var result = [], i = 0, n = input.length, val;
981
982
				for( ; i < n; i++ ){
983
					if( i in input ){
984
						val = input[i];
985
						if( fn.call(val, val, i, input) ){
986
							result.push(val);
987
						}
988
					}
989
				}
990
991
				return	result;
992
			},
993
994
995
			/**
996
			 * Filter files
997
			 *
998
			 * @param	{Array}		files
999
			 * @param	{Function}	eachFn
1000
			 * @param	{Function}	resultFn
1001
			 */
1002
			filterFiles: function (files, eachFn, resultFn){
1003
				if( files.length ){
1004
					// HTML5 or Flash
1005
					var queue = files.concat(), file, result = [], deleted = [];
1006
1007
					(function _next(){
1008
						if( queue.length ){
1009
							file = queue.shift();
1010
							api.getInfo(file, function (err, info){
1011
								(eachFn(file, err ? false : info) ? result : deleted).push(file);
1012
								_next();
1013
							});
1014
						}
1015
						else {
1016
							resultFn(result, deleted);
1017
						}
1018
					})();
1019
				}
1020
				else {
1021
					resultFn([], files);
1022
				}
1023
			},
1024
1025
1026
			upload: function (options){
1027
				options = _extend({
1028
					  jsonp: 'callback'
1029
					, prepare: api.F
1030
					, beforeupload: api.F
1031
					, upload: api.F
1032
					, fileupload: api.F
1033
					, fileprogress: api.F
1034
					, filecomplete: api.F
1035
					, progress: api.F
1036
					, complete: api.F
1037
					, pause: api.F
1038
					, imageOriginal: true
1039
					, chunkSize: api.chunkSize
1040
					, chunkUploadRetry: api.chunkUploadRetry
1041
					, uploadRetry: api.uploadRetry
1042
				}, options);
1043
1044
1045
				if( options.imageAutoOrientation && !options.imageTransform ){
1046
					options.imageTransform = { rotate: 'auto' };
1047
				}
1048
1049
1050
				var
1051
					  proxyXHR = new api.XHR(options)
1052
					, dataArray = this._getFilesDataArray(options.files)
1053
					, _this = this
1054
					, _total = 0
1055
					, _loaded = 0
1056
					, _nextFile
1057
					, _complete = false
1058
				;
1059
1060
1061
				// calc total size
1062
				_each(dataArray, function (data){
1063
					_total += data.size;
1064
				});
1065
1066
				// Array of files
1067
				proxyXHR.files = [];
1068
				_each(dataArray, function (data){
1069
					proxyXHR.files.push(data.file);
1070
				});
1071
1072
				// Set upload status props
1073
				proxyXHR.total	= _total;
1074
				proxyXHR.loaded	= 0;
1075
				proxyXHR.filesLeft = dataArray.length;
1076
1077
				// emit "beforeupload"  event
1078
				options.beforeupload(proxyXHR, options);
1079
1080
				// Upload by file
1081
				_nextFile = function (){
1082
					var
1083
						  data = dataArray.shift()
1084
						, _file = data && data.file
1085
						, _fileLoaded = false
1086
						, _fileOptions = _simpleClone(options)
1087
					;
1088
1089
					proxyXHR.filesLeft = dataArray.length;
1090
1091
					if( _file && _file.name === api.expando ){
1092
						_file = null;
1093
						api.log('[warn] FileAPI.upload() — called without files');
1094
					}
1095
1096
					if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1097
						// Mark active job
1098
						_complete = false;
1099
1100
						// Set current upload file
1101
						proxyXHR.currentFile = _file;
1102
1103
						// Prepare file options
1104
						if (_file && options.prepare(_file, _fileOptions) === false) {
1105
							_nextFile.call(_this);
1106
							return;
1107
						}
1108
						_fileOptions.file = _file;
1109
1110
						_this._getFormData(_fileOptions, data, function (form){
1111
							if( !_loaded ){
1112
								// emit "upload" event
1113
								options.upload(proxyXHR, options);
1114
							}
1115
1116
							var xhr = new api.XHR(_extend({}, _fileOptions, {
1117
1118
								upload: _file ? function (){
1119
									// emit "fileupload" event
1120
									options.fileupload(_file, xhr, _fileOptions);
1121
								} : noop,
1122
1123
								progress: _file ? function (evt){
1124
									if( !_fileLoaded ){
1125
										// For ignore the double calls.
1126
										_fileLoaded = (evt.loaded === evt.total);
1127
1128
										// emit "fileprogress" event
1129
										options.fileprogress({
1130
											  type:   'progress'
1131
											, total:  data.total = evt.total
1132
											, loaded: data.loaded = evt.loaded
1133
										}, _file, xhr, _fileOptions);
1134
1135
										// emit "progress" event
1136
										options.progress({
1137
											  type:   'progress'
1138
											, total:  _total
1139
											, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1140
										}, _file, xhr, _fileOptions);
1141
									}
1142
								} : noop,
1143
1144
								complete: function (err){
1145
									_each(_xhrPropsExport, function (name){
1146
										proxyXHR[name] = xhr[name];
1147
									});
1148
1149
									if( _file ){
1150
										data.total = (data.total || data.size);
1151
										data.loaded	= data.total;
1152
1153
										if( !err ) {
1154
											// emulate 100% "progress"
1155
											this.progress(data);
1156
1157
											// fixed throttle event
1158
											_fileLoaded = true;
1159
1160
											// bytes loaded
1161
											_loaded += data.size; // data.size != data.total, it's desirable fix this
1162
											proxyXHR.loaded = _loaded;
1163
										}
1164
1165
										// emit "filecomplete" event
1166
										options.filecomplete(err, xhr, _file, _fileOptions);
1167
									}
1168
1169
									// upload next file
1170
									setTimeout(function () {_nextFile.call(_this);}, 0);
1171
								}
1172
							})); // xhr
1173
1174
1175
							// ...
1176
							proxyXHR.abort = function (current){
1177
								if (!current) { dataArray.length = 0; }
1178
								this.current = current;
1179
								xhr.abort();
1180
							};
1181
1182
							// Start upload
1183
							xhr.send(form);
1184
						});
1185
					}
1186
					else {
1187
						var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1188
						options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1189
						// Mark done state
1190
						_complete = true;
1191
					}
1192
				};
1193
1194
1195
				// Next tick
1196
				setTimeout(_nextFile, 0);
1197
1198
1199
				// Append more files to the existing request
1200
				// first - add them to the queue head/tail
1201
				proxyXHR.append = function (files, first) {
1202
					files = api._getFilesDataArray([].concat(files));
1203
1204
					_each(files, function (data) {
1205
						_total += data.size;
1206
						proxyXHR.files.push(data.file);
1207
						if (first) {
1208
							dataArray.unshift(data);
1209
						} else {
1210
							dataArray.push(data);
1211
						}
1212
					});
1213
1214
					proxyXHR.statusText = "";
1215
1216
					if( _complete ){
1217
						_nextFile.call(_this);
1218
					}
1219
				};
1220
1221
1222
				// Removes file from queue by file reference and returns it
1223
				proxyXHR.remove = function (file) {
1224
				    var i = dataArray.length, _file;
1225
				    while( i-- ){
1226
						if( dataArray[i].file == file ){
1227
							_file = dataArray.splice(i, 1);
1228
							_total -= _file.size;
1229
						}
1230
					}
1231
					return	_file;
1232
				};
1233
1234
				return proxyXHR;
1235
			},
1236
1237
1238
			_getFilesDataArray: function (data){
1239
				var files = [], oFiles = {};
1240
1241
				if( isInputFile(data) ){
1242
					var tmp = api.getFiles(data);
1243
					oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1244
				}
1245
				else if( _isArray(data) && isInputFile(data[0]) ){
1246
					_each(data, function (input){
1247
						oFiles[input.name || 'file'] = api.getFiles(input);
1248
					});
1249
				}
1250
				else {
1251
					oFiles = data;
1252
				}
1253
1254
				_each(oFiles, function add(file, name){
1255
					if( _isArray(file) ){
1256
						_each(file, function (file){
1257
							add(file, name);
1258
						});
1259
					}
1260
					else if( file && (file.name || file.image) ){
1261
						files.push({
1262
							  name: name
1263
							, file: file
1264
							, size: file.size
1265
							, total: file.size
1266
							, loaded: 0
1267
						});
1268
					}
1269
				});
1270
1271
				if( !files.length ){
1272
					// Create fake `file` object
1273
					files.push({ file: { name: api.expando } });
1274
				}
1275
1276
				return	files;
1277
			},
1278
1279
1280
			_getFormData: function (options, data, fn){
1281
				var
1282
					  file = data.file
1283
					, name = data.name
1284
					, filename = file.name
1285
					, filetype = file.type
1286
					, trans = api.support.transform && options.imageTransform
1287
					, Form = new api.Form
1288
					, queue = api.queue(function (){ fn(Form); })
1289
					, isOrignTrans = trans && _isOriginTransform(trans)
1290
					, postNameConcat = api.postNameConcat
1291
				;
1292
1293
				// Append data
1294
				_each(options.data, function add(val, name){
1295
					if( typeof val == 'object' ){
1296
						_each(val, function (v, i){
1297
							add(v, postNameConcat(name, i));
1298
						});
1299
					}
1300
					else {
1301
						Form.append(name, val);
1302
					}
1303
				});
1304
1305
				(function _addFile(file/**Object*/){
1306
					if( file.image ){ // This is a FileAPI.Image
1307
						queue.inc();
1308
1309
						file.toData(function (err, image){
1310
							// @todo: error
1311
							filename = filename || (new Date).getTime()+'.png';
1312
1313
							_addFile(image);
1314
							queue.next();
1315
						});
1316
					}
1317
					else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1318
						queue.inc();
1319
1320
						if( isOrignTrans ){
1321
							// Convert to array for transform function
1322
							trans = [trans];
1323
						}
1324
1325
						api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1326
							if( isOrignTrans && !err ){
1327
								if( !dataURLtoBlob && !api.flashEngine ){
1328
									// Canvas.toBlob or Flash not supported, use multipart
1329
									Form.multipart = true;
1330
								}
1331
1332
								Form.append(name, images[0], filename,  trans[0].type || filetype);
1333
							}
1334
							else {
1335
								var addOrigin = 0;
1336
1337
								if( !err ){
1338
									_each(images, function (image, idx){
1339
										if( !dataURLtoBlob && !api.flashEngine ){
1340
											Form.multipart = true;
1341
										}
1342
1343
										if( !trans[idx].postName ){
1344
											addOrigin = 1;
1345
										}
1346
1347
										Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1348
									});
1349
								}
1350
1351
								if( err || options.imageOriginal ){
1352
									Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1353
								}
1354
							}
1355
1356
							queue.next();
1357
						});
1358
					}
1359
					else if( filename !== api.expando ){
1360
						Form.append(name, file, filename);
1361
					}
1362
				})(file);
1363
1364
				queue.check();
1365
			},
1366
1367
1368
			reset: function (inp, notRemove){
1369
				var parent, clone;
1370
1371
				if( jQuery ){
1372
					clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1373
					if( !notRemove ){
1374
						jQuery(inp).remove();
1375
					}
1376
				} else {
1377
					parent  = inp.parentNode;
1378
					clone   = parent.insertBefore(inp.cloneNode(true), inp);
1379
					clone.value = '';
1380
1381
					if( !notRemove ){
1382
						parent.removeChild(inp);
1383
					}
1384
1385
					_each(_elEvents[api.uid(inp)], function (fns, type){
1386
						_each(fns, function (fn){
1387
							_off(inp, type, fn);
1388
							_on(clone, type, fn);
1389
						});
1390
					});
1391
				}
1392
1393
				return  clone;
1394
			},
1395
1396
1397
			/**
1398
			 * Load remote file
1399
			 *
1400
			 * @param   {String}    url
1401
			 * @param   {Function}  fn
1402
			 * @return  {XMLHttpRequest}
1403
			 */
1404
			load: function (url, fn){
1405
				var xhr = api.getXHR();
1406
				if( xhr ){
1407
					xhr.open('GET', url, true);
1408
1409
					if( xhr.overrideMimeType ){
1410
				        xhr.overrideMimeType('text/plain; charset=x-user-defined');
1411
					}
1412
1413
					_on(xhr, 'progress', function (/**Event*/evt){
1414
						/** @namespace evt.lengthComputable */
1415
						if( evt.lengthComputable ){
1416
							fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1417
						}
1418
					});
1419
1420
					xhr.onreadystatechange = function(){
1421
						if( xhr.readyState == 4 ){
1422
							xhr.onreadystatechange = null;
1423
							if( xhr.status == 200 ){
1424
								url = url.split('/');
1425
								/** @namespace xhr.responseBody */
1426
								var file = {
1427
								      name: url[url.length-1]
1428
									, size: xhr.getResponseHeader('Content-Length')
1429
									, type: xhr.getResponseHeader('Content-Type')
1430
								};
1431
								file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1432
								fn({ type: 'load', result: file }, xhr);
1433
							}
1434
							else {
1435
								fn({ type: 'error' }, xhr);
1436
							}
1437
					    }
1438
					};
1439
				    xhr.send(null);
1440
				} else {
1441
					fn({ type: 'error' });
1442
				}
1443
1444
				return  xhr;
1445
			},
1446
1447
			encode64: function (str){
1448
				var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1449
1450
				if( typeof str !== 'string' ){
1451
					str	= String(str);
1452
				}
1453
1454
				while( i < str.length ){
1455
					//all three "& 0xff" added below are there to fix a known bug
1456
					//with bytes returned by xhr.responseText
1457
					var
1458
						  byte1 = str.charCodeAt(i++) & 0xff
1459
						, byte2 = str.charCodeAt(i++) & 0xff
1460
						, byte3 = str.charCodeAt(i++) & 0xff
1461
						, enc1 = byte1 >> 2
1462
						, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1463
						, enc3, enc4
1464
					;
1465
1466
					if( isNaN(byte2) ){
1467
						enc3 = enc4 = 64;
1468
					} else {
1469
						enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1470
						enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1471
					}
1472
1473
					outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1474
				}
1475
1476
				return  outStr;
1477
			}
1478
1479
		} // api
1480
	;
1481
1482
1483
	function _emit(target, fn, name, res, ext){
1484
		var evt = {
1485
			  type:		name.type || name
1486
			, target:	target
1487
			, result:	res
1488
		};
1489
		_extend(evt, ext);
1490
		fn(evt);
1491
	}
1492
1493
1494
	function _hasSupportReadAs(as){
1495
		return	FileReader && !!FileReader.prototype['readAs'+as];
1496
	}
1497
1498
1499
	function _readAs(file, fn, as, encoding){
1500
		if( api.isBlob(file) && _hasSupportReadAs(as) ){
1501
			var Reader = new FileReader;
1502
1503
			// Add event listener
1504
			_on(Reader, _readerEvents, function _fn(evt){
1505
				var type = evt.type;
1506
				if( type == 'progress' ){
1507
					_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1508
				}
1509
				else if( type == 'loadend' ){
1510
					_off(Reader, _readerEvents, _fn);
1511
					Reader = null;
1512
				}
1513
				else {
1514
					_emit(file, fn, evt, evt.target.result);
1515
				}
1516
			});
1517
1518
1519
			try {
1520
				// ReadAs ...
1521
				if( encoding ){
1522
					Reader['readAs'+as](file, encoding);
1523
				}
1524
				else {
1525
					Reader['readAs'+as](file);
1526
				}
1527
			}
1528
			catch (err){
1529
				_emit(file, fn, 'error', undef, { error: err.toString() });
1530
			}
1531
		}
1532
		else {
1533
			_emit(file, fn, 'error', undef, { error: 'filreader_not_support_'+as });
1534
		}
1535
	}
1536
1537
1538
	function _isRegularFile(file, callback){
1539
		// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1540
		if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1541
			if( FileReader ){
1542
				try {
1543
					var Reader = new FileReader();
1544
1545
					_one(Reader, _readerEvents, function (evt){
1546
						var isFile = evt.type != 'error';
1547
						callback(isFile);
1548
						if( isFile ){
1549
							Reader.abort();
1550
						}
1551
					});
1552
1553
					Reader.readAsDataURL(file);
1554
				} catch( err ){
1555
					callback(false);
1556
				}
1557
			}
1558
			else {
1559
				callback(null);
1560
			}
1561
		}
1562
		else {
1563
			callback(true);
1564
		}
1565
	}
1566
1567
1568
	function _getAsEntry(item){
1569
		var entry;
1570
		if( item.getAsEntry ){ entry = item.getAsEntry(); }
1571
		else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1572
		return	entry;
1573
	}
1574
1575
1576
	function _readEntryAsFiles(entry, callback){
1577
		if( !entry ){
1578
			// error
1579
			callback('invalid entry');
1580
		}
1581
		else if( entry.isFile ){
1582
			// Read as file
1583
			entry.file(function(file){
1584
				// success
1585
				file.fullPath = entry.fullPath;
1586
				callback(false, [file]);
1587
			}, function (err){
1588
				// error
1589
				callback('FileError.code: '+err.code);
1590
			});
1591
		}
1592
		else if( entry.isDirectory ){
1593
			var reader = entry.createReader(), result = [];
1594
1595
			reader.readEntries(function(entries){
1596
				// success
1597
				api.afor(entries, function (next, entry){
1598
					_readEntryAsFiles(entry, function (err, files){
1599
						if( err ){
1600
							api.log(err);
1601
						}
1602
						else {
1603
							result = result.concat(files);
1604
						}
1605
1606
						if( next ){
1607
							next();
1608
						}
1609
						else {
1610
							callback(false, result);
1611
						}
1612
					});
1613
				});
1614
			}, function (err){
1615
				// error
1616
				callback('directory_reader: ' + err);
1617
			});
1618
		}
1619
		else {
1620
			_readEntryAsFiles(_getAsEntry(entry), callback);
1621
		}
1622
	}
1623
1624
1625
	function _simpleClone(obj){
1626
		var copy = {};
1627
		_each(obj, function (val, key){
1628
			if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1629
				val = _extend({}, val);
1630
			}
1631
			copy[key] = val;
1632
		});
1633
		return	copy;
1634
	}
1635
1636
1637
	function isInputFile(el){
1638
		return	_rinput.test(el && el.tagName);
1639
	}
1640
1641
1642
	function _getDataTransfer(evt){
1643
		return	(evt.originalEvent || evt || '').dataTransfer || {};
1644
	}
1645
1646
1647
	function _isOriginTransform(trans){
1648
		var key;
1649
		for( key in trans ){
1650
			if( trans.hasOwnProperty(key) ){
1651
				if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1652
					return	true;
1653
				}
1654
			}
1655
		}
1656
		return	false;
1657
	}
1658
1659
1660
	// Add default image info reader
1661
	api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1662
		if( !file.__dimensions ){
1663
			var defer = file.__dimensions = api.defer();
1664
1665
			api.readAsImage(file, function (evt){
1666
				var img = evt.target;
1667
				defer.resolve(evt.type == 'load' ? false : 'error', {
1668
					  width:  img.width
1669
					, height: img.height
1670
				});
1671
                img.src = api.EMPTY_PNG;
1672
				img = null;
1673
			});
1674
		}
1675
1676
		file.__dimensions.then(callback);
1677
	});
1678
1679
1680
	/**
1681
	 * Drag'n'Drop special event
1682
	 *
1683
	 * @param	{HTMLElement}	el
1684
	 * @param	{Function}		onHover
1685
	 * @param	{Function}		onDrop
1686
	 */
1687
	api.event.dnd = function (el, onHover, onDrop){
1688
		var _id, _type;
1689
1690
		if( !onDrop ){
1691
			onDrop = onHover;
1692
			onHover = api.F;
1693
		}
1694
1695
		if( FileReader ){
1696
			// Hover
1697
			_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1698
				var
1699
					  types = _getDataTransfer(evt).types
1700
					, i = types && types.length
1701
					, debounceTrigger = false
1702
				;
1703
1704
				while( i-- ){
1705
					if( ~types[i].indexOf('File') ){
1706
						evt[preventDefault]();
1707
1708
						if( _type !== evt.type ){
1709
							_type = evt.type; // Store current type of event
1710
1711
							if( _type != 'dragleave' ){
1712
								onHover.call(evt[currentTarget], true, evt);
1713
							}
1714
1715
							debounceTrigger = true;
1716
						}
1717
1718
						break; // exit from "while"
1719
					}
1720
				}
1721
1722
				if( debounceTrigger ){
1723
					clearTimeout(_id);
1724
					_id = setTimeout(function (){
1725
						onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1726
					}, 50);
1727
				}
1728
			});
1729
1730
1731
			// Drop
1732
			_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1733
				evt[preventDefault]();
1734
1735
				_type = 0;
1736
				onHover.call(evt[currentTarget], false, evt);
1737
1738
				api.getDropFiles(evt, function (files){
1739
					onDrop.call(evt[currentTarget], files, evt);
1740
				});
1741
			});
1742
		}
1743
		else {
1744
			api.log("Drag'n'Drop -- not supported");
1745
		}
1746
	};
1747
1748
1749
	/**
1750
	 * Remove drag'n'drop
1751
	 * @param	{HTMLElement}	el
1752
	 * @param	{Function}		onHover
1753
	 * @param	{Function}		onDrop
1754
	 */
1755
	api.event.dnd.off = function (el, onHover, onDrop){
1756
		_off(el, 'dragenter dragleave dragover', onHover.ff);
1757
		_off(el, 'drop', onDrop.ff);
1758
	};
1759
1760
1761
	// Support jQuery
1762
	if( jQuery && !jQuery.fn.dnd ){
1763
		jQuery.fn.dnd = function (onHover, onDrop){
1764
			return this.each(function (){
1765
				api.event.dnd(this, onHover, onDrop);
1766
			});
1767
		};
1768
1769
		jQuery.fn.offdnd = function (onHover, onDrop){
1770
			return this.each(function (){
1771
				api.event.dnd.off(this, onHover, onDrop);
1772
			});
1773
		};
1774
	}
1775
1776
	// @export
1777
	window.FileAPI  = _extend(api, window.FileAPI);
1778
1779
1780
	// Debug info
1781
	api.log('FileAPI: ' + api.version);
1782
	api.log('protocol: ' + window.location.protocol);
1783
	api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1784
1785
1786
	// @detect 'x-ua-compatible'
1787
	_each(document.getElementsByTagName('meta'), function (meta){
1788
		if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1789
			api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1790
		}
1791
	});
1792
1793
1794
	// @configuration
1795
	if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1796
	if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1797
	if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1798
})(window, void 0);
1799
1800
/*global window, FileAPI, document */
1801
1802
(function (api, document, undef) {
1803
	'use strict';
1804
1805
	var
1806
		min = Math.min,
1807
		round = Math.round,
1808
		getCanvas = function () { return document.createElement('canvas'); },
1809
		support = false,
1810
		exifOrientation = {
1811
			  8:	270
1812
			, 3:	180
1813
			, 6:	90
1814
			, 7:	270
1815
			, 4:	180
1816
			, 5:	90
1817
		}
1818
	;
1819
1820
	try {
1821
		support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1822
	}
1823
	catch (e){}
1824
1825
1826
	function Image(file){
1827
		if( file instanceof Image ){
1828
			var img = new Image(file.file);
1829
			api.extend(img.matrix, file.matrix);
1830
			return	img;
1831
		}
1832
		else if( !(this instanceof Image) ){
1833
			return	new Image(file);
1834
		}
1835
1836
		this.file   = file;
1837
		this.size   = file.size || 100;
1838
1839
		this.matrix	= {
1840
			sx: 0,
1841
			sy: 0,
1842
			sw: 0,
1843
			sh: 0,
1844
			dx: 0,
1845
			dy: 0,
1846
			dw: 0,
1847
			dh: 0,
1848
			resize: 0, // min, max OR preview
1849
			deg: 0,
1850
			quality: 1, // jpeg quality
1851
			filter: 0
1852
		};
1853
	}
1854
1855
1856
	Image.prototype = {
1857
		image: true,
1858
		constructor: Image,
1859
1860
		set: function (attrs){
1861
			api.extend(this.matrix, attrs);
1862
			return	this;
1863
		},
1864
1865
		crop: function (x, y, w, h){
1866
			if( w === undef ){
1867
				w	= x;
1868
				h	= y;
1869
				x = y = 0;
1870
			}
1871
			return	this.set({ sx: x, sy: y, sw: w, sh: h || w });
1872
		},
1873
1874
		resize: function (w, h, strategy){
1875
			if( /min|max/.test(h) ){
1876
				strategy = h;
1877
				h = w;
1878
			}
1879
1880
			return	this.set({ dw: w, dh: h || w, resize: strategy });
1881
		},
1882
1883
		preview: function (w, h){
1884
			return	this.resize(w, h || w, 'preview');
1885
		},
1886
1887
		rotate: function (deg){
1888
			return	this.set({ deg: deg });
1889
		},
1890
1891
		filter: function (filter){
1892
			return	this.set({ filter: filter });
1893
		},
1894
1895
		overlay: function (images){
1896
			return	this.set({ overlay: images });
1897
		},
1898
1899
		clone: function (){
1900
			return	new Image(this);
1901
		},
1902
1903
		_load: function (image, fn){
1904
			var self = this;
1905
1906
			if( /img|video/i.test(image.nodeName) ){
1907
				fn.call(self, null, image);
1908
			}
1909
			else {
1910
				api.readAsImage(image, function (evt){
1911
					fn.call(self, evt.type != 'load', evt.result);
1912
				});
1913
			}
1914
		},
1915
1916
		_apply: function (image, fn){
1917
			var
1918
				  canvas = getCanvas()
1919
				, m = this.getMatrix(image)
1920
				, ctx = canvas.getContext('2d')
1921
				, width = image.videoWidth || image.width
1922
				, height = image.videoHeight || image.height
1923
				, deg = m.deg
1924
				, dw = m.dw
1925
				, dh = m.dh
1926
				, w = width
1927
				, h = height
1928
				, filter = m.filter
1929
				, copy // canvas copy
1930
				, buffer = image
1931
				, overlay = m.overlay
1932
				, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1933
				, renderImageToCanvas = api.renderImageToCanvas
1934
			;
1935
1936
			// Normalize angle
1937
			deg = deg - Math.floor(deg/360)*360;
1938
1939
			// For `renderImageToCanvas`
1940
			image._type = this.file.type;
1941
1942
			while(m.multipass && min(w/dw, h/dh) > 2 ){
1943
				w = (w/2 + 0.5)|0;
1944
				h = (h/2 + 0.5)|0;
1945
1946
				copy = getCanvas();
1947
				copy.width  = w;
1948
				copy.height = h;
1949
1950
				if( buffer !== image ){
1951
					renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1952
					buffer = copy;
1953
				}
1954
				else {
1955
					buffer = copy;
1956
					renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1957
					m.sx = m.sy = m.sw = m.sh = 0;
1958
				}
1959
			}
1960
1961
1962
			canvas.width  = (deg % 180) ? dh : dw;
1963
			canvas.height = (deg % 180) ? dw : dh;
1964
1965
			canvas.type = m.type;
1966
			canvas.quality = m.quality;
1967
1968
			ctx.rotate(deg * Math.PI / 180);
1969
			renderImageToCanvas(ctx.canvas, buffer
1970
				, m.sx, m.sy
1971
				, m.sw || buffer.width
1972
				, m.sh || buffer.height
1973
				, (deg == 180 || deg == 270 ? -dw : 0)
1974
				, (deg == 90 || deg == 180 ? -dh : 0)
1975
				, dw, dh
1976
			);
1977
			dw = canvas.width;
1978
			dh = canvas.height;
1979
1980
			// Apply overlay
1981
			overlay && api.each([].concat(overlay), function (over){
1982
				queue.inc();
1983
				// preload
1984
				var img = new window.Image, fn = function (){
1985
					var
1986
						  x = over.x|0
1987
						, y = over.y|0
1988
						, w = over.w || img.width
1989
						, h = over.h || img.height
1990
						, rel = over.rel
1991
					;
1992
1993
					// center  |  right  |  left
1994
					x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
1995
1996
					// center  |  bottom  |  top
1997
					y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
1998
1999
					api.event.off(img, 'error load abort', fn);
2000
2001
					try {
2002
						ctx.globalAlpha = over.opacity || 1;
2003
						ctx.drawImage(img, x, y, w, h);
2004
					}
2005
					catch (er){}
2006
2007
					queue.next();
2008
				};
2009
2010
				api.event.on(img, 'error load abort', fn);
2011
				img.src = over.src;
2012
2013
				if( img.complete ){
2014
					fn();
2015
				}
2016
			});
2017
2018
			if( filter ){
2019
				queue.inc();
2020
				Image.applyFilter(canvas, filter, queue.next);
2021
			}
2022
2023
			queue.check();
2024
		},
2025
2026
		getMatrix: function (image){
2027
			var
2028
				  m  = api.extend({}, this.matrix)
2029
				, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width
2030
				, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2031
				, dw = m.dw = m.dw || sw
2032
				, dh = m.dh = m.dh || sh
2033
				, sf = sw/sh, df = dw/dh
2034
				, strategy = m.resize
2035
			;
2036
2037
			if( strategy == 'preview' ){
2038
				if( dw != sw || dh != sh ){
2039
					// Make preview
2040
					var w, h;
2041
2042
					if( df >= sf ){
2043
						w	= sw;
2044
						h	= w / df;
2045
					} else {
2046
						h	= sh;
2047
						w	= h * df;
2048
					}
2049
2050
					if( w != sw || h != sh ){
2051
						m.sx	= ~~((sw - w)/2);
2052
						m.sy	= ~~((sh - h)/2);
2053
						sw		= w;
2054
						sh		= h;
2055
					}
2056
				}
2057
			}
2058
			else if( strategy ){
2059
				if( !(sw > dw || sh > dh) ){
2060
					dw = sw;
2061
					dh = sh;
2062
				}
2063
				else if( strategy == 'min' ){
2064
					dw = round(sf < df ? min(sw, dw) : dh*sf);
2065
					dh = round(sf < df ? dw/sf : min(sh, dh));
2066
				}
2067
				else {
2068
					dw = round(sf >= df ? min(sw, dw) : dh*sf);
2069
					dh = round(sf >= df ? dw/sf : min(sh, dh));
2070
				}
2071
			}
2072
2073
			m.sw = sw;
2074
			m.sh = sh;
2075
			m.dw = dw;
2076
			m.dh = dh;
2077
			m.multipass = api.multiPassResize;
2078
			return	m;
2079
		},
2080
2081
		_trans: function (fn){
2082
			this._load(this.file, function (err, image){
2083
				if( err ){
2084
					fn(err);
2085
				}
2086
				else {
2087
					try {
2088
						this._apply(image, fn);
2089
					} catch (err){
2090
						api.log('[err] FileAPI.Image.fn._apply:', err);
2091
						fn(err);
2092
					}
2093
				}
2094
			});
2095
		},
2096
2097
2098
		get: function (fn){
2099
			if( api.support.transform ){
2100
				var _this = this, matrix = _this.matrix;
2101
2102
				if( matrix.deg == 'auto' ){
2103
					api.getInfo(_this.file, function (err, info){
2104
						// rotate by exif orientation
2105
						matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2106
						_this._trans(fn);
2107
					});
2108
				}
2109
				else {
2110
					_this._trans(fn);
2111
				}
2112
			}
2113
			else {
2114
				fn('not_support_transform');
2115
			}
2116
2117
			return this;
2118
		},
2119
2120
2121
		toData: function (fn){
2122
			return this.get(fn);
2123
		}
2124
2125
	};
2126
2127
2128
	Image.exifOrientation = exifOrientation;
2129
2130
2131
	Image.transform = function (file, transform, autoOrientation, fn){
2132
		function _transform(err, img){
2133
			// img -- info object
2134
			var
2135
				  images = {}
2136
				, queue = api.queue(function (err){
2137
					fn(err, images);
2138
				})
2139
			;
2140
2141
			if( !err ){
2142
				api.each(transform, function (params, name){
2143
					if( !queue.isFail() ){
2144
						var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2145
2146
						if( isFn ){
2147
							params(img, ImgTrans);
2148
						}
2149
						else if( params.width ){
2150
							ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2151
						}
2152
						else {
2153
							if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2154
								ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2155
							}
2156
						}
2157
2158
						if( params.crop ){
2159
							var crop = params.crop;
2160
							ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2161
						}
2162
2163
						if( params.rotate === undef && autoOrientation ){
2164
							params.rotate = 'auto';
2165
						}
2166
2167
						ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2168
2169
						if( !isFn ){
2170
							ImgTrans.set({
2171
								  deg: params.rotate
2172
								, overlay: params.overlay
2173
								, filter: params.filter
2174
								, quality: params.quality || 1
2175
							});
2176
						}
2177
2178
						queue.inc();
2179
						ImgTrans.toData(function (err, image){
2180
							if( err ){
2181
								queue.fail();
2182
							}
2183
							else {
2184
								images[name] = image;
2185
								queue.next();
2186
							}
2187
						});
2188
					}
2189
				});
2190
			}
2191
			else {
2192
				queue.fail();
2193
			}
2194
		}
2195
2196
2197
		// @todo: Оло-ло, нужно рефакторить это место
2198
		if( file.width ){
2199
			_transform(false, file);
2200
		} else {
2201
			api.getInfo(file, _transform);
2202
		}
2203
	};
2204
2205
2206
	// @const
2207
	api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2208
		api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2209
			Image[x+'_'+y] = i*3 + j;
2210
			Image[y+'_'+x] = i*3 + j;
2211
		});
2212
	});
2213
2214
2215
	/**
2216
	 * Trabsform element to canvas
2217
	 *
2218
	 * @param    {Image|HTMLVideoElement}   el
2219
	 * @returns  {Canvas}
2220
	 */
2221
	Image.toCanvas = function(el){
2222
		var canvas		= document.createElement('canvas');
2223
		canvas.width	= el.videoWidth || el.width;
2224
		canvas.height	= el.videoHeight || el.height;
2225
		canvas.getContext('2d').drawImage(el, 0, 0);
2226
		return	canvas;
2227
	};
2228
2229
2230
	/**
2231
	 * Create image from DataURL
2232
	 * @param  {String}  dataURL
2233
	 * @param  {Object}  size
2234
	 * @param  {Function}  callback
2235
	 */
2236
	Image.fromDataURL = function (dataURL, size, callback){
2237
		var img = api.newImage(dataURL);
2238
		api.extend(img, size);
2239
		callback(img);
2240
	};
2241
2242
2243
	/**
2244
	 * Apply filter (caman.js)
2245
	 *
2246
	 * @param  {Canvas|Image}   canvas
2247
	 * @param  {String|Function}  filter
2248
	 * @param  {Function}  doneFn
2249
	 */
2250
	Image.applyFilter = function (canvas, filter, doneFn){
2251
		if( typeof filter == 'function' ){
2252
			filter(canvas, doneFn);
2253
		}
2254
		else if( window.Caman ){
2255
			// http://camanjs.com/guides/
2256
			window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2257
				if( typeof filter == 'string' ){
2258
					this[filter]();
2259
				}
2260
				else {
2261
					api.each(filter, function (val, method){
2262
						this[method](val);
2263
					}, this);
2264
				}
2265
				this.render(doneFn);
2266
			});
2267
		}
2268
	};
2269
2270
2271
	/**
2272
	 * For load-image-ios.js
2273
	 */
2274
	api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2275
		try {
2276
			return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2277
		} catch (ex) {
2278
			api.log('renderImageToCanvas failed');
2279
			throw ex;
2280
		}
2281
	};
2282
2283
2284
	// @export
2285
	api.support.canvas = api.support.transform = support;
2286
	api.Image = Image;
2287
})(FileAPI, document);
2288
2289
/*
2290
 * JavaScript Load Image iOS scaling fixes 1.0.3
2291
 * https://github.com/blueimp/JavaScript-Load-Image
2292
 *
2293
 * Copyright 2013, Sebastian Tschan
2294
 * https://blueimp.net
2295
 *
2296
 * iOS image scaling fixes based on
2297
 * https://github.com/stomita/ios-imagefile-megapixel
2298
 *
2299
 * Licensed under the MIT license:
2300
 * http://www.opensource.org/licenses/MIT
2301
 */
2302
2303
/*jslint nomen: true, bitwise: true */
2304
/*global FileAPI, window, document */
2305
2306
(function (factory) {
2307
	'use strict';
2308
	factory(FileAPI);
2309
}(function (loadImage) {
2310
    'use strict';
2311
2312
    // Only apply fixes on the iOS platform:
2313
    if (!window.navigator || !window.navigator.platform ||
2314
             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2315
        return;
2316
    }
2317
2318
    var originalRenderMethod = loadImage.renderImageToCanvas;
2319
2320
    // Detects subsampling in JPEG images:
2321
    loadImage.detectSubsampling = function (img) {
2322
        var canvas,
2323
            context;
2324
        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2325
            canvas = document.createElement('canvas');
2326
            canvas.width = canvas.height = 1;
2327
            context = canvas.getContext('2d');
2328
            context.drawImage(img, -img.width + 1, 0);
2329
            // subsampled image becomes half smaller in rendering size.
2330
            // check alpha channel value to confirm image is covering edge pixel or not.
2331
            // if alpha value is 0 image is not covering, hence subsampled.
2332
            return context.getImageData(0, 0, 1, 1).data[3] === 0;
2333
        }
2334
        return false;
2335
    };
2336
2337
    // Detects vertical squash in JPEG images:
2338
    loadImage.detectVerticalSquash = function (img, subsampled) {
2339
        var naturalHeight = img.naturalHeight || img.height,
2340
            canvas = document.createElement('canvas'),
2341
            context = canvas.getContext('2d'),
2342
            data,
2343
            sy,
2344
            ey,
2345
            py,
2346
            alpha;
2347
        if (subsampled) {
2348
            naturalHeight /= 2;
2349
        }
2350
        canvas.width = 1;
2351
        canvas.height = naturalHeight;
2352
        context.drawImage(img, 0, 0);
2353
        data = context.getImageData(0, 0, 1, naturalHeight).data;
2354
        // search image edge pixel position in case it is squashed vertically:
2355
        sy = 0;
2356
        ey = naturalHeight;
2357
        py = naturalHeight;
2358
        while (py > sy) {
2359
            alpha = data[(py - 1) * 4 + 3];
2360
            if (alpha === 0) {
2361
                ey = py;
2362
            } else {
2363
                sy = py;
2364
            }
2365
            py = (ey + sy) >> 1;
2366
        }
2367
        return (py / naturalHeight) || 1;
2368
    };
2369
2370
    // Renders image to canvas while working around iOS image scaling bugs:
2371
    // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2372
    loadImage.renderImageToCanvas = function (
2373
        canvas,
2374
        img,
2375
        sourceX,
2376
        sourceY,
2377
        sourceWidth,
2378
        sourceHeight,
2379
        destX,
2380
        destY,
2381
        destWidth,
2382
        destHeight
2383
    ) {
2384
        if (img._type === 'image/jpeg') {
2385
            var context = canvas.getContext('2d'),
2386
                tmpCanvas = document.createElement('canvas'),
2387
                tileSize = 1024,
2388
                tmpContext = tmpCanvas.getContext('2d'),
2389
                subsampled,
2390
                vertSquashRatio,
2391
                tileX,
2392
                tileY;
2393
            tmpCanvas.width = tileSize;
2394
            tmpCanvas.height = tileSize;
2395
            context.save();
2396
            subsampled = loadImage.detectSubsampling(img);
2397
            if (subsampled) {
2398
                sourceX /= 2;
2399
                sourceY /= 2;
2400
                sourceWidth /= 2;
2401
                sourceHeight /= 2;
2402
            }
2403
            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2404
            if (subsampled || vertSquashRatio !== 1) {
2405
                sourceY *= vertSquashRatio;
2406
                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2407
                destHeight = Math.ceil(
2408
                    tileSize * destHeight / sourceHeight / vertSquashRatio
2409
                );
2410
                destY = 0;
2411
                tileY = 0;
2412
                while (tileY < sourceHeight) {
2413
                    destX = 0;
2414
                    tileX = 0;
2415
                    while (tileX < sourceWidth) {
2416
                        tmpContext.clearRect(0, 0, tileSize, tileSize);
2417
                        tmpContext.drawImage(
2418
                            img,
2419
                            sourceX,
2420
                            sourceY,
2421
                            sourceWidth,
2422
                            sourceHeight,
2423
                            -tileX,
2424
                            -tileY,
2425
                            sourceWidth,
2426
                            sourceHeight
2427
                        );
2428
                        context.drawImage(
2429
                            tmpCanvas,
2430
                            0,
2431
                            0,
2432
                            tileSize,
2433
                            tileSize,
2434
                            destX,
2435
                            destY,
2436
                            destWidth,
2437
                            destHeight
2438
                        );
2439
                        tileX += tileSize;
2440
                        destX += destWidth;
2441
                    }
2442
                    tileY += tileSize;
2443
                    destY += destHeight;
2444
                }
2445
                context.restore();
2446
                return canvas;
2447
            }
2448
        }
2449
        return originalRenderMethod(
2450
            canvas,
2451
            img,
2452
            sourceX,
2453
            sourceY,
2454
            sourceWidth,
2455
            sourceHeight,
2456
            destX,
2457
            destY,
2458
            destWidth,
2459
            destHeight
2460
        );
2461
    };
2462
2463
}));
2464
2465
/*global window, FileAPI */
2466
2467
(function (api, window){
2468
	"use strict";
2469
2470
	var
2471
		  document = window.document
2472
		, FormData = window.FormData
2473
		, Form = function (){ this.items = []; }
2474
		, encodeURIComponent = window.encodeURIComponent
2475
	;
2476
2477
2478
	Form.prototype = {
2479
2480
		append: function (name, blob, file, type){
2481
			this.items.push({
2482
				  name: name
2483
				, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2484
				, file: blob && (file || blob.name)
2485
				, type:	blob && (type || blob.type)
2486
			});
2487
		},
2488
2489
		each: function (fn){
2490
			var i = 0, n = this.items.length;
2491
			for( ; i < n; i++ ){
2492
				fn.call(this, this.items[i]);
2493
			}
2494
		},
2495
2496
		toData: function (fn, options){
2497
		    // allow chunked transfer if we have only one file to send
2498
		    // flag is used below and in XHR._send
2499
		    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2500
2501
			if( !api.support.html5 ){
2502
				api.log('FileAPI.Form.toHtmlData');
2503
				this.toHtmlData(fn);
2504
			}
2505
			else if( !api.formData || this.multipart || !FormData ){
2506
				api.log('FileAPI.Form.toMultipartData');
2507
				this.toMultipartData(fn);
2508
			}
2509
			else if( options._chunked ){
2510
				api.log('FileAPI.Form.toPlainData');
2511
				this.toPlainData(fn);
2512
			}
2513
			else {
2514
				api.log('FileAPI.Form.toFormData');
2515
				this.toFormData(fn);
2516
			}
2517
		},
2518
2519
		_to: function (data, complete, next, arg){
2520
			var queue = api.queue(function (){
2521
				complete(data);
2522
			});
2523
2524
			this.each(function (file){
2525
				next(file, data, queue, arg);
2526
			});
2527
2528
			queue.check();
2529
		},
2530
2531
2532
		toHtmlData: function (fn){
2533
			this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2534
				var blob = file.blob, hidden;
2535
2536
				if( file.file ){
2537
					api.reset(blob, true);
2538
					// set new name
2539
					blob.name = file.name;
2540
					blob.disabled = false;
2541
					data.appendChild(blob);
2542
				}
2543
				else {
2544
					hidden = document.createElement('input');
2545
					hidden.name  = file.name;
2546
					hidden.type  = 'hidden';
2547
					hidden.value = blob;
2548
					data.appendChild(hidden);
2549
				}
2550
			});
2551
		},
2552
2553
		toPlainData: function (fn){
2554
			this._to({}, fn, function (file, data, queue){
2555
				if( file.file ){
2556
					data.type = file.file;
2557
				}
2558
2559
				if( file.blob.toBlob ){
2560
				    // canvas
2561
					queue.inc();
2562
					_convertFile(file, function (file, blob){
2563
						data.name = file.name;
2564
						data.file = blob;
2565
						data.size = blob.length;
2566
						data.type = file.type;
2567
						queue.next();
2568
					});
2569
				}
2570
				else if( file.file ){
2571
				    // file
2572
					data.name = file.blob.name;
2573
					data.file = file.blob;
2574
					data.size = file.blob.size;
2575
					data.type = file.type;
2576
				}
2577
				else {
2578
				    // additional data
2579
				    if( !data.params ){
2580
				        data.params = [];
2581
				    }
2582
				    data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2583
				}
2584
2585
				data.start = -1;
2586
				data.end = data.file && data.file.FileAPIReadPosition || -1;
2587
				data.retry = 0;
2588
			});
2589
		},
2590
2591
		toFormData: function (fn){
2592
			this._to(new FormData, fn, function (file, data, queue){
2593
				if( file.blob && file.blob.toBlob ){
2594
					queue.inc();
2595
					_convertFile(file, function (file, blob){
2596
						data.append(file.name, blob, file.file);
2597
						queue.next();
2598
					});
2599
				}
2600
				else if( file.file ){
2601
					data.append(file.name, file.blob, file.file);
2602
				}
2603
				else {
2604
					data.append(file.name, file.blob);
2605
				}
2606
2607
				if( file.file ){
2608
					data.append('_'+file.name, file.file);
2609
				}
2610
			});
2611
		},
2612
2613
2614
		toMultipartData: function (fn){
2615
			this._to([], fn, function (file, data, queue, boundary){
2616
				queue.inc();
2617
				_convertFile(file, function (file, blob){
2618
					data.push(
2619
						  '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2620
						+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2621
						+ '\r\n'
2622
						+ '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2623
						+ '\r\n')
2624
					);
2625
					queue.next();
2626
				}, true);
2627
			}, api.expando);
2628
		}
2629
	};
2630
2631
2632
	function _convertFile(file, fn, useBinaryString){
2633
		var blob = file.blob, filename = file.file;
2634
2635
		if( filename ){
2636
			if( !blob.toDataURL ){
2637
				// The Blob is not an image.
2638
				api.readAsBinaryString(blob, function (evt){
2639
					if( evt.type == 'load' ){
2640
						fn(file, evt.result);
2641
					}
2642
				});
2643
				return;
2644
			}
2645
2646
			var
2647
				  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2648
				, type = mime[file.type] ? file.type : 'image/png'
2649
				, ext  = mime[type] || '.png'
2650
				, quality = blob.quality || 1
2651
			;
2652
2653
			if( !filename.match(new RegExp(ext+'$', 'i')) ){
2654
				// Does not change the current extension, but add a new one.
2655
				filename += ext.replace('?', '');
2656
			}
2657
2658
			file.file = filename;
2659
			file.type = type;
2660
2661
			if( !useBinaryString && blob.toBlob ){
2662
				blob.toBlob(function (blob){
2663
					fn(file, blob);
2664
				}, type, quality);
2665
			}
2666
			else {
2667
				fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2668
			}
2669
		}
2670
		else {
2671
			fn(file, blob);
2672
		}
2673
	}
2674
2675
2676
	// @export
2677
	api.Form = Form;
2678
})(FileAPI, window);
2679
2680
/*global window, FileAPI, Uint8Array */
2681
2682
(function (window, api){
2683
	"use strict";
2684
2685
	var
2686
		  noop = function (){}
2687
		, document = window.document
2688
2689
		, XHR = function (options){
2690
			this.uid = api.uid();
2691
			this.xhr = {
2692
				  abort: noop
2693
				, getResponseHeader: noop
2694
				, getAllResponseHeaders: noop
2695
			};
2696
			this.options = options;
2697
		},
2698
2699
		_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2700
	;
2701
2702
2703
	XHR.prototype = {
2704
		status: 0,
2705
		statusText: '',
2706
		constructor: XHR,
2707
2708
		getResponseHeader: function (name){
2709
			return this.xhr.getResponseHeader(name);
2710
		},
2711
2712
		getAllResponseHeaders: function (){
2713
			return this.xhr.getAllResponseHeaders() || {};
2714
		},
2715
2716
		end: function (status, statusText){
2717
			var _this = this, options = _this.options;
2718
2719
			_this.end		=
2720
			_this.abort		= noop;
2721
			_this.status	= status;
2722
2723
			if( statusText ){
2724
				_this.statusText = statusText;
2725
			}
2726
2727
			api.log('xhr.end:', status, statusText);
2728
			options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2729
2730
			if( _this.xhr && _this.xhr.node ){
2731
				setTimeout(function (){
2732
					var node = _this.xhr.node;
2733
					try { node.parentNode.removeChild(node); } catch (e){}
2734
					try { delete window[_this.uid]; } catch (e){}
2735
					window[_this.uid] = _this.xhr.node = null;
2736
				}, 9);
2737
			}
2738
		},
2739
2740
		abort: function (){
2741
			this.end(0, 'abort');
2742
2743
			if( this.xhr ){
2744
				this.xhr.aborted = true;
2745
				this.xhr.abort();
2746
			}
2747
		},
2748
2749
		send: function (FormData){
2750
			var _this = this, options = this.options;
2751
2752
			FormData.toData(function (data){
2753
				// Start uploading
2754
				options.upload(options, _this);
2755
				_this._send.call(_this, options, data);
2756
			}, options);
2757
		},
2758
2759
		_send: function (options, data){
2760
			var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2761
2762
			api.log('XHR._send:', data);
2763
2764
			if( !options.cache ){
2765
				// No cache
2766
				url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2767
			}
2768
2769
			if( data.nodeName ){
2770
				var jsonp = options.jsonp;
2771
2772
				// prepare callback in GET
2773
				url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2774
2775
				// legacy
2776
				options.upload(options, _this);
2777
2778
				var
2779
					onPostMessage = function (evt){
2780
						if( ~url.indexOf(evt.origin) ){
2781
							try {
2782
								var result = api.parseJSON(evt.data);
2783
								if( result.id == uid ){
2784
									complete(result.status, result.statusText, result.response);
2785
								}
2786
							} catch( err ){
2787
								complete(0, err.message);
2788
							}
2789
						}
2790
					},
2791
2792
					// jsonp-callack
2793
					complete = window[uid] = function (status, statusText, response){
2794
						_this.readyState	= 4;
2795
						_this.responseText	= response;
2796
						_this.end(status, statusText);
2797
2798
						api.event.off(window, 'message', onPostMessage);
2799
						window[uid] = xhr = transport = window[onloadFuncName] = null;
2800
					}
2801
				;
2802
2803
				_this.xhr.abort = function (){
2804
					try {
2805
						if( transport.stop ){ transport.stop(); }
2806
						else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2807
						else { transport.contentWindow.document.execCommand('Stop'); }
2808
					}
2809
					catch (er) {}
2810
					complete(0, "abort");
2811
				};
2812
2813
				api.event.on(window, 'message', onPostMessage);
2814
2815
				window[onloadFuncName] = function (){
2816
					try {
2817
						var
2818
							  win = transport.contentWindow
2819
							, doc = win.document
2820
							, result = win.result || api.parseJSON(doc.body.innerHTML)
2821
						;
2822
						complete(result.status, result.statusText, result.response);
2823
					} catch (e){
2824
						api.log('[transport.onload]', e);
2825
					}
2826
				};
2827
2828
				xhr = document.createElement('div');
2829
				xhr.innerHTML = '<form target="'+ uid +'" action="../../../../../../../ng-file-upload-5.0.9/demo/src/main/webapp/js/'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2830
							+ '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2831
							+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2832
							+ '</form>'
2833
				;
2834
2835
				// get form-data & transport
2836
				var
2837
					  form = xhr.getElementsByTagName('form')[0]
2838
					, transport = xhr.getElementsByTagName('iframe')[0]
2839
				;
2840
2841
				form.appendChild(data);
2842
2843
				api.log(form.parentNode.innerHTML);
2844
2845
				// append to DOM
2846
				document.body.appendChild(xhr);
2847
2848
				// keep a reference to node-transport
2849
				_this.xhr.node = xhr;
2850
2851
				// send
2852
				_this.readyState = 2; // loaded
2853
				form.submit();
2854
				form = null;
2855
			}
2856
			else {
2857
				// Clean url
2858
				url = url.replace(/([a-z]+)=(\?)&?/i, '');
2859
2860
				// html5
2861
				if (this.xhr && this.xhr.aborted) {
2862
					api.log("Error: already aborted");
2863
					return;
2864
				}
2865
				xhr = _this.xhr = api.getXHR();
2866
2867
				if (data.params) {
2868
					url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2869
				}
2870
2871
				xhr.open('POST', url, true);
2872
2873
				if( api.withCredentials ){
2874
					xhr.withCredentials = "true";
2875
				}
2876
2877
				if( !options.headers || !options.headers['X-Requested-With'] ){
2878
					xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2879
				}
2880
2881
				api.each(options.headers, function (val, key){
2882
					xhr.setRequestHeader(key, val);
2883
				});
2884
2885
2886
				if ( options._chunked ) {
2887
					// chunked upload
2888
					if( xhr.upload ){
2889
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2890
							if (!data.retry) {
2891
								// show progress only for correct chunk uploads
2892
								options.progress({
2893
									  type:			evt.type
2894
									, total:		data.size
2895
									, loaded:		data.start + evt.loaded
2896
									, totalSize:	data.size
2897
								}, _this, options);
2898
							}
2899
						}, 100), false);
2900
					}
2901
2902
					xhr.onreadystatechange = function (){
2903
						var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2904
2905
						_this.status     = xhr.status;
2906
						_this.statusText = xhr.statusText;
2907
						_this.readyState = xhr.readyState;
2908
2909
						if( xhr.readyState == 4 ){
2910
							try {
2911
								for( var k in _xhrResponsePostfix ){
2912
									_this['response'+k]  = xhr['response'+k];
2913
								}
2914
							}catch(_){}
2915
							xhr.onreadystatechange = null;
2916
2917
							if (!xhr.status || xhr.status - 201 > 0) {
2918
								api.log("Error: " + xhr.status);
2919
								// some kind of error
2920
								// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2921
								// up - server error
2922
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2923
									// let's try again the same chunk
2924
									// only applicable for recoverable error codes 500 && 416
2925
									var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2926
2927
									// inform about recoverable problems
2928
									options.pause(data.file, options);
2929
2930
									// smart restart if server reports about the last known byte
2931
									api.log("X-Last-Known-Byte: " + lkb);
2932
									if (lkb) {
2933
										data.end = lkb;
2934
									} else {
2935
										data.end = data.start - 1;
2936
										if (416 == xhr.status) {
2937
											data.end = data.end - options.chunkSize;
2938
										}
2939
									}
2940
2941
									setTimeout(function () {
2942
										_this._send(options, data);
2943
									}, delay);
2944
								} else {
2945
									// no mo retries
2946
									_this.end(xhr.status);
2947
								}
2948
							} else {
2949
								// success
2950
								data.retry = 0;
2951
2952
								if (data.end == data.size - 1) {
2953
									// finished
2954
									_this.end(xhr.status);
2955
								} else {
2956
									// next chunk
2957
2958
									// shift position if server reports about the last known byte
2959
									api.log("X-Last-Known-Byte: " + lkb);
2960
									if (lkb) {
2961
										data.end = lkb;
2962
									}
2963
									data.file.FileAPIReadPosition = data.end;
2964
2965
									setTimeout(function () {
2966
										_this._send(options, data);
2967
									}, 0);
2968
								}
2969
							}
2970
2971
							xhr = null;
2972
						}
2973
					};
2974
2975
					data.start = data.end + 1;
2976
					data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2977
2978
					// Retrieve a slice of file
2979
					var
2980
						  file = data.file
2981
						, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2982
					;
2983
2984
					if( data.size && !slice.size ){
2985
						setTimeout(function (){
2986
							_this.end(-1);
2987
						});
2988
					} else {
2989
						xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2990
						xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2991
						xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2992
2993
						xhr.send(slice);
2994
					}
2995
2996
					file = slice = null;
2997
				} else {
2998
					// single piece upload
2999
					if( xhr.upload ){
3000
						// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3001
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3002
							options.progress(evt, _this, options);
3003
						}, 100), false);
3004
					}
3005
3006
					xhr.onreadystatechange = function (){
3007
						_this.status     = xhr.status;
3008
						_this.statusText = xhr.statusText;
3009
						_this.readyState = xhr.readyState;
3010
3011
						if( xhr.readyState == 4 ){
3012
							for( var k in _xhrResponsePostfix ){
3013
								_this['response'+k]  = xhr['response'+k];
3014
							}
3015
							xhr.onreadystatechange = null;
3016
3017
							if (!xhr.status || xhr.status > 201) {
3018
								api.log("Error: " + xhr.status);
3019
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3020
									options.retry = (options.retry || 0) + 1;
3021
									var delay = api.networkDownRetryTimeout;
3022
3023
									// inform about recoverable problems
3024
									options.pause(options.file, options);
3025
3026
									setTimeout(function () {
3027
										_this._send(options, data);
3028
									}, delay);
3029
								} else {
3030
									//success
3031
									_this.end(xhr.status);
3032
								}
3033
							} else {
3034
								//success
3035
								_this.end(xhr.status);
3036
							}
3037
3038
							xhr = null;
3039
						}
3040
					};
3041
3042
					if( api.isArray(data) ){
3043
						// multipart
3044
						xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3045
						var rawData = data.join('') +'--_'+ api.expando +'--';
3046
3047
						/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3048
						if( xhr.sendAsBinary ){
3049
							xhr.sendAsBinary(rawData);
3050
						}
3051
						else {
3052
							var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3053
							xhr.send(new Uint8Array(bytes).buffer);
3054
3055
						}
3056
					} else {
3057
						// FormData
3058
						xhr.send(data);
3059
					}
3060
				}
3061
			}
3062
		}
3063
	};
3064
3065
3066
	// @export
3067
	api.XHR = XHR;
3068
})(window, FileAPI);
3069
3070
/**
3071
 * @class	FileAPI.Camera
3072
 * @author	RubaXa	<[email protected]>
3073
 * @support	Chrome 21+, FF 18+, Opera 12+
3074
 */
3075
3076
/*global window, FileAPI, jQuery */
3077
/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3078
(function (window, api){
3079
	"use strict";
3080
3081
	var
3082
		URL = window.URL || window.webkitURL,
3083
3084
		document = window.document,
3085
		navigator = window.navigator,
3086
3087
		getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3088
3089
		html5 = !!getMedia
3090
	;
3091
3092
3093
	// Support "media"
3094
	api.support.media = html5;
3095
3096
3097
	var Camera = function (video){
3098
		this.video = video;
3099
	};
3100
3101
3102
	Camera.prototype = {
3103
		isActive: function (){
3104
			return	!!this._active;
3105
		},
3106
3107
3108
		/**
3109
		 * Start camera streaming
3110
		 * @param	{Function}	callback
3111
		 */
3112
		start: function (callback){
3113
			var
3114
				  _this = this
3115
				, video = _this.video
3116
				, _successId
3117
				, _failId
3118
				, _complete = function (err){
3119
					_this._active = !err;
3120
					clearTimeout(_failId);
3121
					clearTimeout(_successId);
3122
//					api.event.off(video, 'loadedmetadata', _complete);
3123
					callback && callback(err, _this);
3124
				}
3125
			;
3126
3127
			getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3128
				// Success
3129
				_this.stream = stream;
3130
3131
//				api.event.on(video, 'loadedmetadata', function (){
3132
//					_complete(null);
3133
//				});
3134
3135
				// Set camera stream
3136
				video.src = URL.createObjectURL(stream);
3137
3138
				// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3139
				// See crbug.com/110938.
3140
				_successId = setInterval(function (){
3141
					if( _detectVideoSignal(video) ){
3142
						_complete(null);
3143
					}
3144
				}, 1000);
3145
3146
				_failId = setTimeout(function (){
3147
					_complete('timeout');
3148
				}, 5000);
3149
3150
				// Go-go-go!
3151
				video.play();
3152
			}, _complete/*error*/);
3153
		},
3154
3155
3156
		/**
3157
		 * Stop camera streaming
3158
		 */
3159
		stop: function (){
3160
			try {
3161
				this._active = false;
3162
				this.video.pause();
3163
				this.stream.stop();
3164
			} catch( err ){ }
3165
		},
3166
3167
3168
		/**
3169
		 * Create screenshot
3170
		 * @return {FileAPI.Camera.Shot}
3171
		 */
3172
		shot: function (){
3173
			return	new Shot(this.video);
3174
		}
3175
	};
3176
3177
3178
	/**
3179
	 * Get camera element from container
3180
	 *
3181
	 * @static
3182
	 * @param	{HTMLElement}	el
3183
	 * @return	{Camera}
3184
	 */
3185
	Camera.get = function (el){
3186
		return	new Camera(el.firstChild);
3187
	};
3188
3189
3190
	/**
3191
	 * Publish camera element into container
3192
	 *
3193
	 * @static
3194
	 * @param	{HTMLElement}	el
3195
	 * @param	{Object}		options
3196
	 * @param	{Function}		[callback]
3197
	 */
3198
	Camera.publish = function (el, options, callback){
3199
		if( typeof options == 'function' ){
3200
			callback = options;
3201
			options = {};
3202
		}
3203
3204
		// Dimensions of "camera"
3205
		options = api.extend({}, {
3206
			  width:	'100%'
3207
			, height:	'100%'
3208
			, start:	true
3209
		}, options);
3210
3211
3212
		if( el.jquery ){
3213
			// Extract first element, from jQuery collection
3214
			el = el[0];
3215
		}
3216
3217
3218
		var doneFn = function (err){
3219
			if( err ){
3220
				callback(err);
3221
			}
3222
			else {
3223
				// Get camera
3224
				var cam = Camera.get(el);
3225
				if( options.start ){
3226
					cam.start(callback);
3227
				}
3228
				else {
3229
					callback(null, cam);
3230
				}
3231
			}
3232
		};
3233
3234
3235
		el.style.width	= _px(options.width);
3236
		el.style.height	= _px(options.height);
3237
3238
3239
		if( api.html5 && html5 ){
3240
			// Create video element
3241
			var video = document.createElement('video');
3242
3243
			// Set dimensions
3244
			video.style.width	= _px(options.width);
3245
			video.style.height	= _px(options.height);
3246
3247
			// Clean container
3248
			if( window.jQuery ){
3249
				jQuery(el).empty();
3250
			} else {
3251
				el.innerHTML = '';
3252
			}
3253
3254
			// Add "camera" to container
3255
			el.appendChild(video);
3256
3257
			// end
3258
			doneFn();
3259
		}
3260
		else {
3261
			Camera.fallback(el, options, doneFn);
3262
		}
3263
	};
3264
3265
3266
	Camera.fallback = function (el, options, callback){
3267
		callback('not_support_camera');
3268
	};
3269
3270
3271
	/**
3272
	 * @class	FileAPI.Camera.Shot
3273
	 */
3274
	var Shot = function (video){
3275
		var canvas	= video.nodeName ? api.Image.toCanvas(video) : video;
3276
		var shot	= api.Image(canvas);
3277
		shot.type	= 'image/png';
3278
		shot.width	= canvas.width;
3279
		shot.height	= canvas.height;
3280
		shot.size	= canvas.width * canvas.height * 4;
3281
		return	shot;
3282
	};
3283
3284
3285
	/**
3286
	 * Add "px" postfix, if value is a number
3287
	 *
3288
	 * @private
3289
	 * @param	{*}  val
3290
	 * @return	{String}
3291
	 */
3292
	function _px(val){
3293
		return	val >= 0 ? val + 'px' : val;
3294
	}
3295
3296
3297
	/**
3298
	 * @private
3299
	 * @param	{HTMLVideoElement} video
3300
	 * @return	{Boolean}
3301
	 */
3302
	function _detectVideoSignal(video){
3303
		var canvas = document.createElement('canvas'), ctx, res = false;
3304
		try {
3305
			ctx = canvas.getContext('2d');
3306
			ctx.drawImage(video, 0, 0, 1, 1);
3307
			res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3308
		}
3309
		catch( e ){}
3310
		return	res;
3311
	}
3312
3313
3314
	// @export
3315
	Camera.Shot	= Shot;
3316
	api.Camera	= Camera;
3317
})(window, FileAPI);
3318
3319
/**
3320
 * FileAPI fallback to Flash
3321
 *
3322
 * @flash-developer  "Vladimir Demidov" <[email protected]>
3323
 */
3324
3325
/*global window, ActiveXObject, FileAPI */
3326
(function (window, jQuery, api) {
3327
	"use strict";
3328
3329
	var
3330
		  document = window.document
3331
		, location = window.location
3332
		, navigator = window.navigator
3333
		, _each = api.each
3334
	;
3335
3336
3337
	api.support.flash = (function (){
3338
		var mime = navigator.mimeTypes, has = false;
3339
3340
		if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3341
			has	= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3342
		}
3343
		else {
3344
			try {
3345
				has	= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3346
			}
3347
			catch(er){
3348
				api.log('Flash -- does not supported.');
3349
			}
3350
		}
3351
3352
		if( has && /^file:/i.test(location) ){
3353
			api.log('[warn] Flash does not work on `file:` protocol.');
3354
		}
3355
3356
		return	has;
3357
	})();
3358
3359
3360
	   api.support.flash
3361
	&& (0
3362
		|| !api.html5 || !api.support.html5
3363
		|| (api.cors && !api.support.cors)
3364
		|| (api.media && !api.support.media)
3365
	)
3366
	&& (function (){
3367
		var
3368
			  _attr  = api.uid()
3369
			, _retry = 0
3370
			, _files = {}
3371
			, _rhttp = /^https?:/i
3372
3373
			, flash = {
3374
				_fn: {},
3375
3376
3377
				/**
3378
				 * Initialization & preload flash object
3379
				 */
3380
				init: function (){
3381
					var child = document.body && document.body.firstChild;
3382
3383
					if( child ){
3384
						do {
3385
							if( child.nodeType == 1 ){
3386
								api.log('FlashAPI.state: awaiting');
3387
3388
								var dummy = document.createElement('div');
3389
3390
								dummy.id = '_' + _attr;
3391
3392
								_css(dummy, {
3393
									  top: 1
3394
									, right: 1
3395
									, width: 5
3396
									, height: 5
3397
									, position: 'absolute'
3398
									, zIndex: 1e6+'' // set max zIndex
3399
								});
3400
3401
								child.parentNode.insertBefore(dummy, child);
3402
								flash.publish(dummy, _attr);
3403
3404
								return;
3405
							}
3406
						}
3407
						while( child = child.nextSibling );
3408
					}
3409
3410
					if( _retry < 10 ){
3411
						setTimeout(flash.init, ++_retry*50);
3412
					}
3413
				},
3414
3415
3416
				/**
3417
				 * Publish flash-object
3418
				 *
3419
				 * @param {HTMLElement} el
3420
				 * @param {String} id
3421
				 * @param {Object} [opts]
3422
				 */
3423
				publish: function (el, id, opts){
3424
					opts = opts || {};
3425
					el.innerHTML = _makeFlashHTML({
3426
						  id: id
3427
						, src: _getUrl(api.flashUrl, 'r=' + api.version)
3428
//						, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3429
						, wmode: opts.camera ? '' : 'transparent'
3430
						, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3431
							+ '&flashId='+ id
3432
							+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3433
							+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3434
							+ '&timeout='+api.flashAbortTimeout
3435
							+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3436
							+ '&debug='+(api.debug?"1":"")
3437
					}, opts);
3438
				},
3439
3440
3441
				ready: function (){
3442
					api.log('FlashAPI.state: ready');
3443
3444
					flash.ready = api.F;
3445
					flash.isReady = true;
3446
					flash.patch();
3447
					flash.patchCamera && flash.patchCamera();
3448
					api.event.on(document, 'mouseover', flash.mouseover);
3449
					api.event.on(document, 'click', function (evt){
3450
						if( flash.mouseover(evt) ){
3451
							evt.preventDefault
3452
								? evt.preventDefault()
3453
								: (evt.returnValue = true)
3454
							;
3455
						}
3456
					});
3457
				},
3458
3459
3460
				getEl: function (){
3461
					return	document.getElementById('_'+_attr);
3462
				},
3463
3464
3465
				getWrapper: function (node){
3466
					do {
3467
						if( /js-fileapi-wrapper/.test(node.className) ){
3468
							return	node;
3469
						}
3470
					}
3471
					while( (node = node.parentNode) && (node !== document.body) );
3472
				},
3473
				
3474
				disableMouseover: false,
3475
3476
				mouseover: function (evt){
3477
					if (!flash.disableMouseover) {
3478
						var target = api.event.fix(evt).target;
3479
	
3480
						if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3481
							var
3482
								  state = target.getAttribute(_attr)
3483
								, wrapper = flash.getWrapper(target)
3484
							;
3485
	
3486
							if( api.multiFlash ){
3487
								// check state:
3488
								//   i — published
3489
								//   i — initialization
3490
								//   r — ready
3491
								if( state == 'i' || state == 'r' ){
3492
									// publish fail
3493
									return	false;
3494
								}
3495
								else if( state != 'p' ){
3496
									// set "init" state
3497
									target.setAttribute(_attr, 'i');
3498
	
3499
									var dummy = document.createElement('div');
3500
	
3501
									if( !wrapper ){
3502
										api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3503
										return;
3504
									}
3505
	
3506
									_css(dummy, {
3507
										  top:    0
3508
										, left:   0
3509
										, width:  target.offsetWidth
3510
										, height: target.offsetHeight
3511
										, zIndex: 1e6+'' // set max zIndex
3512
										, position: 'absolute'
3513
									});
3514
	
3515
									wrapper.appendChild(dummy);
3516
									flash.publish(dummy, api.uid());
3517
	
3518
									// set "publish" state
3519
									target.setAttribute(_attr, 'p');
3520
								}
3521
	
3522
								return	true;
3523
							}
3524
							else if( wrapper ){
3525
								// Use one flash element
3526
								var box = _getDimensions(wrapper);
3527
								_css(flash.getEl(), box);
3528
	
3529
								// Set current input
3530
								flash.curInp = target;
3531
							}
3532
						}
3533
						else if( !/object|embed/i.test(target.nodeName) ){
3534
							_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3535
						}
3536
					}
3537
				},
3538
3539
				onEvent: function (evt){
3540
					var type = evt.type;
3541
					
3542
					if( type == 'ready' ){
3543
						try {
3544
							// set "ready" state
3545
							flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3546
						} catch (e){
3547
						}
3548
3549
						flash.ready();
3550
						setTimeout(function (){ flash.mouseenter(evt); }, 50);
3551
						return	true;
3552
					}
3553
					else if( type === 'ping' ){
3554
						api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3555
					}
3556
					else if( type === 'log' ){
3557
						api.log('(flash -> js).log:', evt.target);
3558
					}
3559
					else if( type in flash ){
3560
						setTimeout(function (){
3561
							api.log('FlashAPI.event.'+evt.type+':', evt);
3562
							flash[type](evt);
3563
						}, 1);
3564
					}
3565
				},
3566
				mouseDown: function(evt) {
3567
					flash.disableMouseover = true;
3568
				},
3569
				cancel: function(evt) {
3570
					flash.disableMouseover = false;
3571
				},
3572
				mouseenter: function (evt){
3573
					var node = flash.getInput(evt.flashId);
3574
3575
					if( node ){
3576
						// Set multiple mode
3577
						flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3578
3579
3580
						// Set files filter
3581
						var accept = [], exts = {};
3582
3583
						_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3584
							api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3585
								exts[ext] = 1;
3586
							});
3587
						});
3588
3589
						_each(exts, function (i, ext){
3590
							accept.push( ext );
3591
						});
3592
3593
						flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3594
					}
3595
				},
3596
3597
3598
				get: function (id){
3599
					return	document[id] || window[id] || document.embeds[id];
3600
				},
3601
3602
3603
				getInput: function (id){
3604
					if( api.multiFlash ){
3605
						try {
3606
							var node = flash.getWrapper(flash.get(id));
3607
							if( node ){
3608
								return node.getElementsByTagName('input')[0];
3609
							}
3610
						} catch (e){
3611
							api.log('[err] Can not find "input" by flashId:', id, e);
3612
						}
3613
					} else {
3614
						return	flash.curInp;
3615
					}
3616
				},
3617
3618
3619
				select: function (evt){
3620
					try {
3621
						var
3622
							  inp = flash.getInput(evt.flashId)
3623
							, uid = api.uid(inp)
3624
							, files = evt.target.files
3625
							, event
3626
						;
3627
						_each(files, function (file){
3628
							api.checkFileObj(file);
3629
						});
3630
	
3631
						_files[uid] = files;
3632
	
3633
						if( document.createEvent ){
3634
							event = document.createEvent('Event');
3635
							event.files = files;
3636
							event.initEvent('change', true, true);
3637
							inp.dispatchEvent(event);
3638
						}
3639
						else if( jQuery ){
3640
							jQuery(inp).trigger({ type: 'change', files: files });
3641
						}
3642
						else {
3643
							event = document.createEventObject();
3644
							event.files = files;
3645
							inp.fireEvent('onchange', event);
3646
						}
3647
					} finally {
3648
						flash.disableMouseover = false;
3649
					}
3650
				},
3651
3652
				interval: null,
3653
				cmd: function (id, name, data, last) {
3654
					if (flash.uploadInProgress && flash.readInProgress) {
3655
						setTimeout(function() {
3656
							flash.cmd(id, name, data, last);
3657
						}, 100);
3658
					} else {
3659
						this.cmdFn(id, name, data, last);
3660
					}
3661
				},
3662
				
3663
				cmdFn: function(id, name, data, last) {
3664
					try {
3665
						api.log('(js -> flash).'+name+':', data);
3666
						return flash.get(id.flashId || id).cmd(name, data);
3667
					} catch (e){
3668
						api.log('(js -> flash).onError:', e);
3669
						if( !last ){
3670
							// try again
3671
							setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3672
						}
3673
					}
3674
				},
3675
3676
				patch: function (){
3677
					api.flashEngine = true;
3678
3679
					// FileAPI
3680
					_inherit(api, {
3681
						readAsDataURL: function (file, callback){
3682
							if( _isHtmlFile(file) ){
3683
								this.parent.apply(this, arguments);
3684
							}
3685
							else {
3686
								api.log('FlashAPI.readAsBase64');
3687
								flash.readInProgress = true;
3688
								flash.cmd(file, 'readAsBase64', {
3689
									id: file.id,
3690
									callback: _wrap(function _(err, base64){
3691
										flash.readInProgress = false;
3692
										_unwrap(_);
3693
3694
										api.log('FlashAPI.readAsBase64:', err);
3695
3696
										callback({
3697
											  type: err ? 'error' : 'load'
3698
											, error: err
3699
											, result: 'data:'+ file.type +';base64,'+ base64
3700
										});
3701
									})
3702
								});
3703
							}
3704
						},
3705
3706
						readAsText: function (file, encoding, callback){
3707
							if( callback ){
3708
								api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3709
							} else {
3710
								callback = encoding;
3711
							}
3712
3713
							api.readAsDataURL(file, function (evt){
3714
								if( evt.type == 'load' ){
3715
									try {
3716
										evt.result = window.atob(evt.result.split(';base64,')[1]);
3717
									} catch( err ){
3718
										evt.type = 'error';
3719
										evt.error = err.toString();
3720
									}
3721
								}
3722
								callback(evt);
3723
							});
3724
						},
3725
						
3726
						getFiles: function (input, filter, callback){
3727
							if( callback ){
3728
								api.filterFiles(api.getFiles(input), filter, callback);
3729
								return null;
3730
							}
3731
3732
							var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3733
3734
3735
							if( !files ){
3736
								// Файлов нету, вызываем родительский метод
3737
								return	this.parent.apply(this, arguments);
3738
							}
3739
3740
3741
							if( filter ){
3742
								filter	= api.getFilesFilter(filter);
3743
								files	= api.filter(files, function (file){ return filter.test(file.name); });
3744
							}
3745
3746
							return	files;
3747
						},
3748
3749
3750
						getInfo: function (file, fn){
3751
							if( _isHtmlFile(file) ){
3752
								this.parent.apply(this, arguments);
3753
							}
3754
							else if( file.isShot ){
3755
								fn(null, file.info = {
3756
									width: file.width,
3757
									height: file.height
3758
								});
3759
							}
3760
							else {
3761
								if( !file.__info ){
3762
									var defer = file.__info = api.defer();
3763
3764
//									flash.cmd(file, 'getFileInfo', {
3765
//										  id: file.id
3766
//										, callback: _wrap(function _(err, info){
3767
//											_unwrap(_);
3768
//											defer.resolve(err, file.info = info);
3769
//										})
3770
//									});
3771
									defer.resolve(null, file.info = null);
3772
3773
								}
3774
3775
								file.__info.then(fn);
3776
							}
3777
						}
3778
					});
3779
3780
3781
					// FileAPI.Image
3782
					api.support.transform = true;
3783
					api.Image && _inherit(api.Image.prototype, {
3784
						get: function (fn, scaleMode){
3785
							this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3786
							return this.parent(fn);
3787
						},
3788
3789
						_load: function (file, fn){
3790
							api.log('FlashAPI.Image._load:', file);
3791
3792
							if( _isHtmlFile(file) ){
3793
								this.parent.apply(this, arguments);
3794
							}
3795
							else {
3796
								var _this = this;
3797
								api.getInfo(file, function (err){
3798
									fn.call(_this, err, file);
3799
								});
3800
							}
3801
						},
3802
3803
						_apply: function (file, fn){
3804
							api.log('FlashAPI.Image._apply:', file);
3805
3806
							if( _isHtmlFile(file) ){
3807
								this.parent.apply(this, arguments);
3808
							}
3809
							else {
3810
								var m = this.getMatrix(file.info), doneFn = fn;
3811
3812
								flash.cmd(file, 'imageTransform', {
3813
									  id: file.id
3814
									, matrix: m
3815
									, callback: _wrap(function _(err, base64){
3816
										api.log('FlashAPI.Image._apply.callback:', err);
3817
										_unwrap(_);
3818
3819
										if( err ){
3820
											doneFn(err);
3821
										}
3822
										else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3823
											_makeFlashImage({
3824
												  width:	(m.deg % 180) ? m.dh : m.dw
3825
												, height:	(m.deg % 180) ? m.dw : m.dh
3826
												, scale:	m.scaleMode
3827
											}, base64, doneFn);
3828
										}
3829
										else {
3830
											if( m.filter ){
3831
												doneFn = function (err, img){
3832
													if( err ){
3833
														fn(err);
3834
													}
3835
													else {
3836
														api.Image.applyFilter(img, m.filter, function (){
3837
															fn(err, this.canvas);
3838
														});
3839
													}
3840
												};
3841
											}
3842
3843
											api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3844
										}
3845
									})
3846
								});
3847
							}
3848
						},
3849
3850
						toData: function (fn){
3851
							var
3852
								  file = this.file
3853
								, info = file.info
3854
								, matrix = this.getMatrix(info)
3855
							;
3856
							api.log('FlashAPI.Image.toData');
3857
3858
							if( _isHtmlFile(file) ){
3859
								this.parent.apply(this, arguments);
3860
							}
3861
							else {
3862
								if( matrix.deg == 'auto' ){
3863
									matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3864
								}
3865
3866
								fn.call(this, !file.info, {
3867
									  id:		file.id
3868
									, flashId:	file.flashId
3869
									, name:		file.name
3870
									, type:		file.type
3871
									, matrix:	matrix
3872
								});
3873
							}
3874
						}
3875
					});
3876
3877
3878
					api.Image && _inherit(api.Image, {
3879
						fromDataURL: function (dataURL, size, callback){
3880
							if( !api.support.dataURI || dataURL.length > 3e4 ){
3881
								_makeFlashImage(
3882
									  api.extend({ scale: 'exactFit' }, size)
3883
									, dataURL.replace(/^data:[^,]+,/, '')
3884
									, function (err, el){ callback(el); }
3885
								);
3886
							}
3887
							else {
3888
								this.parent(dataURL, size, callback);
3889
							}
3890
						}
3891
					});
3892
3893
					// FileAPI.Form
3894
					_inherit(api.Form.prototype, {
3895
						toData: function (fn){
3896
							var items = this.items, i = items.length;
3897
3898
							for( ; i--; ){
3899
								if( items[i].file && _isHtmlFile(items[i].blob) ){
3900
									return this.parent.apply(this, arguments);
3901
								}
3902
							}
3903
3904
							api.log('FlashAPI.Form.toData');
3905
							fn(items);
3906
						}
3907
					});
3908
3909
3910
					// FileAPI.XHR
3911
					_inherit(api.XHR.prototype, {
3912
						_send: function (options, formData){
3913
							if(
3914
								   formData.nodeName
3915
								|| formData.append && api.support.html5
3916
								|| api.isArray(formData) && (typeof formData[0] === 'string')
3917
							){
3918
								// HTML5, Multipart or IFrame
3919
								return	this.parent.apply(this, arguments);
3920
							}
3921
3922
3923
							var
3924
								  data = {}
3925
								, files = {}
3926
								, _this = this
3927
								, flashId
3928
								, fileId
3929
							;
3930
3931
							_each(formData, function (item){
3932
								if( item.file ){
3933
									files[item.name] = item = _getFileDescr(item.blob);
3934
									fileId  = item.id;
3935
									flashId = item.flashId;
3936
								}
3937
								else {
3938
									data[item.name] = item.blob;
3939
								}
3940
							});
3941
3942
							if( !fileId ){
3943
								flashId = _attr;
3944
							}
3945
3946
							if( !flashId ){
3947
								api.log('[err] FlashAPI._send: flashId -- undefined');
3948
								return this.parent.apply(this, arguments);
3949
							}
3950
							else {
3951
								api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3952
							}
3953
3954
							_this.xhr = {
3955
								headers: {},
3956
								abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3957
								getResponseHeader: function (name){ return this.headers[name]; },
3958
								getAllResponseHeaders: function (){ return this.headers; }
3959
							};
3960
3961
							var queue = api.queue(function (){
3962
								flash.uploadInProgress = true;
3963
								flash.cmd(flashId, 'upload', {
3964
									  url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3965
									, data: data
3966
									, files: fileId ? files : null
3967
									, headers: options.headers || {}
3968
									, callback: _wrap(function upload(evt){
3969
										var type = evt.type, result = evt.result;
3970
3971
										api.log('FlashAPI.upload.'+type);
3972
3973
										if( type == 'progress' ){
3974
											evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3975
											evt.lengthComputable = true;
3976
											options.progress(evt);
3977
										}
3978
										else if( type == 'complete' ){
3979
											flash.uploadInProgress = false;
3980
											_unwrap(upload);
3981
3982
											if( typeof result == 'string' ){
3983
												_this.responseText	= result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3984
											}
3985
3986
											_this.end(evt.status || 200);
3987
										}
3988
										else if( type == 'abort' || type == 'error' ){
3989
											flash.uploadInProgress = false;
3990
											_this.end(evt.status || 0, evt.message);
3991
											_unwrap(upload);
3992
										}
3993
									})
3994
								});
3995
							});
3996
3997
3998
							// #2174: FileReference.load() call while FileReference.upload() or vice versa
3999
							_each(files, function (file){
4000
								queue.inc();
4001
								api.getInfo(file, queue.next);
4002
							});
4003
4004
							queue.check();
4005
						}
4006
					});
4007
				}
4008
			}
4009
		;
4010
4011
4012
		function _makeFlashHTML(opts){
4013
			return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4014
				+ '<param name="movie" value="#src#" />'
4015
				+ '<param name="flashvars" value="#flashvars#" />'
4016
				+ '<param name="swliveconnect" value="true" />'
4017
				+ '<param name="allowscriptaccess" value="always" />'
4018
				+ '<param name="allownetworking" value="all" />'
4019
				+ '<param name="menu" value="false" />'
4020
				+ '<param name="wmode" value="#wmode#" />'
4021
				+ '<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>'
4022
				+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4023
			;
4024
		}
4025
4026
4027
		function _css(el, css){
4028
			if( el && el.style ){
4029
				var key, val;
4030
				for( key in css ){
4031
					val = css[key];
4032
					if( typeof val == 'number' ){
4033
						val += 'px';
4034
					}
4035
					try { el.style[key] = val; } catch (e) {}
4036
				}
4037
				
4038
			}
4039
		}
4040
4041
4042
		function _inherit(obj, methods){
4043
			_each(methods, function (fn, name){
4044
				var prev = obj[name];
4045
				obj[name] = function (){
4046
					this.parent = prev;
4047
					return fn.apply(this, arguments);
4048
				};
4049
			});
4050
		}
4051
4052
		function _isHtmlFile(file){
4053
			return	file && !file.flashId;
4054
		}
4055
4056
		function _wrap(fn){
4057
			var id = fn.wid = api.uid();
4058
			flash._fn[id] = fn;
4059
			return	'FileAPI.Flash._fn.'+id;
4060
		}
4061
4062
4063
		function _unwrap(fn){
4064
			try {
4065
				flash._fn[fn.wid] = null;
4066
				delete	flash._fn[fn.wid];
4067
			}
4068
			catch(e){}
4069
		}
4070
4071
4072
		function _getUrl(url, params){
4073
			if( !_rhttp.test(url) ){
4074
				if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4075
					var path = location.pathname;
4076
					path = path.substr(0, path.lastIndexOf('/'));
4077
					url = (path +'/'+ url).replace('/./', '/');
4078
				}
4079
4080
				if( '//' != url.substr(0, 2) ){
4081
					url = '//' + location.host + url;
4082
				}
4083
4084
				if( !_rhttp.test(url) ){
4085
					url = location.protocol + url;
4086
				}
4087
			}
4088
4089
			if( params ){
4090
				url += (/\?/.test(url) ? '&' : '?') + params;
4091
			}
4092
4093
			return	url;
4094
		}
4095
4096
4097
		function _makeFlashImage(opts, base64, fn){
4098
			var
4099
				  key
4100
				, flashId = api.uid()
4101
				, el = document.createElement('div')
4102
				, attempts = 10
4103
			;
4104
4105
			for( key in opts ){
4106
				el.setAttribute(key, opts[key]);
4107
				el[key] = opts[key];
4108
			}
4109
4110
			_css(el, opts);
4111
4112
			opts.width	= '100%';
4113
			opts.height	= '100%';
4114
4115
			el.innerHTML = _makeFlashHTML(api.extend({
4116
				  id: flashId
4117
				, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4118
				, wmode: 'opaque'
4119
				, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4120
					_unwrap(_);
4121
					if( --attempts > 0 ){
4122
						_setImage();
4123
					}
4124
					return true;
4125
				})
4126
			}, opts));
4127
4128
			function _setImage(){
4129
				try {
4130
					// Get flash-object by id
4131
					var img = flash.get(flashId);
4132
					img.setImage(base64);
4133
				} catch (e){
4134
					api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4135
				}
4136
			}
4137
4138
			fn(false, el);
4139
			el = null;
4140
		}
4141
4142
4143
		function _getFileDescr(file){
4144
			return	{
4145
				  id: file.id
4146
				, name: file.name
4147
				, matrix: file.matrix
4148
				, flashId: file.flashId
4149
			};
4150
		}
4151
4152
4153
		function _getDimensions(el){
4154
			var
4155
				  box = el.getBoundingClientRect()
4156
				, body = document.body
4157
				, docEl = (el && el.ownerDocument).documentElement
4158
			;
4159
			
4160
			function getOffset(obj) {
4161
			    var left, top;
4162
			    left = top = 0;
4163
			    if (obj.offsetParent) {
4164
			        do {
4165
			            left += obj.offsetLeft;
4166
			            top  += obj.offsetTop;
4167
			        } while (obj = obj.offsetParent);
4168
			    }
4169
			    return {
4170
			        left : left,
4171
			        top : top
4172
			    };
4173
			};
4174
			
4175
			return {
4176
				  top:		getOffset(el).top
4177
				, left:		getOffset(el).left
4178
				, width:	el.offsetWidth
4179
				, height:	el.offsetHeight
4180
			};
4181
		}
4182
4183
		// @export
4184
		api.Flash = flash;
4185
4186
4187
		// Check dataURI support
4188
		api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4189
			api.support.dataURI = !(img.width != 1 || img.height != 1);
4190
			flash.init();
4191
		});
4192
	})();
4193
})(window, window.jQuery, FileAPI);
4194
4195
/**
4196
 * FileAPI fallback to Flash
4197
 *
4198
 * @flash-developer  "Vladimir Demidov" <[email protected]>
4199
 */
4200
4201
/*global window, FileAPI */
4202
(function (window, jQuery, api) {
4203
    "use strict";
4204
4205
    var _each = api.each,
4206
        _cameraQueue = [];
4207
4208
4209
    if (api.support.flash && (api.media && !api.support.media)) {
4210
        (function () {
4211
4212
            function _wrap(fn) {
4213
                var id = fn.wid = api.uid();
4214
                api.Flash._fn[id] = fn;
4215
                return 'FileAPI.Flash._fn.' + id;
4216
            }
4217
4218
4219
            function _unwrap(fn) {
4220
                try {
4221
                    api.Flash._fn[fn.wid] = null;
4222
                    delete api.Flash._fn[fn.wid];
4223
                } catch (e) {
4224
                }
4225
            }
4226
4227
            var flash = api.Flash;
4228
            api.extend(api.Flash, {
4229
4230
                patchCamera: function () {
4231
                    api.Camera.fallback = function (el, options, callback) {
4232
                        var camId = api.uid();
4233
                        api.log('FlashAPI.Camera.publish: ' + camId);
4234
                        flash.publish(el, camId, api.extend(options, {
4235
                            camera: true,
4236
                            onEvent: _wrap(function _(evt) {
4237
                                if (evt.type === 'camera') {
4238
                                    _unwrap(_);
4239
4240
                                    if (evt.error) {
4241
                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4242
                                        callback(evt.error);
4243
                                    } else {
4244
                                        api.log('FlashAPI.Camera.publish.success: ' + camId);
4245
                                        callback(null);
4246
                                    }
4247
                                }
4248
                            })
4249
                        }));
4250
                    };
4251
                    // Run
4252
                    _each(_cameraQueue, function (args) {
4253
                        api.Camera.fallback.apply(api.Camera, args);
4254
                    });
4255
                    _cameraQueue = [];
4256
4257
4258
                    // FileAPI.Camera:proto
4259
                    api.extend(api.Camera.prototype, {
4260
                        _id: function () {
4261
                            return this.video.id;
4262
                        },
4263
4264
                        start: function (callback) {
4265
                            var _this = this;
4266
                            flash.cmd(this._id(), 'camera.on', {
4267
                                callback: _wrap(function _(evt) {
4268
                                    _unwrap(_);
4269
4270
                                    if (evt.error) {
4271
                                        api.log('FlashAPI.camera.on.error: ' + evt.error);
4272
                                        callback(evt.error, _this);
4273
                                    } else {
4274
                                        api.log('FlashAPI.camera.on.success: ' + _this._id());
4275
                                        _this._active = true;
4276
                                        callback(null, _this);
4277
                                    }
4278
                                })
4279
                            });
4280
                        },
4281
4282
                        stop: function () {
4283
                            this._active = false;
4284
                            flash.cmd(this._id(), 'camera.off');
4285
                        },
4286
4287
                        shot: function () {
4288
                            api.log('FlashAPI.Camera.shot:', this._id());
4289
4290
                            var shot = api.Flash.cmd(this._id(), 'shot', {});
4291
                            shot.type = 'image/png';
4292
                            shot.flashId = this._id();
4293
                            shot.isShot = true;
4294
4295
                            return new api.Camera.Shot(shot);
4296
                        }
4297
                    });
4298
                }
4299
            });
4300
4301
            api.Camera.fallback = function () {
4302
                _cameraQueue.push(arguments);
4303
            };
4304
4305
        }());
4306
    }
4307
}(window, window.jQuery, FileAPI));
4308
if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
4309

third-party/angularjs-modules-plugins/ng-file-upload-5.0.9/src/FileAPI.js 1 location

@@ 22-4308 (lines=4287) @@
19
/*jslint nomen: true, regexp: true */
20
/*global window, atob, Blob, ArrayBuffer, Uint8Array */
21
22
(function (window) {
23
    'use strict';
24
    var CanvasPrototype = window.HTMLCanvasElement &&
25
            window.HTMLCanvasElement.prototype,
26
        hasBlobConstructor = window.Blob && (function () {
27
            try {
28
                return Boolean(new Blob());
29
            } catch (e) {
30
                return false;
31
            }
32
        }()),
33
        hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
34
            (function () {
35
                try {
36
                    return new Blob([new Uint8Array(100)]).size === 100;
37
                } catch (e) {
38
                    return false;
39
                }
40
            }()),
41
        BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
42
            window.MozBlobBuilder || window.MSBlobBuilder,
43
        dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
44
            window.ArrayBuffer && window.Uint8Array && function (dataURI) {
45
                var byteString,
46
                    arrayBuffer,
47
                    intArray,
48
                    i,
49
                    mimeString,
50
                    bb;
51
                if (dataURI.split(',')[0].indexOf('base64') >= 0) {
52
                    // Convert base64 to raw binary data held in a string:
53
                    byteString = atob(dataURI.split(',')[1]);
54
                } else {
55
                    // Convert base64/URLEncoded data component to raw binary data:
56
                    byteString = decodeURIComponent(dataURI.split(',')[1]);
57
                }
58
                // Write the bytes of the string to an ArrayBuffer:
59
                arrayBuffer = new ArrayBuffer(byteString.length);
60
                intArray = new Uint8Array(arrayBuffer);
61
                for (i = 0; i < byteString.length; i += 1) {
62
                    intArray[i] = byteString.charCodeAt(i);
63
                }
64
                // Separate out the mime component:
65
                mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
66
                // Write the ArrayBuffer (or ArrayBufferView) to a blob:
67
                if (hasBlobConstructor) {
68
                    return new Blob(
69
                        [hasArrayBufferViewSupport ? intArray : arrayBuffer],
70
                        {type: mimeString}
71
                    );
72
                }
73
                bb = new BlobBuilder();
74
                bb.append(arrayBuffer);
75
                return bb.getBlob(mimeString);
76
            };
77
    if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
78
        if (CanvasPrototype.mozGetAsFile) {
79
            CanvasPrototype.toBlob = function (callback, type, quality) {
80
                if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
81
                    callback(dataURLtoBlob(this.toDataURL(type, quality)));
82
                } else {
83
                    callback(this.mozGetAsFile('blob', type));
84
                }
85
            };
86
        } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
87
            CanvasPrototype.toBlob = function (callback, type, quality) {
88
                callback(dataURLtoBlob(this.toDataURL(type, quality)));
89
            };
90
        }
91
    }
92
    window.dataURLtoBlob = dataURLtoBlob;
93
})(window);
94
95
/*jslint evil: true */
96
/*global window, URL, webkitURL, ActiveXObject */
97
98
(function (window, undef){
99
	'use strict';
100
101
	var
102
		gid = 1,
103
		noop = function (){},
104
105
		document = window.document,
106
		doctype = document.doctype || {},
107
		userAgent = window.navigator.userAgent,
108
109
		// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
110
		apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
111
112
		Blob = window.Blob,
113
		File = window.File,
114
		FileReader = window.FileReader,
115
		FormData = window.FormData,
116
117
118
		XMLHttpRequest = window.XMLHttpRequest,
119
		jQuery = window.jQuery,
120
121
		html5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
122
				&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
123
124
		cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
125
126
		chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
127
128
		// https://github.com/blueimp/JavaScript-Canvas-to-Blob
129
		dataURLtoBlob = window.dataURLtoBlob,
130
131
132
		_rimg = /img/i,
133
		_rcanvas = /canvas/i,
134
		_rimgcanvas = /img|canvas/i,
135
		_rinput = /input/i,
136
		_rdata = /^data:[^,]+,/,
137
138
		_toString = {}.toString,
139
140
141
		Math = window.Math,
142
143
		_SIZE_CONST = function (pow){
144
			pow = new window.Number(Math.pow(1024, pow));
145
			pow.from = function (sz){ return Math.round(sz * this); };
146
			return	pow;
147
		},
148
149
		_elEvents = {}, // element event listeners
150
		_infoReader = [], // list of file info processors
151
152
		_readerEvents = 'abort progress error load loadend',
153
		_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
154
155
		currentTarget = 'currentTarget', // for minimize
156
		preventDefault = 'preventDefault', // and this too
157
158
		_isArray = function (ar) {
159
			return	ar && ('length' in ar);
160
		},
161
162
		/**
163
		 * Iterate over a object or array
164
		 */
165
		_each = function (obj, fn, ctx){
166
			if( obj ){
167
				if( _isArray(obj) ){
168
					for( var i = 0, n = obj.length; i < n; i++ ){
169
						if( i in obj ){
170
							fn.call(ctx, obj[i], i, obj);
171
						}
172
					}
173
				}
174
				else {
175
					for( var key in obj ){
176
						if( obj.hasOwnProperty(key) ){
177
							fn.call(ctx, obj[key], key, obj);
178
						}
179
					}
180
				}
181
			}
182
		},
183
184
		/**
185
		 * Merge the contents of two or more objects together into the first object
186
		 */
187
		_extend = function (dst){
188
			var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
189
			for( ; i < args.length; i++ ){
190
				_each(args[i], _ext);
191
			}
192
			return  dst;
193
		},
194
195
		/**
196
		 * Add event listener
197
		 */
198
		_on = function (el, type, fn){
199
			if( el ){
200
				var uid = api.uid(el);
201
202
				if( !_elEvents[uid] ){
203
					_elEvents[uid] = {};
204
				}
205
206
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
207
				_each(type.split(/\s+/), function (type){
208
					if( jQuery && !isFileReader){
209
						jQuery.event.add(el, type, fn);
210
					} else {
211
						if( !_elEvents[uid][type] ){
212
							_elEvents[uid][type] = [];
213
						}
214
215
						_elEvents[uid][type].push(fn);
216
217
						if( el.addEventListener ){ el.addEventListener(type, fn, false); }
218
						else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
219
						else { el['on'+type] = fn; }
220
					}
221
				});
222
			}
223
		},
224
225
226
		/**
227
		 * Remove event listener
228
		 */
229
		_off = function (el, type, fn){
230
			if( el ){
231
				var uid = api.uid(el), events = _elEvents[uid] || {};
232
233
				var isFileReader = (FileReader && el) && (el instanceof FileReader);
234
				_each(type.split(/\s+/), function (type){
235
					if( jQuery && !isFileReader){
236
						jQuery.event.remove(el, type, fn);
237
					}
238
					else {
239
						var fns = events[type] || [], i = fns.length;
240
241
						while( i-- ){
242
							if( fns[i] === fn ){
243
								fns.splice(i, 1);
244
								break;
245
							}
246
						}
247
248
						if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
249
						else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
250
						else { el['on'+type] = null; }
251
					}
252
				});
253
			}
254
		},
255
256
257
		_one = function(el, type, fn){
258
			_on(el, type, function _(evt){
259
				_off(el, type, _);
260
				fn(evt);
261
			});
262
		},
263
264
265
		_fixEvent = function (evt){
266
			if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
267
			if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
268
			return  evt;
269
		},
270
271
272
		_supportInputAttr = function (attr){
273
			var input = document.createElement('input');
274
			input.setAttribute('type', "file");
275
			return attr in input;
276
		},
277
278
279
		/**
280
		 * FileAPI (core object)
281
		 */
282
		api = {
283
			version: '2.0.7',
284
285
			cors: false,
286
			html5: true,
287
			media: false,
288
			formData: true,
289
			multiPassResize: true,
290
291
			debug: false,
292
			pingUrl: false,
293
			multiFlash: false,
294
			flashAbortTimeout: 0,
295
			withCredentials: true,
296
297
			staticPath: './dist/',
298
299
			flashUrl: 0, // @default: './FileAPI.flash.swf'
300
			flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
301
302
			postNameConcat: function (name, idx){
303
				return	name + (idx != null ? '['+ idx +']' : '');
304
			},
305
306
			ext2mime: {
307
				  jpg:	'image/jpeg'
308
				, tif:	'image/tiff'
309
				, txt:	'text/plain'
310
			},
311
312
			// Fallback for flash
313
			accept: {
314
				  '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'
315
				, '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'
316
				, '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'
317
			},
318
319
			uploadRetry : 0,
320
			networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
321
322
			chunkSize : 0,
323
			chunkUploadRetry : 0,
324
			chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
325
326
			KB: _SIZE_CONST(1),
327
			MB: _SIZE_CONST(2),
328
			GB: _SIZE_CONST(3),
329
			TB: _SIZE_CONST(4),
330
331
			EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
332
333
			expando: 'fileapi' + (new Date).getTime(),
334
335
			uid: function (obj){
336
				return	obj
337
					? (obj[api.expando] = obj[api.expando] || api.uid())
338
					: (++gid, api.expando + gid)
339
				;
340
			},
341
342
			log: function (){
343
				if( api.debug && window.console && console.log ){
344
					if( console.log.apply ){
345
						console.log.apply(console, arguments);
346
					}
347
					else {
348
						console.log([].join.call(arguments, ' '));
349
					}
350
				}
351
			},
352
353
			/**
354
			 * Create new image
355
			 *
356
			 * @param {String} [src]
357
			 * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element
358
			 * @returns {HTMLElement}
359
			 */
360
			newImage: function (src, fn){
361
				var img = document.createElement('img');
362
				if( fn ){
363
					api.event.one(img, 'error load', function (evt){
364
						fn(evt.type == 'error', img);
365
						img = null;
366
					});
367
				}
368
				img.src = src;
369
				return	img;
370
			},
371
372
			/**
373
			 * Get XHR
374
			 * @returns {XMLHttpRequest}
375
			 */
376
			getXHR: function (){
377
				var xhr;
378
379
				if( XMLHttpRequest ){
380
					xhr = new XMLHttpRequest;
381
				}
382
				else if( window.ActiveXObject ){
383
					try {
384
						xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
385
					} catch (e) {
386
						xhr = new ActiveXObject('Microsoft.XMLHTTP');
387
					}
388
				}
389
390
				return  xhr;
391
			},
392
393
			isArray: _isArray,
394
395
			support: {
396
				dnd:     cors && ('ondrop' in document.createElement('div')),
397
				cors:    cors,
398
				html5:   html5,
399
				chunked: chunked,
400
				dataURI: true,
401
				accept:   _supportInputAttr('accept'),
402
				multiple: _supportInputAttr('multiple')
403
			},
404
405
			event: {
406
				  on: _on
407
				, off: _off
408
				, one: _one
409
				, fix: _fixEvent
410
			},
411
412
413
			throttle: function(fn, delay) {
414
				var id, args;
415
416
				return function _throttle(){
417
					args = arguments;
418
419
					if( !id ){
420
						fn.apply(window, args);
421
						id = setTimeout(function (){
422
							id = 0;
423
							fn.apply(window, args);
424
						}, delay);
425
					}
426
				};
427
			},
428
429
430
			F: function (){},
431
432
433
			parseJSON: function (str){
434
				var json;
435
				if( window.JSON && JSON.parse ){
436
					json = JSON.parse(str);
437
				}
438
				else {
439
					json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
440
				}
441
				return json;
442
			},
443
444
445
			trim: function (str){
446
				str = String(str);
447
				return	str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
448
			},
449
450
			/**
451
			 * Simple Defer
452
			 * @return	{Object}
453
			 */
454
			defer: function (){
455
				var
456
					  list = []
457
					, result
458
					, error
459
					, defer = {
460
						resolve: function (err, res){
461
							defer.resolve = noop;
462
							error	= err || false;
463
							result	= res;
464
465
							while( res = list.shift() ){
466
								res(error, result);
467
							}
468
						},
469
470
						then: function (fn){
471
							if( error !== undef ){
472
								fn(error, result);
473
							} else {
474
								list.push(fn);
475
							}
476
						}
477
				};
478
479
				return	defer;
480
			},
481
482
			queue: function (fn){
483
				var
484
					  _idx = 0
485
					, _length = 0
486
					, _fail = false
487
					, _end = false
488
					, queue = {
489
						inc: function (){
490
							_length++;
491
						},
492
493
						next: function (){
494
							_idx++;
495
							setTimeout(queue.check, 0);
496
						},
497
498
						check: function (){
499
							(_idx >= _length) && !_fail && queue.end();
500
						},
501
502
						isFail: function (){
503
							return _fail;
504
						},
505
506
						fail: function (){
507
							!_fail && fn(_fail = true);
508
						},
509
510
						end: function (){
511
							if( !_end ){
512
								_end = true;
513
								fn();
514
							}
515
						}
516
					}
517
				;
518
				return queue;
519
			},
520
521
522
			/**
523
			 * For each object
524
			 *
525
			 * @param	{Object|Array}	obj
526
			 * @param	{Function}		fn
527
			 * @param	{*}				[ctx]
528
			 */
529
			each: _each,
530
531
532
			/**
533
			 * Async for
534
			 * @param {Array} array
535
			 * @param {Function} callback
536
			 */
537
			afor: function (array, callback){
538
				var i = 0, n = array.length;
539
540
				if( _isArray(array) && n-- ){
541
					(function _next(){
542
						callback(n != i && _next, array[i], i++);
543
					})();
544
				}
545
				else {
546
					callback(false);
547
				}
548
			},
549
550
551
			/**
552
			 * Merge the contents of two or more objects together into the first object
553
			 *
554
			 * @param	{Object}	dst
555
			 * @return	{Object}
556
			 */
557
			extend: _extend,
558
559
560
			/**
561
			 * Is file?
562
			 * @param  {File}  file
563
			 * @return {Boolean}
564
			 */
565
			isFile: function (file){
566
				return _toString.call(file) === '[object File]';
567
			},
568
569
570
			/**
571
			 * Is blob?
572
			 * @param   {Blob}  blob
573
			 * @returns {Boolean}
574
			 */
575
			isBlob: function (blob) {
576
				return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
577
			},
578
579
580
			/**
581
			 * Is canvas element
582
			 *
583
			 * @param	{HTMLElement}	el
584
			 * @return	{Boolean}
585
			 */
586
			isCanvas: function (el){
587
				return	el && _rcanvas.test(el.nodeName);
588
			},
589
590
591
			getFilesFilter: function (filter){
592
				filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
593
				return	filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
594
			},
595
596
597
598
			/**
599
			 * Read as DataURL
600
			 *
601
			 * @param {File|Element} file
602
			 * @param {Function} fn
603
			 */
604
			readAsDataURL: function (file, fn){
605
				if( api.isCanvas(file) ){
606
					_emit(file, fn, 'load', api.toDataURL(file));
607
				}
608
				else {
609
					_readAs(file, fn, 'DataURL');
610
				}
611
			},
612
613
614
			/**
615
			 * Read as Binary string
616
			 *
617
			 * @param {File} file
618
			 * @param {Function} fn
619
			 */
620
			readAsBinaryString: function (file, fn){
621
				if( _hasSupportReadAs('BinaryString') ){
622
					_readAs(file, fn, 'BinaryString');
623
				} else {
624
					// Hello IE10!
625
					_readAs(file, function (evt){
626
						if( evt.type == 'load' ){
627
							try {
628
								// dataURL -> binaryString
629
								evt.result = api.toBinaryString(evt.result);
630
							} catch (e){
631
								evt.type = 'error';
632
								evt.message = e.toString();
633
							}
634
						}
635
						fn(evt);
636
					}, 'DataURL');
637
				}
638
			},
639
640
641
			/**
642
			 * Read as ArrayBuffer
643
			 *
644
			 * @param {File} file
645
			 * @param {Function} fn
646
			 */
647
			readAsArrayBuffer: function(file, fn){
648
				_readAs(file, fn, 'ArrayBuffer');
649
			},
650
651
652
			/**
653
			 * Read as text
654
			 *
655
			 * @param {File} file
656
			 * @param {String} encoding
657
			 * @param {Function} [fn]
658
			 */
659
			readAsText: function(file, encoding, fn){
660
				if( !fn ){
661
					fn	= encoding;
662
					encoding = 'utf-8';
663
				}
664
665
				_readAs(file, fn, 'Text', encoding);
666
			},
667
668
669
			/**
670
			 * Convert image or canvas to DataURL
671
			 *
672
			 * @param   {Element}  el      Image or Canvas element
673
			 * @param   {String}   [type]  mime-type
674
			 * @return  {String}
675
			 */
676
			toDataURL: function (el, type){
677
				if( typeof el == 'string' ){
678
					return  el;
679
				}
680
				else if( el.toDataURL ){
681
					return  el.toDataURL(type || 'image/png');
682
				}
683
			},
684
685
686
			/**
687
			 * Canvert string, image or canvas to binary string
688
			 *
689
			 * @param   {String|Element} val
690
			 * @return  {String}
691
			 */
692
			toBinaryString: function (val){
693
				return  window.atob(api.toDataURL(val).replace(_rdata, ''));
694
			},
695
696
697
			/**
698
			 * Read file or DataURL as ImageElement
699
			 *
700
			 * @param	{File|String}	file
701
			 * @param	{Function}		fn
702
			 * @param	{Boolean}		[progress]
703
			 */
704
			readAsImage: function (file, fn, progress){
705
				if( api.isFile(file) ){
706
					if( apiURL ){
707
						/** @namespace apiURL.createObjectURL */
708
						var data = apiURL.createObjectURL(file);
709
						if( data === undef ){
710
							_emit(file, fn, 'error');
711
						}
712
						else {
713
							api.readAsImage(data, fn, progress);
714
						}
715
					}
716
					else {
717
						api.readAsDataURL(file, function (evt){
718
							if( evt.type == 'load' ){
719
								api.readAsImage(evt.result, fn, progress);
720
							}
721
							else if( progress || evt.type == 'error' ){
722
								_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
723
							}
724
						});
725
					}
726
				}
727
				else if( api.isCanvas(file) ){
728
					_emit(file, fn, 'load', file);
729
				}
730
				else if( _rimg.test(file.nodeName) ){
731
					if( file.complete ){
732
						_emit(file, fn, 'load', file);
733
					}
734
					else {
735
						var events = 'error abort load';
736
						_one(file, events, function _fn(evt){
737
							if( evt.type == 'load' && apiURL ){
738
								/** @namespace apiURL.revokeObjectURL */
739
								apiURL.revokeObjectURL(file.src);
740
							}
741
742
							_off(file, events, _fn);
743
							_emit(file, fn, evt, file);
744
						});
745
					}
746
				}
747
				else if( file.iframe ){
748
					_emit(file, fn, { type: 'error' });
749
				}
750
				else {
751
					// Created image
752
					var img = api.newImage(file.dataURL || file);
753
					api.readAsImage(img, fn, progress);
754
				}
755
			},
756
757
758
			/**
759
			 * Make file by name
760
			 *
761
			 * @param	{String}	name
762
			 * @return	{Array}
763
			 */
764
			checkFileObj: function (name){
765
				var file = {}, accept = api.accept;
766
767
				if( typeof name == 'object' ){
768
					file = name;
769
				}
770
				else {
771
					file.name = (name + '').split(/\\|\//g).pop();
772
				}
773
774
				if( file.type == null ){
775
					file.type = file.name.split('.').pop();
776
				}
777
778
				_each(accept, function (ext, type){
779
					ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
780
					if( ext.test(file.type) || api.ext2mime[file.type] ){
781
						file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
782
					}
783
				});
784
785
				return	file;
786
			},
787
788
789
			/**
790
			 * Get drop files
791
			 *
792
			 * @param	{Event}	evt
793
			 * @param	{Function} callback
794
			 */
795
			getDropFiles: function (evt, callback){
796
				var
797
					  files = []
798
					, dataTransfer = _getDataTransfer(evt)
799
					, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
800
					, queue = api.queue(function (){ callback(files); })
801
				;
802
803
				_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
804
					queue.inc();
805
806
					try {
807
						if( entrySupport ){
808
							_readEntryAsFiles(item, function (err, entryFiles){
809
								if( err ){
810
									api.log('[err] getDropFiles:', err);
811
								} else {
812
									files.push.apply(files, entryFiles);
813
								}
814
								queue.next();
815
							});
816
						}
817
						else {
818
							_isRegularFile(item, function (yes){
819
								yes && files.push(item);
820
								queue.next();
821
							});
822
						}
823
					}
824
					catch( err ){
825
						queue.next();
826
						api.log('[err] getDropFiles: ', err);
827
					}
828
				});
829
830
				queue.check();
831
			},
832
833
834
			/**
835
			 * Get file list
836
			 *
837
			 * @param	{HTMLInputElement|Event}	input
838
			 * @param	{String|Function}	[filter]
839
			 * @param	{Function}			[callback]
840
			 * @return	{Array|Null}
841
			 */
842
			getFiles: function (input, filter, callback){
843
				var files = [];
844
845
				if( callback ){
846
					api.filterFiles(api.getFiles(input), filter, callback);
847
					return null;
848
				}
849
850
				if( input.jquery ){
851
					// jQuery object
852
					input.each(function (){
853
						files = files.concat(api.getFiles(this));
854
					});
855
					input	= files;
856
					files	= [];
857
				}
858
859
				if( typeof filter == 'string' ){
860
					filter	= api.getFilesFilter(filter);
861
				}
862
863
				if( input.originalEvent ){
864
					// jQuery event
865
					input = _fixEvent(input.originalEvent);
866
				}
867
				else if( input.srcElement ){
868
					// IE Event
869
					input = _fixEvent(input);
870
				}
871
872
873
				if( input.dataTransfer ){
874
					// Drag'n'Drop
875
					input = input.dataTransfer;
876
				}
877
				else if( input.target ){
878
					// Event
879
					input = input.target;
880
				}
881
882
				if( input.files ){
883
					// Input[type="file"]
884
					files = input.files;
885
886
					if( !html5 ){
887
						// Partial support for file api
888
						files[0].blob	= input;
889
						files[0].iframe	= true;
890
					}
891
				}
892
				else if( !html5 && isInputFile(input) ){
893
					if( api.trim(input.value) ){
894
						files = [api.checkFileObj(input.value)];
895
						files[0].blob   = input;
896
						files[0].iframe = true;
897
					}
898
				}
899
				else if( _isArray(input) ){
900
					files	= input;
901
				}
902
903
				return	api.filter(files, function (file){ return !filter || filter.test(file.name); });
904
			},
905
906
907
			/**
908
			 * Get total file size
909
			 * @param	{Array}	files
910
			 * @return	{Number}
911
			 */
912
			getTotalSize: function (files){
913
				var size = 0, i = files && files.length;
914
				while( i-- ){
915
					size += files[i].size;
916
				}
917
				return	size;
918
			},
919
920
921
			/**
922
			 * Get image information
923
			 *
924
			 * @param	{File}		file
925
			 * @param	{Function}	fn
926
			 */
927
			getInfo: function (file, fn){
928
				var info = {}, readers = _infoReader.concat();
929
930
				if( api.isFile(file) ){
931
					(function _next(){
932
						var reader = readers.shift();
933
						if( reader ){
934
							if( reader.test(file.type) ){
935
								reader(file, function (err, res){
936
									if( err ){
937
										fn(err);
938
									}
939
									else {
940
										_extend(info, res);
941
										_next();
942
									}
943
								});
944
							}
945
							else {
946
								_next();
947
							}
948
						}
949
						else {
950
							fn(false, info);
951
						}
952
					})();
953
				}
954
				else {
955
					fn('not_support_info', info);
956
				}
957
			},
958
959
960
			/**
961
			 * Add information reader
962
			 *
963
			 * @param {RegExp} mime
964
			 * @param {Function} fn
965
			 */
966
			addInfoReader: function (mime, fn){
967
				fn.test = function (type){ return mime.test(type); };
968
				_infoReader.push(fn);
969
			},
970
971
972
			/**
973
			 * Filter of array
974
			 *
975
			 * @param	{Array}		input
976
			 * @param	{Function}	fn
977
			 * @return	{Array}
978
			 */
979
			filter: function (input, fn){
980
				var result = [], i = 0, n = input.length, val;
981
982
				for( ; i < n; i++ ){
983
					if( i in input ){
984
						val = input[i];
985
						if( fn.call(val, val, i, input) ){
986
							result.push(val);
987
						}
988
					}
989
				}
990
991
				return	result;
992
			},
993
994
995
			/**
996
			 * Filter files
997
			 *
998
			 * @param	{Array}		files
999
			 * @param	{Function}	eachFn
1000
			 * @param	{Function}	resultFn
1001
			 */
1002
			filterFiles: function (files, eachFn, resultFn){
1003
				if( files.length ){
1004
					// HTML5 or Flash
1005
					var queue = files.concat(), file, result = [], deleted = [];
1006
1007
					(function _next(){
1008
						if( queue.length ){
1009
							file = queue.shift();
1010
							api.getInfo(file, function (err, info){
1011
								(eachFn(file, err ? false : info) ? result : deleted).push(file);
1012
								_next();
1013
							});
1014
						}
1015
						else {
1016
							resultFn(result, deleted);
1017
						}
1018
					})();
1019
				}
1020
				else {
1021
					resultFn([], files);
1022
				}
1023
			},
1024
1025
1026
			upload: function (options){
1027
				options = _extend({
1028
					  jsonp: 'callback'
1029
					, prepare: api.F
1030
					, beforeupload: api.F
1031
					, upload: api.F
1032
					, fileupload: api.F
1033
					, fileprogress: api.F
1034
					, filecomplete: api.F
1035
					, progress: api.F
1036
					, complete: api.F
1037
					, pause: api.F
1038
					, imageOriginal: true
1039
					, chunkSize: api.chunkSize
1040
					, chunkUploadRetry: api.chunkUploadRetry
1041
					, uploadRetry: api.uploadRetry
1042
				}, options);
1043
1044
1045
				if( options.imageAutoOrientation && !options.imageTransform ){
1046
					options.imageTransform = { rotate: 'auto' };
1047
				}
1048
1049
1050
				var
1051
					  proxyXHR = new api.XHR(options)
1052
					, dataArray = this._getFilesDataArray(options.files)
1053
					, _this = this
1054
					, _total = 0
1055
					, _loaded = 0
1056
					, _nextFile
1057
					, _complete = false
1058
				;
1059
1060
1061
				// calc total size
1062
				_each(dataArray, function (data){
1063
					_total += data.size;
1064
				});
1065
1066
				// Array of files
1067
				proxyXHR.files = [];
1068
				_each(dataArray, function (data){
1069
					proxyXHR.files.push(data.file);
1070
				});
1071
1072
				// Set upload status props
1073
				proxyXHR.total	= _total;
1074
				proxyXHR.loaded	= 0;
1075
				proxyXHR.filesLeft = dataArray.length;
1076
1077
				// emit "beforeupload"  event
1078
				options.beforeupload(proxyXHR, options);
1079
1080
				// Upload by file
1081
				_nextFile = function (){
1082
					var
1083
						  data = dataArray.shift()
1084
						, _file = data && data.file
1085
						, _fileLoaded = false
1086
						, _fileOptions = _simpleClone(options)
1087
					;
1088
1089
					proxyXHR.filesLeft = dataArray.length;
1090
1091
					if( _file && _file.name === api.expando ){
1092
						_file = null;
1093
						api.log('[warn] FileAPI.upload() — called without files');
1094
					}
1095
1096
					if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
1097
						// Mark active job
1098
						_complete = false;
1099
1100
						// Set current upload file
1101
						proxyXHR.currentFile = _file;
1102
1103
						// Prepare file options
1104
						if (_file && options.prepare(_file, _fileOptions) === false) {
1105
							_nextFile.call(_this);
1106
							return;
1107
						}
1108
						_fileOptions.file = _file;
1109
1110
						_this._getFormData(_fileOptions, data, function (form){
1111
							if( !_loaded ){
1112
								// emit "upload" event
1113
								options.upload(proxyXHR, options);
1114
							}
1115
1116
							var xhr = new api.XHR(_extend({}, _fileOptions, {
1117
1118
								upload: _file ? function (){
1119
									// emit "fileupload" event
1120
									options.fileupload(_file, xhr, _fileOptions);
1121
								} : noop,
1122
1123
								progress: _file ? function (evt){
1124
									if( !_fileLoaded ){
1125
										// For ignore the double calls.
1126
										_fileLoaded = (evt.loaded === evt.total);
1127
1128
										// emit "fileprogress" event
1129
										options.fileprogress({
1130
											  type:   'progress'
1131
											, total:  data.total = evt.total
1132
											, loaded: data.loaded = evt.loaded
1133
										}, _file, xhr, _fileOptions);
1134
1135
										// emit "progress" event
1136
										options.progress({
1137
											  type:   'progress'
1138
											, total:  _total
1139
											, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
1140
										}, _file, xhr, _fileOptions);
1141
									}
1142
								} : noop,
1143
1144
								complete: function (err){
1145
									_each(_xhrPropsExport, function (name){
1146
										proxyXHR[name] = xhr[name];
1147
									});
1148
1149
									if( _file ){
1150
										data.total = (data.total || data.size);
1151
										data.loaded	= data.total;
1152
1153
										if( !err ) {
1154
											// emulate 100% "progress"
1155
											this.progress(data);
1156
1157
											// fixed throttle event
1158
											_fileLoaded = true;
1159
1160
											// bytes loaded
1161
											_loaded += data.size; // data.size != data.total, it's desirable fix this
1162
											proxyXHR.loaded = _loaded;
1163
										}
1164
1165
										// emit "filecomplete" event
1166
										options.filecomplete(err, xhr, _file, _fileOptions);
1167
									}
1168
1169
									// upload next file
1170
									setTimeout(function () {_nextFile.call(_this);}, 0);
1171
								}
1172
							})); // xhr
1173
1174
1175
							// ...
1176
							proxyXHR.abort = function (current){
1177
								if (!current) { dataArray.length = 0; }
1178
								this.current = current;
1179
								xhr.abort();
1180
							};
1181
1182
							// Start upload
1183
							xhr.send(form);
1184
						});
1185
					}
1186
					else {
1187
						var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
1188
						options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
1189
						// Mark done state
1190
						_complete = true;
1191
					}
1192
				};
1193
1194
1195
				// Next tick
1196
				setTimeout(_nextFile, 0);
1197
1198
1199
				// Append more files to the existing request
1200
				// first - add them to the queue head/tail
1201
				proxyXHR.append = function (files, first) {
1202
					files = api._getFilesDataArray([].concat(files));
1203
1204
					_each(files, function (data) {
1205
						_total += data.size;
1206
						proxyXHR.files.push(data.file);
1207
						if (first) {
1208
							dataArray.unshift(data);
1209
						} else {
1210
							dataArray.push(data);
1211
						}
1212
					});
1213
1214
					proxyXHR.statusText = "";
1215
1216
					if( _complete ){
1217
						_nextFile.call(_this);
1218
					}
1219
				};
1220
1221
1222
				// Removes file from queue by file reference and returns it
1223
				proxyXHR.remove = function (file) {
1224
				    var i = dataArray.length, _file;
1225
				    while( i-- ){
1226
						if( dataArray[i].file == file ){
1227
							_file = dataArray.splice(i, 1);
1228
							_total -= _file.size;
1229
						}
1230
					}
1231
					return	_file;
1232
				};
1233
1234
				return proxyXHR;
1235
			},
1236
1237
1238
			_getFilesDataArray: function (data){
1239
				var files = [], oFiles = {};
1240
1241
				if( isInputFile(data) ){
1242
					var tmp = api.getFiles(data);
1243
					oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
1244
				}
1245
				else if( _isArray(data) && isInputFile(data[0]) ){
1246
					_each(data, function (input){
1247
						oFiles[input.name || 'file'] = api.getFiles(input);
1248
					});
1249
				}
1250
				else {
1251
					oFiles = data;
1252
				}
1253
1254
				_each(oFiles, function add(file, name){
1255
					if( _isArray(file) ){
1256
						_each(file, function (file){
1257
							add(file, name);
1258
						});
1259
					}
1260
					else if( file && (file.name || file.image) ){
1261
						files.push({
1262
							  name: name
1263
							, file: file
1264
							, size: file.size
1265
							, total: file.size
1266
							, loaded: 0
1267
						});
1268
					}
1269
				});
1270
1271
				if( !files.length ){
1272
					// Create fake `file` object
1273
					files.push({ file: { name: api.expando } });
1274
				}
1275
1276
				return	files;
1277
			},
1278
1279
1280
			_getFormData: function (options, data, fn){
1281
				var
1282
					  file = data.file
1283
					, name = data.name
1284
					, filename = file.name
1285
					, filetype = file.type
1286
					, trans = api.support.transform && options.imageTransform
1287
					, Form = new api.Form
1288
					, queue = api.queue(function (){ fn(Form); })
1289
					, isOrignTrans = trans && _isOriginTransform(trans)
1290
					, postNameConcat = api.postNameConcat
1291
				;
1292
1293
				// Append data
1294
				_each(options.data, function add(val, name){
1295
					if( typeof val == 'object' ){
1296
						_each(val, function (v, i){
1297
							add(v, postNameConcat(name, i));
1298
						});
1299
					}
1300
					else {
1301
						Form.append(name, val);
1302
					}
1303
				});
1304
1305
				(function _addFile(file/**Object*/){
1306
					if( file.image ){ // This is a FileAPI.Image
1307
						queue.inc();
1308
1309
						file.toData(function (err, image){
1310
							// @todo: error
1311
							filename = filename || (new Date).getTime()+'.png';
1312
1313
							_addFile(image);
1314
							queue.next();
1315
						});
1316
					}
1317
					else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
1318
						queue.inc();
1319
1320
						if( isOrignTrans ){
1321
							// Convert to array for transform function
1322
							trans = [trans];
1323
						}
1324
1325
						api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
1326
							if( isOrignTrans && !err ){
1327
								if( !dataURLtoBlob && !api.flashEngine ){
1328
									// Canvas.toBlob or Flash not supported, use multipart
1329
									Form.multipart = true;
1330
								}
1331
1332
								Form.append(name, images[0], filename,  trans[0].type || filetype);
1333
							}
1334
							else {
1335
								var addOrigin = 0;
1336
1337
								if( !err ){
1338
									_each(images, function (image, idx){
1339
										if( !dataURLtoBlob && !api.flashEngine ){
1340
											Form.multipart = true;
1341
										}
1342
1343
										if( !trans[idx].postName ){
1344
											addOrigin = 1;
1345
										}
1346
1347
										Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
1348
									});
1349
								}
1350
1351
								if( err || options.imageOriginal ){
1352
									Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
1353
								}
1354
							}
1355
1356
							queue.next();
1357
						});
1358
					}
1359
					else if( filename !== api.expando ){
1360
						Form.append(name, file, filename);
1361
					}
1362
				})(file);
1363
1364
				queue.check();
1365
			},
1366
1367
1368
			reset: function (inp, notRemove){
1369
				var parent, clone;
1370
1371
				if( jQuery ){
1372
					clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
1373
					if( !notRemove ){
1374
						jQuery(inp).remove();
1375
					}
1376
				} else {
1377
					parent  = inp.parentNode;
1378
					clone   = parent.insertBefore(inp.cloneNode(true), inp);
1379
					clone.value = '';
1380
1381
					if( !notRemove ){
1382
						parent.removeChild(inp);
1383
					}
1384
1385
					_each(_elEvents[api.uid(inp)], function (fns, type){
1386
						_each(fns, function (fn){
1387
							_off(inp, type, fn);
1388
							_on(clone, type, fn);
1389
						});
1390
					});
1391
				}
1392
1393
				return  clone;
1394
			},
1395
1396
1397
			/**
1398
			 * Load remote file
1399
			 *
1400
			 * @param   {String}    url
1401
			 * @param   {Function}  fn
1402
			 * @return  {XMLHttpRequest}
1403
			 */
1404
			load: function (url, fn){
1405
				var xhr = api.getXHR();
1406
				if( xhr ){
1407
					xhr.open('GET', url, true);
1408
1409
					if( xhr.overrideMimeType ){
1410
				        xhr.overrideMimeType('text/plain; charset=x-user-defined');
1411
					}
1412
1413
					_on(xhr, 'progress', function (/**Event*/evt){
1414
						/** @namespace evt.lengthComputable */
1415
						if( evt.lengthComputable ){
1416
							fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
1417
						}
1418
					});
1419
1420
					xhr.onreadystatechange = function(){
1421
						if( xhr.readyState == 4 ){
1422
							xhr.onreadystatechange = null;
1423
							if( xhr.status == 200 ){
1424
								url = url.split('/');
1425
								/** @namespace xhr.responseBody */
1426
								var file = {
1427
								      name: url[url.length-1]
1428
									, size: xhr.getResponseHeader('Content-Length')
1429
									, type: xhr.getResponseHeader('Content-Type')
1430
								};
1431
								file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
1432
								fn({ type: 'load', result: file }, xhr);
1433
							}
1434
							else {
1435
								fn({ type: 'error' }, xhr);
1436
							}
1437
					    }
1438
					};
1439
				    xhr.send(null);
1440
				} else {
1441
					fn({ type: 'error' });
1442
				}
1443
1444
				return  xhr;
1445
			},
1446
1447
			encode64: function (str){
1448
				var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
1449
1450
				if( typeof str !== 'string' ){
1451
					str	= String(str);
1452
				}
1453
1454
				while( i < str.length ){
1455
					//all three "& 0xff" added below are there to fix a known bug
1456
					//with bytes returned by xhr.responseText
1457
					var
1458
						  byte1 = str.charCodeAt(i++) & 0xff
1459
						, byte2 = str.charCodeAt(i++) & 0xff
1460
						, byte3 = str.charCodeAt(i++) & 0xff
1461
						, enc1 = byte1 >> 2
1462
						, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
1463
						, enc3, enc4
1464
					;
1465
1466
					if( isNaN(byte2) ){
1467
						enc3 = enc4 = 64;
1468
					} else {
1469
						enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1470
						enc4 = isNaN(byte3) ? 64 : byte3 & 63;
1471
					}
1472
1473
					outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
1474
				}
1475
1476
				return  outStr;
1477
			}
1478
1479
		} // api
1480
	;
1481
1482
1483
	function _emit(target, fn, name, res, ext){
1484
		var evt = {
1485
			  type:		name.type || name
1486
			, target:	target
1487
			, result:	res
1488
		};
1489
		_extend(evt, ext);
1490
		fn(evt);
1491
	}
1492
1493
1494
	function _hasSupportReadAs(as){
1495
		return	FileReader && !!FileReader.prototype['readAs'+as];
1496
	}
1497
1498
1499
	function _readAs(file, fn, as, encoding){
1500
		if( api.isBlob(file) && _hasSupportReadAs(as) ){
1501
			var Reader = new FileReader;
1502
1503
			// Add event listener
1504
			_on(Reader, _readerEvents, function _fn(evt){
1505
				var type = evt.type;
1506
				if( type == 'progress' ){
1507
					_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
1508
				}
1509
				else if( type == 'loadend' ){
1510
					_off(Reader, _readerEvents, _fn);
1511
					Reader = null;
1512
				}
1513
				else {
1514
					_emit(file, fn, evt, evt.target.result);
1515
				}
1516
			});
1517
1518
1519
			try {
1520
				// ReadAs ...
1521
				if( encoding ){
1522
					Reader['readAs'+as](file, encoding);
1523
				}
1524
				else {
1525
					Reader['readAs'+as](file);
1526
				}
1527
			}
1528
			catch (err){
1529
				_emit(file, fn, 'error', undef, { error: err.toString() });
1530
			}
1531
		}
1532
		else {
1533
			_emit(file, fn, 'error', undef, { error: 'filreader_not_support_'+as });
1534
		}
1535
	}
1536
1537
1538
	function _isRegularFile(file, callback){
1539
		// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
1540
		if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
1541
			if( FileReader ){
1542
				try {
1543
					var Reader = new FileReader();
1544
1545
					_one(Reader, _readerEvents, function (evt){
1546
						var isFile = evt.type != 'error';
1547
						callback(isFile);
1548
						if( isFile ){
1549
							Reader.abort();
1550
						}
1551
					});
1552
1553
					Reader.readAsDataURL(file);
1554
				} catch( err ){
1555
					callback(false);
1556
				}
1557
			}
1558
			else {
1559
				callback(null);
1560
			}
1561
		}
1562
		else {
1563
			callback(true);
1564
		}
1565
	}
1566
1567
1568
	function _getAsEntry(item){
1569
		var entry;
1570
		if( item.getAsEntry ){ entry = item.getAsEntry(); }
1571
		else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
1572
		return	entry;
1573
	}
1574
1575
1576
	function _readEntryAsFiles(entry, callback){
1577
		if( !entry ){
1578
			// error
1579
			callback('invalid entry');
1580
		}
1581
		else if( entry.isFile ){
1582
			// Read as file
1583
			entry.file(function(file){
1584
				// success
1585
				file.fullPath = entry.fullPath;
1586
				callback(false, [file]);
1587
			}, function (err){
1588
				// error
1589
				callback('FileError.code: '+err.code);
1590
			});
1591
		}
1592
		else if( entry.isDirectory ){
1593
			var reader = entry.createReader(), result = [];
1594
1595
			reader.readEntries(function(entries){
1596
				// success
1597
				api.afor(entries, function (next, entry){
1598
					_readEntryAsFiles(entry, function (err, files){
1599
						if( err ){
1600
							api.log(err);
1601
						}
1602
						else {
1603
							result = result.concat(files);
1604
						}
1605
1606
						if( next ){
1607
							next();
1608
						}
1609
						else {
1610
							callback(false, result);
1611
						}
1612
					});
1613
				});
1614
			}, function (err){
1615
				// error
1616
				callback('directory_reader: ' + err);
1617
			});
1618
		}
1619
		else {
1620
			_readEntryAsFiles(_getAsEntry(entry), callback);
1621
		}
1622
	}
1623
1624
1625
	function _simpleClone(obj){
1626
		var copy = {};
1627
		_each(obj, function (val, key){
1628
			if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
1629
				val = _extend({}, val);
1630
			}
1631
			copy[key] = val;
1632
		});
1633
		return	copy;
1634
	}
1635
1636
1637
	function isInputFile(el){
1638
		return	_rinput.test(el && el.tagName);
1639
	}
1640
1641
1642
	function _getDataTransfer(evt){
1643
		return	(evt.originalEvent || evt || '').dataTransfer || {};
1644
	}
1645
1646
1647
	function _isOriginTransform(trans){
1648
		var key;
1649
		for( key in trans ){
1650
			if( trans.hasOwnProperty(key) ){
1651
				if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
1652
					return	true;
1653
				}
1654
			}
1655
		}
1656
		return	false;
1657
	}
1658
1659
1660
	// Add default image info reader
1661
	api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
1662
		if( !file.__dimensions ){
1663
			var defer = file.__dimensions = api.defer();
1664
1665
			api.readAsImage(file, function (evt){
1666
				var img = evt.target;
1667
				defer.resolve(evt.type == 'load' ? false : 'error', {
1668
					  width:  img.width
1669
					, height: img.height
1670
				});
1671
                img.src = api.EMPTY_PNG;
1672
				img = null;
1673
			});
1674
		}
1675
1676
		file.__dimensions.then(callback);
1677
	});
1678
1679
1680
	/**
1681
	 * Drag'n'Drop special event
1682
	 *
1683
	 * @param	{HTMLElement}	el
1684
	 * @param	{Function}		onHover
1685
	 * @param	{Function}		onDrop
1686
	 */
1687
	api.event.dnd = function (el, onHover, onDrop){
1688
		var _id, _type;
1689
1690
		if( !onDrop ){
1691
			onDrop = onHover;
1692
			onHover = api.F;
1693
		}
1694
1695
		if( FileReader ){
1696
			// Hover
1697
			_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
1698
				var
1699
					  types = _getDataTransfer(evt).types
1700
					, i = types && types.length
1701
					, debounceTrigger = false
1702
				;
1703
1704
				while( i-- ){
1705
					if( ~types[i].indexOf('File') ){
1706
						evt[preventDefault]();
1707
1708
						if( _type !== evt.type ){
1709
							_type = evt.type; // Store current type of event
1710
1711
							if( _type != 'dragleave' ){
1712
								onHover.call(evt[currentTarget], true, evt);
1713
							}
1714
1715
							debounceTrigger = true;
1716
						}
1717
1718
						break; // exit from "while"
1719
					}
1720
				}
1721
1722
				if( debounceTrigger ){
1723
					clearTimeout(_id);
1724
					_id = setTimeout(function (){
1725
						onHover.call(evt[currentTarget], _type != 'dragleave', evt);
1726
					}, 50);
1727
				}
1728
			});
1729
1730
1731
			// Drop
1732
			_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
1733
				evt[preventDefault]();
1734
1735
				_type = 0;
1736
				onHover.call(evt[currentTarget], false, evt);
1737
1738
				api.getDropFiles(evt, function (files){
1739
					onDrop.call(evt[currentTarget], files, evt);
1740
				});
1741
			});
1742
		}
1743
		else {
1744
			api.log("Drag'n'Drop -- not supported");
1745
		}
1746
	};
1747
1748
1749
	/**
1750
	 * Remove drag'n'drop
1751
	 * @param	{HTMLElement}	el
1752
	 * @param	{Function}		onHover
1753
	 * @param	{Function}		onDrop
1754
	 */
1755
	api.event.dnd.off = function (el, onHover, onDrop){
1756
		_off(el, 'dragenter dragleave dragover', onHover.ff);
1757
		_off(el, 'drop', onDrop.ff);
1758
	};
1759
1760
1761
	// Support jQuery
1762
	if( jQuery && !jQuery.fn.dnd ){
1763
		jQuery.fn.dnd = function (onHover, onDrop){
1764
			return this.each(function (){
1765
				api.event.dnd(this, onHover, onDrop);
1766
			});
1767
		};
1768
1769
		jQuery.fn.offdnd = function (onHover, onDrop){
1770
			return this.each(function (){
1771
				api.event.dnd.off(this, onHover, onDrop);
1772
			});
1773
		};
1774
	}
1775
1776
	// @export
1777
	window.FileAPI  = _extend(api, window.FileAPI);
1778
1779
1780
	// Debug info
1781
	api.log('FileAPI: ' + api.version);
1782
	api.log('protocol: ' + window.location.protocol);
1783
	api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
1784
1785
1786
	// @detect 'x-ua-compatible'
1787
	_each(document.getElementsByTagName('meta'), function (meta){
1788
		if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
1789
			api.log('meta.http-equiv: ' + meta.getAttribute('content'));
1790
		}
1791
	});
1792
1793
1794
	// @configuration
1795
	if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
1796
	if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
1797
	if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
1798
})(window, void 0);
1799
1800
/*global window, FileAPI, document */
1801
1802
(function (api, document, undef) {
1803
	'use strict';
1804
1805
	var
1806
		min = Math.min,
1807
		round = Math.round,
1808
		getCanvas = function () { return document.createElement('canvas'); },
1809
		support = false,
1810
		exifOrientation = {
1811
			  8:	270
1812
			, 3:	180
1813
			, 6:	90
1814
			, 7:	270
1815
			, 4:	180
1816
			, 5:	90
1817
		}
1818
	;
1819
1820
	try {
1821
		support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
1822
	}
1823
	catch (e){}
1824
1825
1826
	function Image(file){
1827
		if( file instanceof Image ){
1828
			var img = new Image(file.file);
1829
			api.extend(img.matrix, file.matrix);
1830
			return	img;
1831
		}
1832
		else if( !(this instanceof Image) ){
1833
			return	new Image(file);
1834
		}
1835
1836
		this.file   = file;
1837
		this.size   = file.size || 100;
1838
1839
		this.matrix	= {
1840
			sx: 0,
1841
			sy: 0,
1842
			sw: 0,
1843
			sh: 0,
1844
			dx: 0,
1845
			dy: 0,
1846
			dw: 0,
1847
			dh: 0,
1848
			resize: 0, // min, max OR preview
1849
			deg: 0,
1850
			quality: 1, // jpeg quality
1851
			filter: 0
1852
		};
1853
	}
1854
1855
1856
	Image.prototype = {
1857
		image: true,
1858
		constructor: Image,
1859
1860
		set: function (attrs){
1861
			api.extend(this.matrix, attrs);
1862
			return	this;
1863
		},
1864
1865
		crop: function (x, y, w, h){
1866
			if( w === undef ){
1867
				w	= x;
1868
				h	= y;
1869
				x = y = 0;
1870
			}
1871
			return	this.set({ sx: x, sy: y, sw: w, sh: h || w });
1872
		},
1873
1874
		resize: function (w, h, strategy){
1875
			if( /min|max/.test(h) ){
1876
				strategy = h;
1877
				h = w;
1878
			}
1879
1880
			return	this.set({ dw: w, dh: h || w, resize: strategy });
1881
		},
1882
1883
		preview: function (w, h){
1884
			return	this.resize(w, h || w, 'preview');
1885
		},
1886
1887
		rotate: function (deg){
1888
			return	this.set({ deg: deg });
1889
		},
1890
1891
		filter: function (filter){
1892
			return	this.set({ filter: filter });
1893
		},
1894
1895
		overlay: function (images){
1896
			return	this.set({ overlay: images });
1897
		},
1898
1899
		clone: function (){
1900
			return	new Image(this);
1901
		},
1902
1903
		_load: function (image, fn){
1904
			var self = this;
1905
1906
			if( /img|video/i.test(image.nodeName) ){
1907
				fn.call(self, null, image);
1908
			}
1909
			else {
1910
				api.readAsImage(image, function (evt){
1911
					fn.call(self, evt.type != 'load', evt.result);
1912
				});
1913
			}
1914
		},
1915
1916
		_apply: function (image, fn){
1917
			var
1918
				  canvas = getCanvas()
1919
				, m = this.getMatrix(image)
1920
				, ctx = canvas.getContext('2d')
1921
				, width = image.videoWidth || image.width
1922
				, height = image.videoHeight || image.height
1923
				, deg = m.deg
1924
				, dw = m.dw
1925
				, dh = m.dh
1926
				, w = width
1927
				, h = height
1928
				, filter = m.filter
1929
				, copy // canvas copy
1930
				, buffer = image
1931
				, overlay = m.overlay
1932
				, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
1933
				, renderImageToCanvas = api.renderImageToCanvas
1934
			;
1935
1936
			// Normalize angle
1937
			deg = deg - Math.floor(deg/360)*360;
1938
1939
			// For `renderImageToCanvas`
1940
			image._type = this.file.type;
1941
1942
			while(m.multipass && min(w/dw, h/dh) > 2 ){
1943
				w = (w/2 + 0.5)|0;
1944
				h = (h/2 + 0.5)|0;
1945
1946
				copy = getCanvas();
1947
				copy.width  = w;
1948
				copy.height = h;
1949
1950
				if( buffer !== image ){
1951
					renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
1952
					buffer = copy;
1953
				}
1954
				else {
1955
					buffer = copy;
1956
					renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
1957
					m.sx = m.sy = m.sw = m.sh = 0;
1958
				}
1959
			}
1960
1961
1962
			canvas.width  = (deg % 180) ? dh : dw;
1963
			canvas.height = (deg % 180) ? dw : dh;
1964
1965
			canvas.type = m.type;
1966
			canvas.quality = m.quality;
1967
1968
			ctx.rotate(deg * Math.PI / 180);
1969
			renderImageToCanvas(ctx.canvas, buffer
1970
				, m.sx, m.sy
1971
				, m.sw || buffer.width
1972
				, m.sh || buffer.height
1973
				, (deg == 180 || deg == 270 ? -dw : 0)
1974
				, (deg == 90 || deg == 180 ? -dh : 0)
1975
				, dw, dh
1976
			);
1977
			dw = canvas.width;
1978
			dh = canvas.height;
1979
1980
			// Apply overlay
1981
			overlay && api.each([].concat(overlay), function (over){
1982
				queue.inc();
1983
				// preload
1984
				var img = new window.Image, fn = function (){
1985
					var
1986
						  x = over.x|0
1987
						, y = over.y|0
1988
						, w = over.w || img.width
1989
						, h = over.h || img.height
1990
						, rel = over.rel
1991
					;
1992
1993
					// center  |  right  |  left
1994
					x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
1995
1996
					// center  |  bottom  |  top
1997
					y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
1998
1999
					api.event.off(img, 'error load abort', fn);
2000
2001
					try {
2002
						ctx.globalAlpha = over.opacity || 1;
2003
						ctx.drawImage(img, x, y, w, h);
2004
					}
2005
					catch (er){}
2006
2007
					queue.next();
2008
				};
2009
2010
				api.event.on(img, 'error load abort', fn);
2011
				img.src = over.src;
2012
2013
				if( img.complete ){
2014
					fn();
2015
				}
2016
			});
2017
2018
			if( filter ){
2019
				queue.inc();
2020
				Image.applyFilter(canvas, filter, queue.next);
2021
			}
2022
2023
			queue.check();
2024
		},
2025
2026
		getMatrix: function (image){
2027
			var
2028
				  m  = api.extend({}, this.matrix)
2029
				, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width
2030
				, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
2031
				, dw = m.dw = m.dw || sw
2032
				, dh = m.dh = m.dh || sh
2033
				, sf = sw/sh, df = dw/dh
2034
				, strategy = m.resize
2035
			;
2036
2037
			if( strategy == 'preview' ){
2038
				if( dw != sw || dh != sh ){
2039
					// Make preview
2040
					var w, h;
2041
2042
					if( df >= sf ){
2043
						w	= sw;
2044
						h	= w / df;
2045
					} else {
2046
						h	= sh;
2047
						w	= h * df;
2048
					}
2049
2050
					if( w != sw || h != sh ){
2051
						m.sx	= ~~((sw - w)/2);
2052
						m.sy	= ~~((sh - h)/2);
2053
						sw		= w;
2054
						sh		= h;
2055
					}
2056
				}
2057
			}
2058
			else if( strategy ){
2059
				if( !(sw > dw || sh > dh) ){
2060
					dw = sw;
2061
					dh = sh;
2062
				}
2063
				else if( strategy == 'min' ){
2064
					dw = round(sf < df ? min(sw, dw) : dh*sf);
2065
					dh = round(sf < df ? dw/sf : min(sh, dh));
2066
				}
2067
				else {
2068
					dw = round(sf >= df ? min(sw, dw) : dh*sf);
2069
					dh = round(sf >= df ? dw/sf : min(sh, dh));
2070
				}
2071
			}
2072
2073
			m.sw = sw;
2074
			m.sh = sh;
2075
			m.dw = dw;
2076
			m.dh = dh;
2077
			m.multipass = api.multiPassResize;
2078
			return	m;
2079
		},
2080
2081
		_trans: function (fn){
2082
			this._load(this.file, function (err, image){
2083
				if( err ){
2084
					fn(err);
2085
				}
2086
				else {
2087
					try {
2088
						this._apply(image, fn);
2089
					} catch (err){
2090
						api.log('[err] FileAPI.Image.fn._apply:', err);
2091
						fn(err);
2092
					}
2093
				}
2094
			});
2095
		},
2096
2097
2098
		get: function (fn){
2099
			if( api.support.transform ){
2100
				var _this = this, matrix = _this.matrix;
2101
2102
				if( matrix.deg == 'auto' ){
2103
					api.getInfo(_this.file, function (err, info){
2104
						// rotate by exif orientation
2105
						matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2106
						_this._trans(fn);
2107
					});
2108
				}
2109
				else {
2110
					_this._trans(fn);
2111
				}
2112
			}
2113
			else {
2114
				fn('not_support_transform');
2115
			}
2116
2117
			return this;
2118
		},
2119
2120
2121
		toData: function (fn){
2122
			return this.get(fn);
2123
		}
2124
2125
	};
2126
2127
2128
	Image.exifOrientation = exifOrientation;
2129
2130
2131
	Image.transform = function (file, transform, autoOrientation, fn){
2132
		function _transform(err, img){
2133
			// img -- info object
2134
			var
2135
				  images = {}
2136
				, queue = api.queue(function (err){
2137
					fn(err, images);
2138
				})
2139
			;
2140
2141
			if( !err ){
2142
				api.each(transform, function (params, name){
2143
					if( !queue.isFail() ){
2144
						var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2145
2146
						if( isFn ){
2147
							params(img, ImgTrans);
2148
						}
2149
						else if( params.width ){
2150
							ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2151
						}
2152
						else {
2153
							if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2154
								ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2155
							}
2156
						}
2157
2158
						if( params.crop ){
2159
							var crop = params.crop;
2160
							ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2161
						}
2162
2163
						if( params.rotate === undef && autoOrientation ){
2164
							params.rotate = 'auto';
2165
						}
2166
2167
						ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2168
2169
						if( !isFn ){
2170
							ImgTrans.set({
2171
								  deg: params.rotate
2172
								, overlay: params.overlay
2173
								, filter: params.filter
2174
								, quality: params.quality || 1
2175
							});
2176
						}
2177
2178
						queue.inc();
2179
						ImgTrans.toData(function (err, image){
2180
							if( err ){
2181
								queue.fail();
2182
							}
2183
							else {
2184
								images[name] = image;
2185
								queue.next();
2186
							}
2187
						});
2188
					}
2189
				});
2190
			}
2191
			else {
2192
				queue.fail();
2193
			}
2194
		}
2195
2196
2197
		// @todo: Оло-ло, нужно рефакторить это место
2198
		if( file.width ){
2199
			_transform(false, file);
2200
		} else {
2201
			api.getInfo(file, _transform);
2202
		}
2203
	};
2204
2205
2206
	// @const
2207
	api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2208
		api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2209
			Image[x+'_'+y] = i*3 + j;
2210
			Image[y+'_'+x] = i*3 + j;
2211
		});
2212
	});
2213
2214
2215
	/**
2216
	 * Trabsform element to canvas
2217
	 *
2218
	 * @param    {Image|HTMLVideoElement}   el
2219
	 * @returns  {Canvas}
2220
	 */
2221
	Image.toCanvas = function(el){
2222
		var canvas		= document.createElement('canvas');
2223
		canvas.width	= el.videoWidth || el.width;
2224
		canvas.height	= el.videoHeight || el.height;
2225
		canvas.getContext('2d').drawImage(el, 0, 0);
2226
		return	canvas;
2227
	};
2228
2229
2230
	/**
2231
	 * Create image from DataURL
2232
	 * @param  {String}  dataURL
2233
	 * @param  {Object}  size
2234
	 * @param  {Function}  callback
2235
	 */
2236
	Image.fromDataURL = function (dataURL, size, callback){
2237
		var img = api.newImage(dataURL);
2238
		api.extend(img, size);
2239
		callback(img);
2240
	};
2241
2242
2243
	/**
2244
	 * Apply filter (caman.js)
2245
	 *
2246
	 * @param  {Canvas|Image}   canvas
2247
	 * @param  {String|Function}  filter
2248
	 * @param  {Function}  doneFn
2249
	 */
2250
	Image.applyFilter = function (canvas, filter, doneFn){
2251
		if( typeof filter == 'function' ){
2252
			filter(canvas, doneFn);
2253
		}
2254
		else if( window.Caman ){
2255
			// http://camanjs.com/guides/
2256
			window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2257
				if( typeof filter == 'string' ){
2258
					this[filter]();
2259
				}
2260
				else {
2261
					api.each(filter, function (val, method){
2262
						this[method](val);
2263
					}, this);
2264
				}
2265
				this.render(doneFn);
2266
			});
2267
		}
2268
	};
2269
2270
2271
	/**
2272
	 * For load-image-ios.js
2273
	 */
2274
	api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2275
		try {
2276
			return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2277
		} catch (ex) {
2278
			api.log('renderImageToCanvas failed');
2279
			throw ex;
2280
		}
2281
	};
2282
2283
2284
	// @export
2285
	api.support.canvas = api.support.transform = support;
2286
	api.Image = Image;
2287
})(FileAPI, document);
2288
2289
/*
2290
 * JavaScript Load Image iOS scaling fixes 1.0.3
2291
 * https://github.com/blueimp/JavaScript-Load-Image
2292
 *
2293
 * Copyright 2013, Sebastian Tschan
2294
 * https://blueimp.net
2295
 *
2296
 * iOS image scaling fixes based on
2297
 * https://github.com/stomita/ios-imagefile-megapixel
2298
 *
2299
 * Licensed under the MIT license:
2300
 * http://www.opensource.org/licenses/MIT
2301
 */
2302
2303
/*jslint nomen: true, bitwise: true */
2304
/*global FileAPI, window, document */
2305
2306
(function (factory) {
2307
	'use strict';
2308
	factory(FileAPI);
2309
}(function (loadImage) {
2310
    'use strict';
2311
2312
    // Only apply fixes on the iOS platform:
2313
    if (!window.navigator || !window.navigator.platform ||
2314
             !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2315
        return;
2316
    }
2317
2318
    var originalRenderMethod = loadImage.renderImageToCanvas;
2319
2320
    // Detects subsampling in JPEG images:
2321
    loadImage.detectSubsampling = function (img) {
2322
        var canvas,
2323
            context;
2324
        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2325
            canvas = document.createElement('canvas');
2326
            canvas.width = canvas.height = 1;
2327
            context = canvas.getContext('2d');
2328
            context.drawImage(img, -img.width + 1, 0);
2329
            // subsampled image becomes half smaller in rendering size.
2330
            // check alpha channel value to confirm image is covering edge pixel or not.
2331
            // if alpha value is 0 image is not covering, hence subsampled.
2332
            return context.getImageData(0, 0, 1, 1).data[3] === 0;
2333
        }
2334
        return false;
2335
    };
2336
2337
    // Detects vertical squash in JPEG images:
2338
    loadImage.detectVerticalSquash = function (img, subsampled) {
2339
        var naturalHeight = img.naturalHeight || img.height,
2340
            canvas = document.createElement('canvas'),
2341
            context = canvas.getContext('2d'),
2342
            data,
2343
            sy,
2344
            ey,
2345
            py,
2346
            alpha;
2347
        if (subsampled) {
2348
            naturalHeight /= 2;
2349
        }
2350
        canvas.width = 1;
2351
        canvas.height = naturalHeight;
2352
        context.drawImage(img, 0, 0);
2353
        data = context.getImageData(0, 0, 1, naturalHeight).data;
2354
        // search image edge pixel position in case it is squashed vertically:
2355
        sy = 0;
2356
        ey = naturalHeight;
2357
        py = naturalHeight;
2358
        while (py > sy) {
2359
            alpha = data[(py - 1) * 4 + 3];
2360
            if (alpha === 0) {
2361
                ey = py;
2362
            } else {
2363
                sy = py;
2364
            }
2365
            py = (ey + sy) >> 1;
2366
        }
2367
        return (py / naturalHeight) || 1;
2368
    };
2369
2370
    // Renders image to canvas while working around iOS image scaling bugs:
2371
    // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2372
    loadImage.renderImageToCanvas = function (
2373
        canvas,
2374
        img,
2375
        sourceX,
2376
        sourceY,
2377
        sourceWidth,
2378
        sourceHeight,
2379
        destX,
2380
        destY,
2381
        destWidth,
2382
        destHeight
2383
    ) {
2384
        if (img._type === 'image/jpeg') {
2385
            var context = canvas.getContext('2d'),
2386
                tmpCanvas = document.createElement('canvas'),
2387
                tileSize = 1024,
2388
                tmpContext = tmpCanvas.getContext('2d'),
2389
                subsampled,
2390
                vertSquashRatio,
2391
                tileX,
2392
                tileY;
2393
            tmpCanvas.width = tileSize;
2394
            tmpCanvas.height = tileSize;
2395
            context.save();
2396
            subsampled = loadImage.detectSubsampling(img);
2397
            if (subsampled) {
2398
                sourceX /= 2;
2399
                sourceY /= 2;
2400
                sourceWidth /= 2;
2401
                sourceHeight /= 2;
2402
            }
2403
            vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2404
            if (subsampled || vertSquashRatio !== 1) {
2405
                sourceY *= vertSquashRatio;
2406
                destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2407
                destHeight = Math.ceil(
2408
                    tileSize * destHeight / sourceHeight / vertSquashRatio
2409
                );
2410
                destY = 0;
2411
                tileY = 0;
2412
                while (tileY < sourceHeight) {
2413
                    destX = 0;
2414
                    tileX = 0;
2415
                    while (tileX < sourceWidth) {
2416
                        tmpContext.clearRect(0, 0, tileSize, tileSize);
2417
                        tmpContext.drawImage(
2418
                            img,
2419
                            sourceX,
2420
                            sourceY,
2421
                            sourceWidth,
2422
                            sourceHeight,
2423
                            -tileX,
2424
                            -tileY,
2425
                            sourceWidth,
2426
                            sourceHeight
2427
                        );
2428
                        context.drawImage(
2429
                            tmpCanvas,
2430
                            0,
2431
                            0,
2432
                            tileSize,
2433
                            tileSize,
2434
                            destX,
2435
                            destY,
2436
                            destWidth,
2437
                            destHeight
2438
                        );
2439
                        tileX += tileSize;
2440
                        destX += destWidth;
2441
                    }
2442
                    tileY += tileSize;
2443
                    destY += destHeight;
2444
                }
2445
                context.restore();
2446
                return canvas;
2447
            }
2448
        }
2449
        return originalRenderMethod(
2450
            canvas,
2451
            img,
2452
            sourceX,
2453
            sourceY,
2454
            sourceWidth,
2455
            sourceHeight,
2456
            destX,
2457
            destY,
2458
            destWidth,
2459
            destHeight
2460
        );
2461
    };
2462
2463
}));
2464
2465
/*global window, FileAPI */
2466
2467
(function (api, window){
2468
	"use strict";
2469
2470
	var
2471
		  document = window.document
2472
		, FormData = window.FormData
2473
		, Form = function (){ this.items = []; }
2474
		, encodeURIComponent = window.encodeURIComponent
2475
	;
2476
2477
2478
	Form.prototype = {
2479
2480
		append: function (name, blob, file, type){
2481
			this.items.push({
2482
				  name: name
2483
				, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2484
				, file: blob && (file || blob.name)
2485
				, type:	blob && (type || blob.type)
2486
			});
2487
		},
2488
2489
		each: function (fn){
2490
			var i = 0, n = this.items.length;
2491
			for( ; i < n; i++ ){
2492
				fn.call(this, this.items[i]);
2493
			}
2494
		},
2495
2496
		toData: function (fn, options){
2497
		    // allow chunked transfer if we have only one file to send
2498
		    // flag is used below and in XHR._send
2499
		    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2500
2501
			if( !api.support.html5 ){
2502
				api.log('FileAPI.Form.toHtmlData');
2503
				this.toHtmlData(fn);
2504
			}
2505
			else if( !api.formData || this.multipart || !FormData ){
2506
				api.log('FileAPI.Form.toMultipartData');
2507
				this.toMultipartData(fn);
2508
			}
2509
			else if( options._chunked ){
2510
				api.log('FileAPI.Form.toPlainData');
2511
				this.toPlainData(fn);
2512
			}
2513
			else {
2514
				api.log('FileAPI.Form.toFormData');
2515
				this.toFormData(fn);
2516
			}
2517
		},
2518
2519
		_to: function (data, complete, next, arg){
2520
			var queue = api.queue(function (){
2521
				complete(data);
2522
			});
2523
2524
			this.each(function (file){
2525
				next(file, data, queue, arg);
2526
			});
2527
2528
			queue.check();
2529
		},
2530
2531
2532
		toHtmlData: function (fn){
2533
			this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2534
				var blob = file.blob, hidden;
2535
2536
				if( file.file ){
2537
					api.reset(blob, true);
2538
					// set new name
2539
					blob.name = file.name;
2540
					blob.disabled = false;
2541
					data.appendChild(blob);
2542
				}
2543
				else {
2544
					hidden = document.createElement('input');
2545
					hidden.name  = file.name;
2546
					hidden.type  = 'hidden';
2547
					hidden.value = blob;
2548
					data.appendChild(hidden);
2549
				}
2550
			});
2551
		},
2552
2553
		toPlainData: function (fn){
2554
			this._to({}, fn, function (file, data, queue){
2555
				if( file.file ){
2556
					data.type = file.file;
2557
				}
2558
2559
				if( file.blob.toBlob ){
2560
				    // canvas
2561
					queue.inc();
2562
					_convertFile(file, function (file, blob){
2563
						data.name = file.name;
2564
						data.file = blob;
2565
						data.size = blob.length;
2566
						data.type = file.type;
2567
						queue.next();
2568
					});
2569
				}
2570
				else if( file.file ){
2571
				    // file
2572
					data.name = file.blob.name;
2573
					data.file = file.blob;
2574
					data.size = file.blob.size;
2575
					data.type = file.type;
2576
				}
2577
				else {
2578
				    // additional data
2579
				    if( !data.params ){
2580
				        data.params = [];
2581
				    }
2582
				    data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2583
				}
2584
2585
				data.start = -1;
2586
				data.end = data.file && data.file.FileAPIReadPosition || -1;
2587
				data.retry = 0;
2588
			});
2589
		},
2590
2591
		toFormData: function (fn){
2592
			this._to(new FormData, fn, function (file, data, queue){
2593
				if( file.blob && file.blob.toBlob ){
2594
					queue.inc();
2595
					_convertFile(file, function (file, blob){
2596
						data.append(file.name, blob, file.file);
2597
						queue.next();
2598
					});
2599
				}
2600
				else if( file.file ){
2601
					data.append(file.name, file.blob, file.file);
2602
				}
2603
				else {
2604
					data.append(file.name, file.blob);
2605
				}
2606
2607
				if( file.file ){
2608
					data.append('_'+file.name, file.file);
2609
				}
2610
			});
2611
		},
2612
2613
2614
		toMultipartData: function (fn){
2615
			this._to([], fn, function (file, data, queue, boundary){
2616
				queue.inc();
2617
				_convertFile(file, function (file, blob){
2618
					data.push(
2619
						  '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2620
						+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2621
						+ '\r\n'
2622
						+ '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2623
						+ '\r\n')
2624
					);
2625
					queue.next();
2626
				}, true);
2627
			}, api.expando);
2628
		}
2629
	};
2630
2631
2632
	function _convertFile(file, fn, useBinaryString){
2633
		var blob = file.blob, filename = file.file;
2634
2635
		if( filename ){
2636
			if( !blob.toDataURL ){
2637
				// The Blob is not an image.
2638
				api.readAsBinaryString(blob, function (evt){
2639
					if( evt.type == 'load' ){
2640
						fn(file, evt.result);
2641
					}
2642
				});
2643
				return;
2644
			}
2645
2646
			var
2647
				  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2648
				, type = mime[file.type] ? file.type : 'image/png'
2649
				, ext  = mime[type] || '.png'
2650
				, quality = blob.quality || 1
2651
			;
2652
2653
			if( !filename.match(new RegExp(ext+'$', 'i')) ){
2654
				// Does not change the current extension, but add a new one.
2655
				filename += ext.replace('?', '');
2656
			}
2657
2658
			file.file = filename;
2659
			file.type = type;
2660
2661
			if( !useBinaryString && blob.toBlob ){
2662
				blob.toBlob(function (blob){
2663
					fn(file, blob);
2664
				}, type, quality);
2665
			}
2666
			else {
2667
				fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2668
			}
2669
		}
2670
		else {
2671
			fn(file, blob);
2672
		}
2673
	}
2674
2675
2676
	// @export
2677
	api.Form = Form;
2678
})(FileAPI, window);
2679
2680
/*global window, FileAPI, Uint8Array */
2681
2682
(function (window, api){
2683
	"use strict";
2684
2685
	var
2686
		  noop = function (){}
2687
		, document = window.document
2688
2689
		, XHR = function (options){
2690
			this.uid = api.uid();
2691
			this.xhr = {
2692
				  abort: noop
2693
				, getResponseHeader: noop
2694
				, getAllResponseHeaders: noop
2695
			};
2696
			this.options = options;
2697
		},
2698
2699
		_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2700
	;
2701
2702
2703
	XHR.prototype = {
2704
		status: 0,
2705
		statusText: '',
2706
		constructor: XHR,
2707
2708
		getResponseHeader: function (name){
2709
			return this.xhr.getResponseHeader(name);
2710
		},
2711
2712
		getAllResponseHeaders: function (){
2713
			return this.xhr.getAllResponseHeaders() || {};
2714
		},
2715
2716
		end: function (status, statusText){
2717
			var _this = this, options = _this.options;
2718
2719
			_this.end		=
2720
			_this.abort		= noop;
2721
			_this.status	= status;
2722
2723
			if( statusText ){
2724
				_this.statusText = statusText;
2725
			}
2726
2727
			api.log('xhr.end:', status, statusText);
2728
			options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2729
2730
			if( _this.xhr && _this.xhr.node ){
2731
				setTimeout(function (){
2732
					var node = _this.xhr.node;
2733
					try { node.parentNode.removeChild(node); } catch (e){}
2734
					try { delete window[_this.uid]; } catch (e){}
2735
					window[_this.uid] = _this.xhr.node = null;
2736
				}, 9);
2737
			}
2738
		},
2739
2740
		abort: function (){
2741
			this.end(0, 'abort');
2742
2743
			if( this.xhr ){
2744
				this.xhr.aborted = true;
2745
				this.xhr.abort();
2746
			}
2747
		},
2748
2749
		send: function (FormData){
2750
			var _this = this, options = this.options;
2751
2752
			FormData.toData(function (data){
2753
				// Start uploading
2754
				options.upload(options, _this);
2755
				_this._send.call(_this, options, data);
2756
			}, options);
2757
		},
2758
2759
		_send: function (options, data){
2760
			var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2761
2762
			api.log('XHR._send:', data);
2763
2764
			if( !options.cache ){
2765
				// No cache
2766
				url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2767
			}
2768
2769
			if( data.nodeName ){
2770
				var jsonp = options.jsonp;
2771
2772
				// prepare callback in GET
2773
				url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2774
2775
				// legacy
2776
				options.upload(options, _this);
2777
2778
				var
2779
					onPostMessage = function (evt){
2780
						if( ~url.indexOf(evt.origin) ){
2781
							try {
2782
								var result = api.parseJSON(evt.data);
2783
								if( result.id == uid ){
2784
									complete(result.status, result.statusText, result.response);
2785
								}
2786
							} catch( err ){
2787
								complete(0, err.message);
2788
							}
2789
						}
2790
					},
2791
2792
					// jsonp-callack
2793
					complete = window[uid] = function (status, statusText, response){
2794
						_this.readyState	= 4;
2795
						_this.responseText	= response;
2796
						_this.end(status, statusText);
2797
2798
						api.event.off(window, 'message', onPostMessage);
2799
						window[uid] = xhr = transport = window[onloadFuncName] = null;
2800
					}
2801
				;
2802
2803
				_this.xhr.abort = function (){
2804
					try {
2805
						if( transport.stop ){ transport.stop(); }
2806
						else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2807
						else { transport.contentWindow.document.execCommand('Stop'); }
2808
					}
2809
					catch (er) {}
2810
					complete(0, "abort");
2811
				};
2812
2813
				api.event.on(window, 'message', onPostMessage);
2814
2815
				window[onloadFuncName] = function (){
2816
					try {
2817
						var
2818
							  win = transport.contentWindow
2819
							, doc = win.document
2820
							, result = win.result || api.parseJSON(doc.body.innerHTML)
2821
						;
2822
						complete(result.status, result.statusText, result.response);
2823
					} catch (e){
2824
						api.log('[transport.onload]', e);
2825
					}
2826
				};
2827
2828
				xhr = document.createElement('div');
2829
				xhr.innerHTML = '<form target="'+ uid +'" action="../../../ng-file-upload-5.0.9/src/'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2830
							+ '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2831
							+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2832
							+ '</form>'
2833
				;
2834
2835
				// get form-data & transport
2836
				var
2837
					  form = xhr.getElementsByTagName('form')[0]
2838
					, transport = xhr.getElementsByTagName('iframe')[0]
2839
				;
2840
2841
				form.appendChild(data);
2842
2843
				api.log(form.parentNode.innerHTML);
2844
2845
				// append to DOM
2846
				document.body.appendChild(xhr);
2847
2848
				// keep a reference to node-transport
2849
				_this.xhr.node = xhr;
2850
2851
				// send
2852
				_this.readyState = 2; // loaded
2853
				form.submit();
2854
				form = null;
2855
			}
2856
			else {
2857
				// Clean url
2858
				url = url.replace(/([a-z]+)=(\?)&?/i, '');
2859
2860
				// html5
2861
				if (this.xhr && this.xhr.aborted) {
2862
					api.log("Error: already aborted");
2863
					return;
2864
				}
2865
				xhr = _this.xhr = api.getXHR();
2866
2867
				if (data.params) {
2868
					url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2869
				}
2870
2871
				xhr.open('POST', url, true);
2872
2873
				if( api.withCredentials ){
2874
					xhr.withCredentials = "true";
2875
				}
2876
2877
				if( !options.headers || !options.headers['X-Requested-With'] ){
2878
					xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2879
				}
2880
2881
				api.each(options.headers, function (val, key){
2882
					xhr.setRequestHeader(key, val);
2883
				});
2884
2885
2886
				if ( options._chunked ) {
2887
					// chunked upload
2888
					if( xhr.upload ){
2889
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2890
							if (!data.retry) {
2891
								// show progress only for correct chunk uploads
2892
								options.progress({
2893
									  type:			evt.type
2894
									, total:		data.size
2895
									, loaded:		data.start + evt.loaded
2896
									, totalSize:	data.size
2897
								}, _this, options);
2898
							}
2899
						}, 100), false);
2900
					}
2901
2902
					xhr.onreadystatechange = function (){
2903
						var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2904
2905
						_this.status     = xhr.status;
2906
						_this.statusText = xhr.statusText;
2907
						_this.readyState = xhr.readyState;
2908
2909
						if( xhr.readyState == 4 ){
2910
							try {
2911
								for( var k in _xhrResponsePostfix ){
2912
									_this['response'+k]  = xhr['response'+k];
2913
								}
2914
							}catch(_){}
2915
							xhr.onreadystatechange = null;
2916
2917
							if (!xhr.status || xhr.status - 201 > 0) {
2918
								api.log("Error: " + xhr.status);
2919
								// some kind of error
2920
								// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2921
								// up - server error
2922
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2923
									// let's try again the same chunk
2924
									// only applicable for recoverable error codes 500 && 416
2925
									var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2926
2927
									// inform about recoverable problems
2928
									options.pause(data.file, options);
2929
2930
									// smart restart if server reports about the last known byte
2931
									api.log("X-Last-Known-Byte: " + lkb);
2932
									if (lkb) {
2933
										data.end = lkb;
2934
									} else {
2935
										data.end = data.start - 1;
2936
										if (416 == xhr.status) {
2937
											data.end = data.end - options.chunkSize;
2938
										}
2939
									}
2940
2941
									setTimeout(function () {
2942
										_this._send(options, data);
2943
									}, delay);
2944
								} else {
2945
									// no mo retries
2946
									_this.end(xhr.status);
2947
								}
2948
							} else {
2949
								// success
2950
								data.retry = 0;
2951
2952
								if (data.end == data.size - 1) {
2953
									// finished
2954
									_this.end(xhr.status);
2955
								} else {
2956
									// next chunk
2957
2958
									// shift position if server reports about the last known byte
2959
									api.log("X-Last-Known-Byte: " + lkb);
2960
									if (lkb) {
2961
										data.end = lkb;
2962
									}
2963
									data.file.FileAPIReadPosition = data.end;
2964
2965
									setTimeout(function () {
2966
										_this._send(options, data);
2967
									}, 0);
2968
								}
2969
							}
2970
2971
							xhr = null;
2972
						}
2973
					};
2974
2975
					data.start = data.end + 1;
2976
					data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2977
2978
					// Retrieve a slice of file
2979
					var
2980
						  file = data.file
2981
						, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2982
					;
2983
2984
					if( data.size && !slice.size ){
2985
						setTimeout(function (){
2986
							_this.end(-1);
2987
						});
2988
					} else {
2989
						xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2990
						xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2991
						xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2992
2993
						xhr.send(slice);
2994
					}
2995
2996
					file = slice = null;
2997
				} else {
2998
					// single piece upload
2999
					if( xhr.upload ){
3000
						// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3001
						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3002
							options.progress(evt, _this, options);
3003
						}, 100), false);
3004
					}
3005
3006
					xhr.onreadystatechange = function (){
3007
						_this.status     = xhr.status;
3008
						_this.statusText = xhr.statusText;
3009
						_this.readyState = xhr.readyState;
3010
3011
						if( xhr.readyState == 4 ){
3012
							for( var k in _xhrResponsePostfix ){
3013
								_this['response'+k]  = xhr['response'+k];
3014
							}
3015
							xhr.onreadystatechange = null;
3016
3017
							if (!xhr.status || xhr.status > 201) {
3018
								api.log("Error: " + xhr.status);
3019
								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3020
									options.retry = (options.retry || 0) + 1;
3021
									var delay = api.networkDownRetryTimeout;
3022
3023
									// inform about recoverable problems
3024
									options.pause(options.file, options);
3025
3026
									setTimeout(function () {
3027
										_this._send(options, data);
3028
									}, delay);
3029
								} else {
3030
									//success
3031
									_this.end(xhr.status);
3032
								}
3033
							} else {
3034
								//success
3035
								_this.end(xhr.status);
3036
							}
3037
3038
							xhr = null;
3039
						}
3040
					};
3041
3042
					if( api.isArray(data) ){
3043
						// multipart
3044
						xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3045
						var rawData = data.join('') +'--_'+ api.expando +'--';
3046
3047
						/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3048
						if( xhr.sendAsBinary ){
3049
							xhr.sendAsBinary(rawData);
3050
						}
3051
						else {
3052
							var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3053
							xhr.send(new Uint8Array(bytes).buffer);
3054
3055
						}
3056
					} else {
3057
						// FormData
3058
						xhr.send(data);
3059
					}
3060
				}
3061
			}
3062
		}
3063
	};
3064
3065
3066
	// @export
3067
	api.XHR = XHR;
3068
})(window, FileAPI);
3069
3070
/**
3071
 * @class	FileAPI.Camera
3072
 * @author	RubaXa	<[email protected]>
3073
 * @support	Chrome 21+, FF 18+, Opera 12+
3074
 */
3075
3076
/*global window, FileAPI, jQuery */
3077
/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3078
(function (window, api){
3079
	"use strict";
3080
3081
	var
3082
		URL = window.URL || window.webkitURL,
3083
3084
		document = window.document,
3085
		navigator = window.navigator,
3086
3087
		getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3088
3089
		html5 = !!getMedia
3090
	;
3091
3092
3093
	// Support "media"
3094
	api.support.media = html5;
3095
3096
3097
	var Camera = function (video){
3098
		this.video = video;
3099
	};
3100
3101
3102
	Camera.prototype = {
3103
		isActive: function (){
3104
			return	!!this._active;
3105
		},
3106
3107
3108
		/**
3109
		 * Start camera streaming
3110
		 * @param	{Function}	callback
3111
		 */
3112
		start: function (callback){
3113
			var
3114
				  _this = this
3115
				, video = _this.video
3116
				, _successId
3117
				, _failId
3118
				, _complete = function (err){
3119
					_this._active = !err;
3120
					clearTimeout(_failId);
3121
					clearTimeout(_successId);
3122
//					api.event.off(video, 'loadedmetadata', _complete);
3123
					callback && callback(err, _this);
3124
				}
3125
			;
3126
3127
			getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3128
				// Success
3129
				_this.stream = stream;
3130
3131
//				api.event.on(video, 'loadedmetadata', function (){
3132
//					_complete(null);
3133
//				});
3134
3135
				// Set camera stream
3136
				video.src = URL.createObjectURL(stream);
3137
3138
				// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3139
				// See crbug.com/110938.
3140
				_successId = setInterval(function (){
3141
					if( _detectVideoSignal(video) ){
3142
						_complete(null);
3143
					}
3144
				}, 1000);
3145
3146
				_failId = setTimeout(function (){
3147
					_complete('timeout');
3148
				}, 5000);
3149
3150
				// Go-go-go!
3151
				video.play();
3152
			}, _complete/*error*/);
3153
		},
3154
3155
3156
		/**
3157
		 * Stop camera streaming
3158
		 */
3159
		stop: function (){
3160
			try {
3161
				this._active = false;
3162
				this.video.pause();
3163
				this.stream.stop();
3164
			} catch( err ){ }
3165
		},
3166
3167
3168
		/**
3169
		 * Create screenshot
3170
		 * @return {FileAPI.Camera.Shot}
3171
		 */
3172
		shot: function (){
3173
			return	new Shot(this.video);
3174
		}
3175
	};
3176
3177
3178
	/**
3179
	 * Get camera element from container
3180
	 *
3181
	 * @static
3182
	 * @param	{HTMLElement}	el
3183
	 * @return	{Camera}
3184
	 */
3185
	Camera.get = function (el){
3186
		return	new Camera(el.firstChild);
3187
	};
3188
3189
3190
	/**
3191
	 * Publish camera element into container
3192
	 *
3193
	 * @static
3194
	 * @param	{HTMLElement}	el
3195
	 * @param	{Object}		options
3196
	 * @param	{Function}		[callback]
3197
	 */
3198
	Camera.publish = function (el, options, callback){
3199
		if( typeof options == 'function' ){
3200
			callback = options;
3201
			options = {};
3202
		}
3203
3204
		// Dimensions of "camera"
3205
		options = api.extend({}, {
3206
			  width:	'100%'
3207
			, height:	'100%'
3208
			, start:	true
3209
		}, options);
3210
3211
3212
		if( el.jquery ){
3213
			// Extract first element, from jQuery collection
3214
			el = el[0];
3215
		}
3216
3217
3218
		var doneFn = function (err){
3219
			if( err ){
3220
				callback(err);
3221
			}
3222
			else {
3223
				// Get camera
3224
				var cam = Camera.get(el);
3225
				if( options.start ){
3226
					cam.start(callback);
3227
				}
3228
				else {
3229
					callback(null, cam);
3230
				}
3231
			}
3232
		};
3233
3234
3235
		el.style.width	= _px(options.width);
3236
		el.style.height	= _px(options.height);
3237
3238
3239
		if( api.html5 && html5 ){
3240
			// Create video element
3241
			var video = document.createElement('video');
3242
3243
			// Set dimensions
3244
			video.style.width	= _px(options.width);
3245
			video.style.height	= _px(options.height);
3246
3247
			// Clean container
3248
			if( window.jQuery ){
3249
				jQuery(el).empty();
3250
			} else {
3251
				el.innerHTML = '';
3252
			}
3253
3254
			// Add "camera" to container
3255
			el.appendChild(video);
3256
3257
			// end
3258
			doneFn();
3259
		}
3260
		else {
3261
			Camera.fallback(el, options, doneFn);
3262
		}
3263
	};
3264
3265
3266
	Camera.fallback = function (el, options, callback){
3267
		callback('not_support_camera');
3268
	};
3269
3270
3271
	/**
3272
	 * @class	FileAPI.Camera.Shot
3273
	 */
3274
	var Shot = function (video){
3275
		var canvas	= video.nodeName ? api.Image.toCanvas(video) : video;
3276
		var shot	= api.Image(canvas);
3277
		shot.type	= 'image/png';
3278
		shot.width	= canvas.width;
3279
		shot.height	= canvas.height;
3280
		shot.size	= canvas.width * canvas.height * 4;
3281
		return	shot;
3282
	};
3283
3284
3285
	/**
3286
	 * Add "px" postfix, if value is a number
3287
	 *
3288
	 * @private
3289
	 * @param	{*}  val
3290
	 * @return	{String}
3291
	 */
3292
	function _px(val){
3293
		return	val >= 0 ? val + 'px' : val;
3294
	}
3295
3296
3297
	/**
3298
	 * @private
3299
	 * @param	{HTMLVideoElement} video
3300
	 * @return	{Boolean}
3301
	 */
3302
	function _detectVideoSignal(video){
3303
		var canvas = document.createElement('canvas'), ctx, res = false;
3304
		try {
3305
			ctx = canvas.getContext('2d');
3306
			ctx.drawImage(video, 0, 0, 1, 1);
3307
			res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3308
		}
3309
		catch( e ){}
3310
		return	res;
3311
	}
3312
3313
3314
	// @export
3315
	Camera.Shot	= Shot;
3316
	api.Camera	= Camera;
3317
})(window, FileAPI);
3318
3319
/**
3320
 * FileAPI fallback to Flash
3321
 *
3322
 * @flash-developer  "Vladimir Demidov" <[email protected]>
3323
 */
3324
3325
/*global window, ActiveXObject, FileAPI */
3326
(function (window, jQuery, api) {
3327
	"use strict";
3328
3329
	var
3330
		  document = window.document
3331
		, location = window.location
3332
		, navigator = window.navigator
3333
		, _each = api.each
3334
	;
3335
3336
3337
	api.support.flash = (function (){
3338
		var mime = navigator.mimeTypes, has = false;
3339
3340
		if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3341
			has	= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3342
		}
3343
		else {
3344
			try {
3345
				has	= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3346
			}
3347
			catch(er){
3348
				api.log('Flash -- does not supported.');
3349
			}
3350
		}
3351
3352
		if( has && /^file:/i.test(location) ){
3353
			api.log('[warn] Flash does not work on `file:` protocol.');
3354
		}
3355
3356
		return	has;
3357
	})();
3358
3359
3360
	   api.support.flash
3361
	&& (0
3362
		|| !api.html5 || !api.support.html5
3363
		|| (api.cors && !api.support.cors)
3364
		|| (api.media && !api.support.media)
3365
	)
3366
	&& (function (){
3367
		var
3368
			  _attr  = api.uid()
3369
			, _retry = 0
3370
			, _files = {}
3371
			, _rhttp = /^https?:/i
3372
3373
			, flash = {
3374
				_fn: {},
3375
3376
3377
				/**
3378
				 * Initialization & preload flash object
3379
				 */
3380
				init: function (){
3381
					var child = document.body && document.body.firstChild;
3382
3383
					if( child ){
3384
						do {
3385
							if( child.nodeType == 1 ){
3386
								api.log('FlashAPI.state: awaiting');
3387
3388
								var dummy = document.createElement('div');
3389
3390
								dummy.id = '_' + _attr;
3391
3392
								_css(dummy, {
3393
									  top: 1
3394
									, right: 1
3395
									, width: 5
3396
									, height: 5
3397
									, position: 'absolute'
3398
									, zIndex: 1e6+'' // set max zIndex
3399
								});
3400
3401
								child.parentNode.insertBefore(dummy, child);
3402
								flash.publish(dummy, _attr);
3403
3404
								return;
3405
							}
3406
						}
3407
						while( child = child.nextSibling );
3408
					}
3409
3410
					if( _retry < 10 ){
3411
						setTimeout(flash.init, ++_retry*50);
3412
					}
3413
				},
3414
3415
3416
				/**
3417
				 * Publish flash-object
3418
				 *
3419
				 * @param {HTMLElement} el
3420
				 * @param {String} id
3421
				 * @param {Object} [opts]
3422
				 */
3423
				publish: function (el, id, opts){
3424
					opts = opts || {};
3425
					el.innerHTML = _makeFlashHTML({
3426
						  id: id
3427
						, src: _getUrl(api.flashUrl, 'r=' + api.version)
3428
//						, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3429
						, wmode: opts.camera ? '' : 'transparent'
3430
						, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3431
							+ '&flashId='+ id
3432
							+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3433
							+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3434
							+ '&timeout='+api.flashAbortTimeout
3435
							+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3436
							+ '&debug='+(api.debug?"1":"")
3437
					}, opts);
3438
				},
3439
3440
3441
				ready: function (){
3442
					api.log('FlashAPI.state: ready');
3443
3444
					flash.ready = api.F;
3445
					flash.isReady = true;
3446
					flash.patch();
3447
					flash.patchCamera && flash.patchCamera();
3448
					api.event.on(document, 'mouseover', flash.mouseover);
3449
					api.event.on(document, 'click', function (evt){
3450
						if( flash.mouseover(evt) ){
3451
							evt.preventDefault
3452
								? evt.preventDefault()
3453
								: (evt.returnValue = true)
3454
							;
3455
						}
3456
					});
3457
				},
3458
3459
3460
				getEl: function (){
3461
					return	document.getElementById('_'+_attr);
3462
				},
3463
3464
3465
				getWrapper: function (node){
3466
					do {
3467
						if( /js-fileapi-wrapper/.test(node.className) ){
3468
							return	node;
3469
						}
3470
					}
3471
					while( (node = node.parentNode) && (node !== document.body) );
3472
				},
3473
				
3474
				disableMouseover: false,
3475
3476
				mouseover: function (evt){
3477
					if (!flash.disableMouseover) {
3478
						var target = api.event.fix(evt).target;
3479
	
3480
						if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3481
							var
3482
								  state = target.getAttribute(_attr)
3483
								, wrapper = flash.getWrapper(target)
3484
							;
3485
	
3486
							if( api.multiFlash ){
3487
								// check state:
3488
								//   i — published
3489
								//   i — initialization
3490
								//   r — ready
3491
								if( state == 'i' || state == 'r' ){
3492
									// publish fail
3493
									return	false;
3494
								}
3495
								else if( state != 'p' ){
3496
									// set "init" state
3497
									target.setAttribute(_attr, 'i');
3498
	
3499
									var dummy = document.createElement('div');
3500
	
3501
									if( !wrapper ){
3502
										api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3503
										return;
3504
									}
3505
	
3506
									_css(dummy, {
3507
										  top:    0
3508
										, left:   0
3509
										, width:  target.offsetWidth
3510
										, height: target.offsetHeight
3511
										, zIndex: 1e6+'' // set max zIndex
3512
										, position: 'absolute'
3513
									});
3514
	
3515
									wrapper.appendChild(dummy);
3516
									flash.publish(dummy, api.uid());
3517
	
3518
									// set "publish" state
3519
									target.setAttribute(_attr, 'p');
3520
								}
3521
	
3522
								return	true;
3523
							}
3524
							else if( wrapper ){
3525
								// Use one flash element
3526
								var box = _getDimensions(wrapper);
3527
								_css(flash.getEl(), box);
3528
	
3529
								// Set current input
3530
								flash.curInp = target;
3531
							}
3532
						}
3533
						else if( !/object|embed/i.test(target.nodeName) ){
3534
							_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3535
						}
3536
					}
3537
				},
3538
3539
				onEvent: function (evt){
3540
					var type = evt.type;
3541
					
3542
					if( type == 'ready' ){
3543
						try {
3544
							// set "ready" state
3545
							flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3546
						} catch (e){
3547
						}
3548
3549
						flash.ready();
3550
						setTimeout(function (){ flash.mouseenter(evt); }, 50);
3551
						return	true;
3552
					}
3553
					else if( type === 'ping' ){
3554
						api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3555
					}
3556
					else if( type === 'log' ){
3557
						api.log('(flash -> js).log:', evt.target);
3558
					}
3559
					else if( type in flash ){
3560
						setTimeout(function (){
3561
							api.log('FlashAPI.event.'+evt.type+':', evt);
3562
							flash[type](evt);
3563
						}, 1);
3564
					}
3565
				},
3566
				mouseDown: function(evt) {
3567
					flash.disableMouseover = true;
3568
				},
3569
				cancel: function(evt) {
3570
					flash.disableMouseover = false;
3571
				},
3572
				mouseenter: function (evt){
3573
					var node = flash.getInput(evt.flashId);
3574
3575
					if( node ){
3576
						// Set multiple mode
3577
						flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3578
3579
3580
						// Set files filter
3581
						var accept = [], exts = {};
3582
3583
						_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3584
							api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3585
								exts[ext] = 1;
3586
							});
3587
						});
3588
3589
						_each(exts, function (i, ext){
3590
							accept.push( ext );
3591
						});
3592
3593
						flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3594
					}
3595
				},
3596
3597
3598
				get: function (id){
3599
					return	document[id] || window[id] || document.embeds[id];
3600
				},
3601
3602
3603
				getInput: function (id){
3604
					if( api.multiFlash ){
3605
						try {
3606
							var node = flash.getWrapper(flash.get(id));
3607
							if( node ){
3608
								return node.getElementsByTagName('input')[0];
3609
							}
3610
						} catch (e){
3611
							api.log('[err] Can not find "input" by flashId:', id, e);
3612
						}
3613
					} else {
3614
						return	flash.curInp;
3615
					}
3616
				},
3617
3618
3619
				select: function (evt){
3620
					try {
3621
						var
3622
							  inp = flash.getInput(evt.flashId)
3623
							, uid = api.uid(inp)
3624
							, files = evt.target.files
3625
							, event
3626
						;
3627
						_each(files, function (file){
3628
							api.checkFileObj(file);
3629
						});
3630
	
3631
						_files[uid] = files;
3632
	
3633
						if( document.createEvent ){
3634
							event = document.createEvent('Event');
3635
							event.files = files;
3636
							event.initEvent('change', true, true);
3637
							inp.dispatchEvent(event);
3638
						}
3639
						else if( jQuery ){
3640
							jQuery(inp).trigger({ type: 'change', files: files });
3641
						}
3642
						else {
3643
							event = document.createEventObject();
3644
							event.files = files;
3645
							inp.fireEvent('onchange', event);
3646
						}
3647
					} finally {
3648
						flash.disableMouseover = false;
3649
					}
3650
				},
3651
3652
				interval: null,
3653
				cmd: function (id, name, data, last) {
3654
					if (flash.uploadInProgress && flash.readInProgress) {
3655
						setTimeout(function() {
3656
							flash.cmd(id, name, data, last);
3657
						}, 100);
3658
					} else {
3659
						this.cmdFn(id, name, data, last);
3660
					}
3661
				},
3662
				
3663
				cmdFn: function(id, name, data, last) {
3664
					try {
3665
						api.log('(js -> flash).'+name+':', data);
3666
						return flash.get(id.flashId || id).cmd(name, data);
3667
					} catch (e){
3668
						api.log('(js -> flash).onError:', e);
3669
						if( !last ){
3670
							// try again
3671
							setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3672
						}
3673
					}
3674
				},
3675
3676
				patch: function (){
3677
					api.flashEngine = true;
3678
3679
					// FileAPI
3680
					_inherit(api, {
3681
						readAsDataURL: function (file, callback){
3682
							if( _isHtmlFile(file) ){
3683
								this.parent.apply(this, arguments);
3684
							}
3685
							else {
3686
								api.log('FlashAPI.readAsBase64');
3687
								flash.readInProgress = true;
3688
								flash.cmd(file, 'readAsBase64', {
3689
									id: file.id,
3690
									callback: _wrap(function _(err, base64){
3691
										flash.readInProgress = false;
3692
										_unwrap(_);
3693
3694
										api.log('FlashAPI.readAsBase64:', err);
3695
3696
										callback({
3697
											  type: err ? 'error' : 'load'
3698
											, error: err
3699
											, result: 'data:'+ file.type +';base64,'+ base64
3700
										});
3701
									})
3702
								});
3703
							}
3704
						},
3705
3706
						readAsText: function (file, encoding, callback){
3707
							if( callback ){
3708
								api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
3709
							} else {
3710
								callback = encoding;
3711
							}
3712
3713
							api.readAsDataURL(file, function (evt){
3714
								if( evt.type == 'load' ){
3715
									try {
3716
										evt.result = window.atob(evt.result.split(';base64,')[1]);
3717
									} catch( err ){
3718
										evt.type = 'error';
3719
										evt.error = err.toString();
3720
									}
3721
								}
3722
								callback(evt);
3723
							});
3724
						},
3725
						
3726
						getFiles: function (input, filter, callback){
3727
							if( callback ){
3728
								api.filterFiles(api.getFiles(input), filter, callback);
3729
								return null;
3730
							}
3731
3732
							var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3733
3734
3735
							if( !files ){
3736
								// Файлов нету, вызываем родительский метод
3737
								return	this.parent.apply(this, arguments);
3738
							}
3739
3740
3741
							if( filter ){
3742
								filter	= api.getFilesFilter(filter);
3743
								files	= api.filter(files, function (file){ return filter.test(file.name); });
3744
							}
3745
3746
							return	files;
3747
						},
3748
3749
3750
						getInfo: function (file, fn){
3751
							if( _isHtmlFile(file) ){
3752
								this.parent.apply(this, arguments);
3753
							}
3754
							else if( file.isShot ){
3755
								fn(null, file.info = {
3756
									width: file.width,
3757
									height: file.height
3758
								});
3759
							}
3760
							else {
3761
								if( !file.__info ){
3762
									var defer = file.__info = api.defer();
3763
3764
//									flash.cmd(file, 'getFileInfo', {
3765
//										  id: file.id
3766
//										, callback: _wrap(function _(err, info){
3767
//											_unwrap(_);
3768
//											defer.resolve(err, file.info = info);
3769
//										})
3770
//									});
3771
									defer.resolve(null, file.info = null);
3772
3773
								}
3774
3775
								file.__info.then(fn);
3776
							}
3777
						}
3778
					});
3779
3780
3781
					// FileAPI.Image
3782
					api.support.transform = true;
3783
					api.Image && _inherit(api.Image.prototype, {
3784
						get: function (fn, scaleMode){
3785
							this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3786
							return this.parent(fn);
3787
						},
3788
3789
						_load: function (file, fn){
3790
							api.log('FlashAPI.Image._load:', file);
3791
3792
							if( _isHtmlFile(file) ){
3793
								this.parent.apply(this, arguments);
3794
							}
3795
							else {
3796
								var _this = this;
3797
								api.getInfo(file, function (err){
3798
									fn.call(_this, err, file);
3799
								});
3800
							}
3801
						},
3802
3803
						_apply: function (file, fn){
3804
							api.log('FlashAPI.Image._apply:', file);
3805
3806
							if( _isHtmlFile(file) ){
3807
								this.parent.apply(this, arguments);
3808
							}
3809
							else {
3810
								var m = this.getMatrix(file.info), doneFn = fn;
3811
3812
								flash.cmd(file, 'imageTransform', {
3813
									  id: file.id
3814
									, matrix: m
3815
									, callback: _wrap(function _(err, base64){
3816
										api.log('FlashAPI.Image._apply.callback:', err);
3817
										_unwrap(_);
3818
3819
										if( err ){
3820
											doneFn(err);
3821
										}
3822
										else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3823
											_makeFlashImage({
3824
												  width:	(m.deg % 180) ? m.dh : m.dw
3825
												, height:	(m.deg % 180) ? m.dw : m.dh
3826
												, scale:	m.scaleMode
3827
											}, base64, doneFn);
3828
										}
3829
										else {
3830
											if( m.filter ){
3831
												doneFn = function (err, img){
3832
													if( err ){
3833
														fn(err);
3834
													}
3835
													else {
3836
														api.Image.applyFilter(img, m.filter, function (){
3837
															fn(err, this.canvas);
3838
														});
3839
													}
3840
												};
3841
											}
3842
3843
											api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3844
										}
3845
									})
3846
								});
3847
							}
3848
						},
3849
3850
						toData: function (fn){
3851
							var
3852
								  file = this.file
3853
								, info = file.info
3854
								, matrix = this.getMatrix(info)
3855
							;
3856
							api.log('FlashAPI.Image.toData');
3857
3858
							if( _isHtmlFile(file) ){
3859
								this.parent.apply(this, arguments);
3860
							}
3861
							else {
3862
								if( matrix.deg == 'auto' ){
3863
									matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3864
								}
3865
3866
								fn.call(this, !file.info, {
3867
									  id:		file.id
3868
									, flashId:	file.flashId
3869
									, name:		file.name
3870
									, type:		file.type
3871
									, matrix:	matrix
3872
								});
3873
							}
3874
						}
3875
					});
3876
3877
3878
					api.Image && _inherit(api.Image, {
3879
						fromDataURL: function (dataURL, size, callback){
3880
							if( !api.support.dataURI || dataURL.length > 3e4 ){
3881
								_makeFlashImage(
3882
									  api.extend({ scale: 'exactFit' }, size)
3883
									, dataURL.replace(/^data:[^,]+,/, '')
3884
									, function (err, el){ callback(el); }
3885
								);
3886
							}
3887
							else {
3888
								this.parent(dataURL, size, callback);
3889
							}
3890
						}
3891
					});
3892
3893
					// FileAPI.Form
3894
					_inherit(api.Form.prototype, {
3895
						toData: function (fn){
3896
							var items = this.items, i = items.length;
3897
3898
							for( ; i--; ){
3899
								if( items[i].file && _isHtmlFile(items[i].blob) ){
3900
									return this.parent.apply(this, arguments);
3901
								}
3902
							}
3903
3904
							api.log('FlashAPI.Form.toData');
3905
							fn(items);
3906
						}
3907
					});
3908
3909
3910
					// FileAPI.XHR
3911
					_inherit(api.XHR.prototype, {
3912
						_send: function (options, formData){
3913
							if(
3914
								   formData.nodeName
3915
								|| formData.append && api.support.html5
3916
								|| api.isArray(formData) && (typeof formData[0] === 'string')
3917
							){
3918
								// HTML5, Multipart or IFrame
3919
								return	this.parent.apply(this, arguments);
3920
							}
3921
3922
3923
							var
3924
								  data = {}
3925
								, files = {}
3926
								, _this = this
3927
								, flashId
3928
								, fileId
3929
							;
3930
3931
							_each(formData, function (item){
3932
								if( item.file ){
3933
									files[item.name] = item = _getFileDescr(item.blob);
3934
									fileId  = item.id;
3935
									flashId = item.flashId;
3936
								}
3937
								else {
3938
									data[item.name] = item.blob;
3939
								}
3940
							});
3941
3942
							if( !fileId ){
3943
								flashId = _attr;
3944
							}
3945
3946
							if( !flashId ){
3947
								api.log('[err] FlashAPI._send: flashId -- undefined');
3948
								return this.parent.apply(this, arguments);
3949
							}
3950
							else {
3951
								api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3952
							}
3953
3954
							_this.xhr = {
3955
								headers: {},
3956
								abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
3957
								getResponseHeader: function (name){ return this.headers[name]; },
3958
								getAllResponseHeaders: function (){ return this.headers; }
3959
							};
3960
3961
							var queue = api.queue(function (){
3962
								flash.uploadInProgress = true;
3963
								flash.cmd(flashId, 'upload', {
3964
									  url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3965
									, data: data
3966
									, files: fileId ? files : null
3967
									, headers: options.headers || {}
3968
									, callback: _wrap(function upload(evt){
3969
										var type = evt.type, result = evt.result;
3970
3971
										api.log('FlashAPI.upload.'+type);
3972
3973
										if( type == 'progress' ){
3974
											evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3975
											evt.lengthComputable = true;
3976
											options.progress(evt);
3977
										}
3978
										else if( type == 'complete' ){
3979
											flash.uploadInProgress = false;
3980
											_unwrap(upload);
3981
3982
											if( typeof result == 'string' ){
3983
												_this.responseText	= result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3984
											}
3985
3986
											_this.end(evt.status || 200);
3987
										}
3988
										else if( type == 'abort' || type == 'error' ){
3989
											flash.uploadInProgress = false;
3990
											_this.end(evt.status || 0, evt.message);
3991
											_unwrap(upload);
3992
										}
3993
									})
3994
								});
3995
							});
3996
3997
3998
							// #2174: FileReference.load() call while FileReference.upload() or vice versa
3999
							_each(files, function (file){
4000
								queue.inc();
4001
								api.getInfo(file, queue.next);
4002
							});
4003
4004
							queue.check();
4005
						}
4006
					});
4007
				}
4008
			}
4009
		;
4010
4011
4012
		function _makeFlashHTML(opts){
4013
			return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
4014
				+ '<param name="movie" value="#src#" />'
4015
				+ '<param name="flashvars" value="#flashvars#" />'
4016
				+ '<param name="swliveconnect" value="true" />'
4017
				+ '<param name="allowscriptaccess" value="always" />'
4018
				+ '<param name="allownetworking" value="all" />'
4019
				+ '<param name="menu" value="false" />'
4020
				+ '<param name="wmode" value="#wmode#" />'
4021
				+ '<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>'
4022
				+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
4023
			;
4024
		}
4025
4026
4027
		function _css(el, css){
4028
			if( el && el.style ){
4029
				var key, val;
4030
				for( key in css ){
4031
					val = css[key];
4032
					if( typeof val == 'number' ){
4033
						val += 'px';
4034
					}
4035
					try { el.style[key] = val; } catch (e) {}
4036
				}
4037
				
4038
			}
4039
		}
4040
4041
4042
		function _inherit(obj, methods){
4043
			_each(methods, function (fn, name){
4044
				var prev = obj[name];
4045
				obj[name] = function (){
4046
					this.parent = prev;
4047
					return fn.apply(this, arguments);
4048
				};
4049
			});
4050
		}
4051
4052
		function _isHtmlFile(file){
4053
			return	file && !file.flashId;
4054
		}
4055
4056
		function _wrap(fn){
4057
			var id = fn.wid = api.uid();
4058
			flash._fn[id] = fn;
4059
			return	'FileAPI.Flash._fn.'+id;
4060
		}
4061
4062
4063
		function _unwrap(fn){
4064
			try {
4065
				flash._fn[fn.wid] = null;
4066
				delete	flash._fn[fn.wid];
4067
			}
4068
			catch(e){}
4069
		}
4070
4071
4072
		function _getUrl(url, params){
4073
			if( !_rhttp.test(url) ){
4074
				if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4075
					var path = location.pathname;
4076
					path = path.substr(0, path.lastIndexOf('/'));
4077
					url = (path +'/'+ url).replace('/./', '/');
4078
				}
4079
4080
				if( '//' != url.substr(0, 2) ){
4081
					url = '//' + location.host + url;
4082
				}
4083
4084
				if( !_rhttp.test(url) ){
4085
					url = location.protocol + url;
4086
				}
4087
			}
4088
4089
			if( params ){
4090
				url += (/\?/.test(url) ? '&' : '?') + params;
4091
			}
4092
4093
			return	url;
4094
		}
4095
4096
4097
		function _makeFlashImage(opts, base64, fn){
4098
			var
4099
				  key
4100
				, flashId = api.uid()
4101
				, el = document.createElement('div')
4102
				, attempts = 10
4103
			;
4104
4105
			for( key in opts ){
4106
				el.setAttribute(key, opts[key]);
4107
				el[key] = opts[key];
4108
			}
4109
4110
			_css(el, opts);
4111
4112
			opts.width	= '100%';
4113
			opts.height	= '100%';
4114
4115
			el.innerHTML = _makeFlashHTML(api.extend({
4116
				  id: flashId
4117
				, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4118
				, wmode: 'opaque'
4119
				, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4120
					_unwrap(_);
4121
					if( --attempts > 0 ){
4122
						_setImage();
4123
					}
4124
					return true;
4125
				})
4126
			}, opts));
4127
4128
			function _setImage(){
4129
				try {
4130
					// Get flash-object by id
4131
					var img = flash.get(flashId);
4132
					img.setImage(base64);
4133
				} catch (e){
4134
					api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4135
				}
4136
			}
4137
4138
			fn(false, el);
4139
			el = null;
4140
		}
4141
4142
4143
		function _getFileDescr(file){
4144
			return	{
4145
				  id: file.id
4146
				, name: file.name
4147
				, matrix: file.matrix
4148
				, flashId: file.flashId
4149
			};
4150
		}
4151
4152
4153
		function _getDimensions(el){
4154
			var
4155
				  box = el.getBoundingClientRect()
4156
				, body = document.body
4157
				, docEl = (el && el.ownerDocument).documentElement
4158
			;
4159
			
4160
			function getOffset(obj) {
4161
			    var left, top;
4162
			    left = top = 0;
4163
			    if (obj.offsetParent) {
4164
			        do {
4165
			            left += obj.offsetLeft;
4166
			            top  += obj.offsetTop;
4167
			        } while (obj = obj.offsetParent);
4168
			    }
4169
			    return {
4170
			        left : left,
4171
			        top : top
4172
			    };
4173
			};
4174
			
4175
			return {
4176
				  top:		getOffset(el).top
4177
				, left:		getOffset(el).left
4178
				, width:	el.offsetWidth
4179
				, height:	el.offsetHeight
4180
			};
4181
		}
4182
4183
		// @export
4184
		api.Flash = flash;
4185
4186
4187
		// Check dataURI support
4188
		api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4189
			api.support.dataURI = !(img.width != 1 || img.height != 1);
4190
			flash.init();
4191
		});
4192
	})();
4193
})(window, window.jQuery, FileAPI);
4194
4195
/**
4196
 * FileAPI fallback to Flash
4197
 *
4198
 * @flash-developer  "Vladimir Demidov" <[email protected]>
4199
 */
4200
4201
/*global window, FileAPI */
4202
(function (window, jQuery, api) {
4203
    "use strict";
4204
4205
    var _each = api.each,
4206
        _cameraQueue = [];
4207
4208
4209
    if (api.support.flash && (api.media && !api.support.media)) {
4210
        (function () {
4211
4212
            function _wrap(fn) {
4213
                var id = fn.wid = api.uid();
4214
                api.Flash._fn[id] = fn;
4215
                return 'FileAPI.Flash._fn.' + id;
4216
            }
4217
4218
4219
            function _unwrap(fn) {
4220
                try {
4221
                    api.Flash._fn[fn.wid] = null;
4222
                    delete api.Flash._fn[fn.wid];
4223
                } catch (e) {
4224
                }
4225
            }
4226
4227
            var flash = api.Flash;
4228
            api.extend(api.Flash, {
4229
4230
                patchCamera: function () {
4231
                    api.Camera.fallback = function (el, options, callback) {
4232
                        var camId = api.uid();
4233
                        api.log('FlashAPI.Camera.publish: ' + camId);
4234
                        flash.publish(el, camId, api.extend(options, {
4235
                            camera: true,
4236
                            onEvent: _wrap(function _(evt) {
4237
                                if (evt.type === 'camera') {
4238
                                    _unwrap(_);
4239
4240
                                    if (evt.error) {
4241
                                        api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4242
                                        callback(evt.error);
4243
                                    } else {
4244
                                        api.log('FlashAPI.Camera.publish.success: ' + camId);
4245
                                        callback(null);
4246
                                    }
4247
                                }
4248
                            })
4249
                        }));
4250
                    };
4251
                    // Run
4252
                    _each(_cameraQueue, function (args) {
4253
                        api.Camera.fallback.apply(api.Camera, args);
4254
                    });
4255
                    _cameraQueue = [];
4256
4257
4258
                    // FileAPI.Camera:proto
4259
                    api.extend(api.Camera.prototype, {
4260
                        _id: function () {
4261
                            return this.video.id;
4262
                        },
4263
4264
                        start: function (callback) {
4265
                            var _this = this;
4266
                            flash.cmd(this._id(), 'camera.on', {
4267
                                callback: _wrap(function _(evt) {
4268
                                    _unwrap(_);
4269
4270
                                    if (evt.error) {
4271
                                        api.log('FlashAPI.camera.on.error: ' + evt.error);
4272
                                        callback(evt.error, _this);
4273
                                    } else {
4274
                                        api.log('FlashAPI.camera.on.success: ' + _this._id());
4275
                                        _this._active = true;
4276
                                        callback(null, _this);
4277
                                    }
4278
                                })
4279
                            });
4280
                        },
4281
4282
                        stop: function () {
4283
                            this._active = false;
4284
                            flash.cmd(this._id(), 'camera.off');
4285
                        },
4286
4287
                        shot: function () {
4288
                            api.log('FlashAPI.Camera.shot:', this._id());
4289
4290
                            var shot = api.Flash.cmd(this._id(), 'shot', {});
4291
                            shot.type = 'image/png';
4292
                            shot.flashId = this._id();
4293
                            shot.isShot = true;
4294
4295
                            return new api.Camera.Shot(shot);
4296
                        }
4297
                    });
4298
                }
4299
            });
4300
4301
            api.Camera.fallback = function () {
4302
                _cameraQueue.push(arguments);
4303
            };
4304
4305
        }());
4306
    }
4307
}(window, window.jQuery, FileAPI));
4308
if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
4309