fileapi 0.1.2 → 0.1.3

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