fileapi 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,10 +40,20 @@ FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
40
40
  if( !file.__exif ){
41
41
  var defer = file.__exif = FileAPI.defer();
42
42
 
43
- FileAPI.readAsBinaryString(file, function (evt){
43
+ var blob = file;
44
+ if (blob instanceof Blob && blob.size > 128*1024) {
45
+ try {
46
+ var size = Math.min(blob.size, 128 * 1024);
47
+ blob = (blob.slice || blob.mozSlice || blob.webkitSlice).call(blob, 0, size);
48
+ } catch (e) {
49
+ FileAPI.log("exception "+ e);
50
+ }
51
+ }
52
+
53
+ FileAPI.readAsBinaryString(blob, function (evt){
44
54
  if( evt.type == 'load' ){
45
55
  var binaryString = evt.result;
46
- var oFile = new BinaryFile(binaryString, 0, file.size);
56
+ var oFile = new BinaryFile(binaryString, 0, blob.size);
47
57
  var exif = EXIF.readFromBinaryFile(oFile);
48
58
 
49
59
  defer.resolve(false, { 'exif': exif || {} });
@@ -0,0 +1,4228 @@
1
+ /*! FileAPI 2.0.9 - 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
+
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.9',
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|height|width/.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 == 'height' ){
2059
+ dw = dh * sf;
2060
+ }
2061
+ else if( strategy == 'width' ){
2062
+ dh = dw / sf;
2063
+ }
2064
+ else if( strategy ){
2065
+ if( !(sw > dw || sh > dh) ){
2066
+ dw = sw;
2067
+ dh = sh;
2068
+ }
2069
+ else if( strategy == 'min' ){
2070
+ dw = round(sf < df ? min(sw, dw) : dh*sf);
2071
+ dh = round(sf < df ? dw/sf : min(sh, dh));
2072
+ }
2073
+ else {
2074
+ dw = round(sf >= df ? min(sw, dw) : dh*sf);
2075
+ dh = round(sf >= df ? dw/sf : min(sh, dh));
2076
+ }
2077
+ }
2078
+
2079
+ m.sw = sw;
2080
+ m.sh = sh;
2081
+ m.dw = dw;
2082
+ m.dh = dh;
2083
+ m.multipass = api.multiPassResize;
2084
+ return m;
2085
+ },
2086
+
2087
+ _trans: function (fn){
2088
+ this._load(this.file, function (err, image){
2089
+ if( err ){
2090
+ fn(err);
2091
+ }
2092
+ else {
2093
+ try {
2094
+ this._apply(image, fn);
2095
+ } catch (err){
2096
+ api.log('[err] FileAPI.Image.fn._apply:', err);
2097
+ fn(err);
2098
+ }
2099
+ }
2100
+ });
2101
+ },
2102
+
2103
+
2104
+ get: function (fn){
2105
+ if( api.support.transform ){
2106
+ var _this = this, matrix = _this.matrix;
2107
+
2108
+ if( matrix.deg == 'auto' ){
2109
+ api.getInfo(_this.file, function (err, info){
2110
+ // rotate by exif orientation
2111
+ matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
2112
+ _this._trans(fn);
2113
+ });
2114
+ }
2115
+ else {
2116
+ _this._trans(fn);
2117
+ }
2118
+ }
2119
+ else {
2120
+ fn('not_support_transform');
2121
+ }
2122
+
2123
+ return this;
2124
+ },
2125
+
2126
+
2127
+ toData: function (fn){
2128
+ return this.get(fn);
2129
+ }
2130
+
2131
+ };
2132
+
2133
+
2134
+ Image.exifOrientation = exifOrientation;
2135
+
2136
+
2137
+ Image.transform = function (file, transform, autoOrientation, fn){
2138
+ function _transform(err, img){
2139
+ // img -- info object
2140
+ var
2141
+ images = {}
2142
+ , queue = api.queue(function (err){
2143
+ fn(err, images);
2144
+ })
2145
+ ;
2146
+
2147
+ if( !err ){
2148
+ api.each(transform, function (params, name){
2149
+ if( !queue.isFail() ){
2150
+ var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
2151
+
2152
+ if( isFn ){
2153
+ params(img, ImgTrans);
2154
+ }
2155
+ else if( params.width ){
2156
+ ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
2157
+ }
2158
+ else {
2159
+ if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
2160
+ ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
2161
+ }
2162
+ }
2163
+
2164
+ if( params.crop ){
2165
+ var crop = params.crop;
2166
+ ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
2167
+ }
2168
+
2169
+ if( params.rotate === undef && autoOrientation ){
2170
+ params.rotate = 'auto';
2171
+ }
2172
+
2173
+ ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
2174
+
2175
+ if( !isFn ){
2176
+ ImgTrans.set({
2177
+ deg: params.rotate
2178
+ , overlay: params.overlay
2179
+ , filter: params.filter
2180
+ , quality: params.quality || 1
2181
+ });
2182
+ }
2183
+
2184
+ queue.inc();
2185
+ ImgTrans.toData(function (err, image){
2186
+ if( err ){
2187
+ queue.fail();
2188
+ }
2189
+ else {
2190
+ images[name] = image;
2191
+ queue.next();
2192
+ }
2193
+ });
2194
+ }
2195
+ });
2196
+ }
2197
+ else {
2198
+ queue.fail();
2199
+ }
2200
+ }
2201
+
2202
+
2203
+ // @todo: Оло-ло, нужно рефакторить это место
2204
+ if( file.width ){
2205
+ _transform(false, file);
2206
+ } else {
2207
+ api.getInfo(file, _transform);
2208
+ }
2209
+ };
2210
+
2211
+
2212
+ // @const
2213
+ api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
2214
+ api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
2215
+ Image[x+'_'+y] = i*3 + j;
2216
+ Image[y+'_'+x] = i*3 + j;
2217
+ });
2218
+ });
2219
+
2220
+
2221
+ /**
2222
+ * Trabsform element to canvas
2223
+ *
2224
+ * @param {Image|HTMLVideoElement} el
2225
+ * @returns {Canvas}
2226
+ */
2227
+ Image.toCanvas = function(el){
2228
+ var canvas = document.createElement('canvas');
2229
+ canvas.width = el.videoWidth || el.width;
2230
+ canvas.height = el.videoHeight || el.height;
2231
+ canvas.getContext('2d').drawImage(el, 0, 0);
2232
+ return canvas;
2233
+ };
2234
+
2235
+
2236
+ /**
2237
+ * Create image from DataURL
2238
+ * @param {String} dataURL
2239
+ * @param {Object} size
2240
+ * @param {Function} callback
2241
+ */
2242
+ Image.fromDataURL = function (dataURL, size, callback){
2243
+ var img = api.newImage(dataURL);
2244
+ api.extend(img, size);
2245
+ callback(img);
2246
+ };
2247
+
2248
+
2249
+ /**
2250
+ * Apply filter (caman.js)
2251
+ *
2252
+ * @param {Canvas|Image} canvas
2253
+ * @param {String|Function} filter
2254
+ * @param {Function} doneFn
2255
+ */
2256
+ Image.applyFilter = function (canvas, filter, doneFn){
2257
+ if( typeof filter == 'function' ){
2258
+ filter(canvas, doneFn);
2259
+ }
2260
+ else if( window.Caman ){
2261
+ // http://camanjs.com/guides/
2262
+ window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
2263
+ if( typeof filter == 'string' ){
2264
+ this[filter]();
2265
+ }
2266
+ else {
2267
+ api.each(filter, function (val, method){
2268
+ this[method](val);
2269
+ }, this);
2270
+ }
2271
+ this.render(doneFn);
2272
+ });
2273
+ }
2274
+ };
2275
+
2276
+
2277
+ /**
2278
+ * For load-image-ios.js
2279
+ */
2280
+ api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
2281
+ try {
2282
+ return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
2283
+ } catch (ex) {
2284
+ api.log('renderImageToCanvas failed');
2285
+ throw ex;
2286
+ }
2287
+ };
2288
+
2289
+
2290
+ // @export
2291
+ api.support.canvas = api.support.transform = support;
2292
+ api.Image = Image;
2293
+ })(FileAPI, document);
2294
+
2295
+ /*
2296
+ * JavaScript Load Image iOS scaling fixes 1.0.3
2297
+ * https://github.com/blueimp/JavaScript-Load-Image
2298
+ *
2299
+ * Copyright 2013, Sebastian Tschan
2300
+ * https://blueimp.net
2301
+ *
2302
+ * iOS image scaling fixes based on
2303
+ * https://github.com/stomita/ios-imagefile-megapixel
2304
+ *
2305
+ * Licensed under the MIT license:
2306
+ * http://www.opensource.org/licenses/MIT
2307
+ */
2308
+
2309
+ /*jslint nomen: true, bitwise: true */
2310
+ /*global FileAPI, window, document */
2311
+
2312
+ (function (factory) {
2313
+ 'use strict';
2314
+ factory(FileAPI);
2315
+ }(function (loadImage) {
2316
+ 'use strict';
2317
+
2318
+ // Only apply fixes on the iOS platform:
2319
+ if (!window.navigator || !window.navigator.platform ||
2320
+ !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
2321
+ return;
2322
+ }
2323
+
2324
+ var originalRenderMethod = loadImage.renderImageToCanvas;
2325
+
2326
+ // Detects subsampling in JPEG images:
2327
+ loadImage.detectSubsampling = function (img) {
2328
+ var canvas,
2329
+ context;
2330
+ if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
2331
+ canvas = document.createElement('canvas');
2332
+ canvas.width = canvas.height = 1;
2333
+ context = canvas.getContext('2d');
2334
+ context.drawImage(img, -img.width + 1, 0);
2335
+ // subsampled image becomes half smaller in rendering size.
2336
+ // check alpha channel value to confirm image is covering edge pixel or not.
2337
+ // if alpha value is 0 image is not covering, hence subsampled.
2338
+ return context.getImageData(0, 0, 1, 1).data[3] === 0;
2339
+ }
2340
+ return false;
2341
+ };
2342
+
2343
+ // Detects vertical squash in JPEG images:
2344
+ loadImage.detectVerticalSquash = function (img, subsampled) {
2345
+ var naturalHeight = img.naturalHeight || img.height,
2346
+ canvas = document.createElement('canvas'),
2347
+ context = canvas.getContext('2d'),
2348
+ data,
2349
+ sy,
2350
+ ey,
2351
+ py,
2352
+ alpha;
2353
+ if (subsampled) {
2354
+ naturalHeight /= 2;
2355
+ }
2356
+ canvas.width = 1;
2357
+ canvas.height = naturalHeight;
2358
+ context.drawImage(img, 0, 0);
2359
+ data = context.getImageData(0, 0, 1, naturalHeight).data;
2360
+ // search image edge pixel position in case it is squashed vertically:
2361
+ sy = 0;
2362
+ ey = naturalHeight;
2363
+ py = naturalHeight;
2364
+ while (py > sy) {
2365
+ alpha = data[(py - 1) * 4 + 3];
2366
+ if (alpha === 0) {
2367
+ ey = py;
2368
+ } else {
2369
+ sy = py;
2370
+ }
2371
+ py = (ey + sy) >> 1;
2372
+ }
2373
+ return (py / naturalHeight) || 1;
2374
+ };
2375
+
2376
+ // Renders image to canvas while working around iOS image scaling bugs:
2377
+ // https://github.com/blueimp/JavaScript-Load-Image/issues/13
2378
+ loadImage.renderImageToCanvas = function (
2379
+ canvas,
2380
+ img,
2381
+ sourceX,
2382
+ sourceY,
2383
+ sourceWidth,
2384
+ sourceHeight,
2385
+ destX,
2386
+ destY,
2387
+ destWidth,
2388
+ destHeight
2389
+ ) {
2390
+ if (img._type === 'image/jpeg') {
2391
+ var context = canvas.getContext('2d'),
2392
+ tmpCanvas = document.createElement('canvas'),
2393
+ tileSize = 1024,
2394
+ tmpContext = tmpCanvas.getContext('2d'),
2395
+ subsampled,
2396
+ vertSquashRatio,
2397
+ tileX,
2398
+ tileY;
2399
+ tmpCanvas.width = tileSize;
2400
+ tmpCanvas.height = tileSize;
2401
+ context.save();
2402
+ subsampled = loadImage.detectSubsampling(img);
2403
+ if (subsampled) {
2404
+ sourceX /= 2;
2405
+ sourceY /= 2;
2406
+ sourceWidth /= 2;
2407
+ sourceHeight /= 2;
2408
+ }
2409
+ vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
2410
+ if (subsampled || vertSquashRatio !== 1) {
2411
+ sourceY *= vertSquashRatio;
2412
+ destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
2413
+ destHeight = Math.ceil(
2414
+ tileSize * destHeight / sourceHeight / vertSquashRatio
2415
+ );
2416
+ destY = 0;
2417
+ tileY = 0;
2418
+ while (tileY < sourceHeight) {
2419
+ destX = 0;
2420
+ tileX = 0;
2421
+ while (tileX < sourceWidth) {
2422
+ tmpContext.clearRect(0, 0, tileSize, tileSize);
2423
+ tmpContext.drawImage(
2424
+ img,
2425
+ sourceX,
2426
+ sourceY,
2427
+ sourceWidth,
2428
+ sourceHeight,
2429
+ -tileX,
2430
+ -tileY,
2431
+ sourceWidth,
2432
+ sourceHeight
2433
+ );
2434
+ context.drawImage(
2435
+ tmpCanvas,
2436
+ 0,
2437
+ 0,
2438
+ tileSize,
2439
+ tileSize,
2440
+ destX,
2441
+ destY,
2442
+ destWidth,
2443
+ destHeight
2444
+ );
2445
+ tileX += tileSize;
2446
+ destX += destWidth;
2447
+ }
2448
+ tileY += tileSize;
2449
+ destY += destHeight;
2450
+ }
2451
+ context.restore();
2452
+ return canvas;
2453
+ }
2454
+ }
2455
+ return originalRenderMethod(
2456
+ canvas,
2457
+ img,
2458
+ sourceX,
2459
+ sourceY,
2460
+ sourceWidth,
2461
+ sourceHeight,
2462
+ destX,
2463
+ destY,
2464
+ destWidth,
2465
+ destHeight
2466
+ );
2467
+ };
2468
+
2469
+ }));
2470
+
2471
+ /*global window, FileAPI */
2472
+
2473
+ (function (api, window){
2474
+ "use strict";
2475
+
2476
+ var
2477
+ document = window.document
2478
+ , FormData = window.FormData
2479
+ , Form = function (){ this.items = []; }
2480
+ , encodeURIComponent = window.encodeURIComponent
2481
+ ;
2482
+
2483
+
2484
+ Form.prototype = {
2485
+
2486
+ append: function (name, blob, file, type){
2487
+ this.items.push({
2488
+ name: name
2489
+ , blob: blob && blob.blob || (blob == void 0 ? '' : blob)
2490
+ , file: blob && (file || blob.name)
2491
+ , type: blob && (type || blob.type)
2492
+ });
2493
+ },
2494
+
2495
+ each: function (fn){
2496
+ var i = 0, n = this.items.length;
2497
+ for( ; i < n; i++ ){
2498
+ fn.call(this, this.items[i]);
2499
+ }
2500
+ },
2501
+
2502
+ toData: function (fn, options){
2503
+ // allow chunked transfer if we have only one file to send
2504
+ // flag is used below and in XHR._send
2505
+ options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
2506
+
2507
+ if( !api.support.html5 ){
2508
+ api.log('FileAPI.Form.toHtmlData');
2509
+ this.toHtmlData(fn);
2510
+ }
2511
+ else if( !api.formData || this.multipart || !FormData ){
2512
+ api.log('FileAPI.Form.toMultipartData');
2513
+ this.toMultipartData(fn);
2514
+ }
2515
+ else if( options._chunked ){
2516
+ api.log('FileAPI.Form.toPlainData');
2517
+ this.toPlainData(fn);
2518
+ }
2519
+ else {
2520
+ api.log('FileAPI.Form.toFormData');
2521
+ this.toFormData(fn);
2522
+ }
2523
+ },
2524
+
2525
+ _to: function (data, complete, next, arg){
2526
+ var queue = api.queue(function (){
2527
+ complete(data);
2528
+ });
2529
+
2530
+ this.each(function (file){
2531
+ next(file, data, queue, arg);
2532
+ });
2533
+
2534
+ queue.check();
2535
+ },
2536
+
2537
+
2538
+ toHtmlData: function (fn){
2539
+ this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
2540
+ var blob = file.blob, hidden;
2541
+
2542
+ if( file.file ){
2543
+ api.reset(blob, true);
2544
+ // set new name
2545
+ blob.name = file.name;
2546
+ blob.disabled = false;
2547
+ data.appendChild(blob);
2548
+ }
2549
+ else {
2550
+ hidden = document.createElement('input');
2551
+ hidden.name = file.name;
2552
+ hidden.type = 'hidden';
2553
+ hidden.value = blob;
2554
+ data.appendChild(hidden);
2555
+ }
2556
+ });
2557
+ },
2558
+
2559
+ toPlainData: function (fn){
2560
+ this._to({}, fn, function (file, data, queue){
2561
+ if( file.file ){
2562
+ data.type = file.file;
2563
+ }
2564
+
2565
+ if( file.blob.toBlob ){
2566
+ // canvas
2567
+ queue.inc();
2568
+ _convertFile(file, function (file, blob){
2569
+ data.name = file.name;
2570
+ data.file = blob;
2571
+ data.size = blob.length;
2572
+ data.type = file.type;
2573
+ queue.next();
2574
+ });
2575
+ }
2576
+ else if( file.file ){
2577
+ // file
2578
+ data.name = file.blob.name;
2579
+ data.file = file.blob;
2580
+ data.size = file.blob.size;
2581
+ data.type = file.type;
2582
+ }
2583
+ else {
2584
+ // additional data
2585
+ if( !data.params ){
2586
+ data.params = [];
2587
+ }
2588
+ data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
2589
+ }
2590
+
2591
+ data.start = -1;
2592
+ data.end = data.file && data.file.FileAPIReadPosition || -1;
2593
+ data.retry = 0;
2594
+ });
2595
+ },
2596
+
2597
+ toFormData: function (fn){
2598
+ this._to(new FormData, fn, function (file, data, queue){
2599
+ if( file.blob && file.blob.toBlob ){
2600
+ queue.inc();
2601
+ _convertFile(file, function (file, blob){
2602
+ data.append(file.name, blob, file.file);
2603
+ queue.next();
2604
+ });
2605
+ }
2606
+ else if( file.file ){
2607
+ data.append(file.name, file.blob, file.file);
2608
+ }
2609
+ else {
2610
+ data.append(file.name, file.blob);
2611
+ }
2612
+
2613
+ if( file.file ){
2614
+ data.append('_'+file.name, file.file);
2615
+ }
2616
+ });
2617
+ },
2618
+
2619
+
2620
+ toMultipartData: function (fn){
2621
+ this._to([], fn, function (file, data, queue, boundary){
2622
+ queue.inc();
2623
+ _convertFile(file, function (file, blob){
2624
+ data.push(
2625
+ '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
2626
+ + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
2627
+ + '\r\n'
2628
+ + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
2629
+ + '\r\n')
2630
+ );
2631
+ queue.next();
2632
+ }, true);
2633
+ }, api.expando);
2634
+ }
2635
+ };
2636
+
2637
+
2638
+ function _convertFile(file, fn, useBinaryString){
2639
+ var blob = file.blob, filename = file.file;
2640
+
2641
+ if( filename ){
2642
+ if( !blob.toDataURL ){
2643
+ // The Blob is not an image.
2644
+ api.readAsBinaryString(blob, function (evt){
2645
+ if( evt.type == 'load' ){
2646
+ fn(file, evt.result);
2647
+ }
2648
+ });
2649
+ return;
2650
+ }
2651
+
2652
+ var
2653
+ mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
2654
+ , type = mime[file.type] ? file.type : 'image/png'
2655
+ , ext = mime[type] || '.png'
2656
+ , quality = blob.quality || 1
2657
+ ;
2658
+
2659
+ if( !filename.match(new RegExp(ext+'$', 'i')) ){
2660
+ // Does not change the current extension, but add a new one.
2661
+ filename += ext.replace('?', '');
2662
+ }
2663
+
2664
+ file.file = filename;
2665
+ file.type = type;
2666
+
2667
+ if( !useBinaryString && blob.toBlob ){
2668
+ blob.toBlob(function (blob){
2669
+ fn(file, blob);
2670
+ }, type, quality);
2671
+ }
2672
+ else {
2673
+ fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
2674
+ }
2675
+ }
2676
+ else {
2677
+ fn(file, blob);
2678
+ }
2679
+ }
2680
+
2681
+
2682
+ // @export
2683
+ api.Form = Form;
2684
+ })(FileAPI, window);
2685
+
2686
+ /*global window, FileAPI, Uint8Array */
2687
+
2688
+ (function (window, api){
2689
+ "use strict";
2690
+
2691
+ var
2692
+ noop = function (){}
2693
+ , document = window.document
2694
+
2695
+ , XHR = function (options){
2696
+ this.uid = api.uid();
2697
+ this.xhr = {
2698
+ abort: noop
2699
+ , getResponseHeader: noop
2700
+ , getAllResponseHeaders: noop
2701
+ };
2702
+ this.options = options;
2703
+ },
2704
+
2705
+ _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
2706
+ ;
2707
+
2708
+
2709
+ XHR.prototype = {
2710
+ status: 0,
2711
+ statusText: '',
2712
+ constructor: XHR,
2713
+
2714
+ getResponseHeader: function (name){
2715
+ return this.xhr.getResponseHeader(name);
2716
+ },
2717
+
2718
+ getAllResponseHeaders: function (){
2719
+ return this.xhr.getAllResponseHeaders() || {};
2720
+ },
2721
+
2722
+ end: function (status, statusText){
2723
+ var _this = this, options = _this.options;
2724
+
2725
+ _this.end =
2726
+ _this.abort = noop;
2727
+ _this.status = status;
2728
+
2729
+ if( statusText ){
2730
+ _this.statusText = statusText;
2731
+ }
2732
+
2733
+ api.log('xhr.end:', status, statusText);
2734
+ options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
2735
+
2736
+ if( _this.xhr && _this.xhr.node ){
2737
+ setTimeout(function (){
2738
+ var node = _this.xhr.node;
2739
+ try { node.parentNode.removeChild(node); } catch (e){}
2740
+ try { delete window[_this.uid]; } catch (e){}
2741
+ window[_this.uid] = _this.xhr.node = null;
2742
+ }, 9);
2743
+ }
2744
+ },
2745
+
2746
+ abort: function (){
2747
+ this.end(0, 'abort');
2748
+
2749
+ if( this.xhr ){
2750
+ this.xhr.aborted = true;
2751
+ this.xhr.abort();
2752
+ }
2753
+ },
2754
+
2755
+ send: function (FormData){
2756
+ var _this = this, options = this.options;
2757
+
2758
+ FormData.toData(function (data){
2759
+ // Start uploading
2760
+ options.upload(options, _this);
2761
+ _this._send.call(_this, options, data);
2762
+ }, options);
2763
+ },
2764
+
2765
+ _send: function (options, data){
2766
+ var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
2767
+
2768
+ api.log('XHR._send:', data);
2769
+
2770
+ if( !options.cache ){
2771
+ // No cache
2772
+ url += (~url.indexOf('?') ? '&' : '?') + api.uid();
2773
+ }
2774
+
2775
+ if( data.nodeName ){
2776
+ var jsonp = options.jsonp;
2777
+
2778
+ // prepare callback in GET
2779
+ url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
2780
+
2781
+ // legacy
2782
+ options.upload(options, _this);
2783
+
2784
+ var
2785
+ onPostMessage = function (evt){
2786
+ if( ~url.indexOf(evt.origin) ){
2787
+ try {
2788
+ var result = api.parseJSON(evt.data);
2789
+ if( result.id == uid ){
2790
+ complete(result.status, result.statusText, result.response);
2791
+ }
2792
+ } catch( err ){
2793
+ complete(0, err.message);
2794
+ }
2795
+ }
2796
+ },
2797
+
2798
+ // jsonp-callack
2799
+ complete = window[uid] = function (status, statusText, response){
2800
+ _this.readyState = 4;
2801
+ _this.responseText = response;
2802
+ _this.end(status, statusText);
2803
+
2804
+ api.event.off(window, 'message', onPostMessage);
2805
+ window[uid] = xhr = transport = window[onloadFuncName] = null;
2806
+ }
2807
+ ;
2808
+
2809
+ _this.xhr.abort = function (){
2810
+ try {
2811
+ if( transport.stop ){ transport.stop(); }
2812
+ else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
2813
+ else { transport.contentWindow.document.execCommand('Stop'); }
2814
+ }
2815
+ catch (er) {}
2816
+ complete(0, "abort");
2817
+ };
2818
+
2819
+ api.event.on(window, 'message', onPostMessage);
2820
+
2821
+ window[onloadFuncName] = function (){
2822
+ try {
2823
+ var
2824
+ win = transport.contentWindow
2825
+ , doc = win.document
2826
+ , result = win.result || api.parseJSON(doc.body.innerHTML)
2827
+ ;
2828
+ complete(result.status, result.statusText, result.response);
2829
+ } catch (e){
2830
+ api.log('[transport.onload]', e);
2831
+ }
2832
+ };
2833
+
2834
+ xhr = document.createElement('div');
2835
+ xhr.innerHTML = '<form target="'+ uid +'" action="'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
2836
+ + '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
2837
+ + (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
2838
+ + '</form>'
2839
+ ;
2840
+
2841
+ // get form-data & transport
2842
+ var
2843
+ form = xhr.getElementsByTagName('form')[0]
2844
+ , transport = xhr.getElementsByTagName('iframe')[0]
2845
+ ;
2846
+
2847
+ form.appendChild(data);
2848
+
2849
+ api.log(form.parentNode.innerHTML);
2850
+
2851
+ // append to DOM
2852
+ document.body.appendChild(xhr);
2853
+
2854
+ // keep a reference to node-transport
2855
+ _this.xhr.node = xhr;
2856
+
2857
+ // send
2858
+ _this.readyState = 2; // loaded
2859
+ form.submit();
2860
+ form = null;
2861
+ }
2862
+ else {
2863
+ // Clean url
2864
+ url = url.replace(/([a-z]+)=(\?)&?/i, '');
2865
+
2866
+ // html5
2867
+ if (this.xhr && this.xhr.aborted) {
2868
+ api.log("Error: already aborted");
2869
+ return;
2870
+ }
2871
+ xhr = _this.xhr = api.getXHR();
2872
+
2873
+ if (data.params) {
2874
+ url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
2875
+ }
2876
+
2877
+ xhr.open('POST', url, true);
2878
+
2879
+ if( api.withCredentials ){
2880
+ xhr.withCredentials = "true";
2881
+ }
2882
+
2883
+ if( !options.headers || !options.headers['X-Requested-With'] ){
2884
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2885
+ }
2886
+
2887
+ api.each(options.headers, function (val, key){
2888
+ xhr.setRequestHeader(key, val);
2889
+ });
2890
+
2891
+
2892
+ if ( options._chunked ) {
2893
+ // chunked upload
2894
+ if( xhr.upload ){
2895
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
2896
+ if (!data.retry) {
2897
+ // show progress only for correct chunk uploads
2898
+ options.progress({
2899
+ type: evt.type
2900
+ , total: data.size
2901
+ , loaded: data.start + evt.loaded
2902
+ , totalSize: data.size
2903
+ }, _this, options);
2904
+ }
2905
+ }, 100), false);
2906
+ }
2907
+
2908
+ xhr.onreadystatechange = function (){
2909
+ var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
2910
+
2911
+ _this.status = xhr.status;
2912
+ _this.statusText = xhr.statusText;
2913
+ _this.readyState = xhr.readyState;
2914
+
2915
+ if( xhr.readyState == 4 ){
2916
+ for( var k in _xhrResponsePostfix ){
2917
+ _this['response'+k] = xhr['response'+k];
2918
+ }
2919
+ xhr.onreadystatechange = null;
2920
+
2921
+ if (!xhr.status || xhr.status - 201 > 0) {
2922
+ api.log("Error: " + xhr.status);
2923
+ // some kind of error
2924
+ // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
2925
+ // up - server error
2926
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
2927
+ // let's try again the same chunk
2928
+ // only applicable for recoverable error codes 500 && 416
2929
+ var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
2930
+
2931
+ // inform about recoverable problems
2932
+ options.pause(data.file, options);
2933
+
2934
+ // smart restart if server reports about the last known byte
2935
+ api.log("X-Last-Known-Byte: " + lkb);
2936
+ if (lkb) {
2937
+ data.end = lkb;
2938
+ } else {
2939
+ data.end = data.start - 1;
2940
+ if (416 == xhr.status) {
2941
+ data.end = data.end - options.chunkSize;
2942
+ }
2943
+ }
2944
+
2945
+ setTimeout(function () {
2946
+ _this._send(options, data);
2947
+ }, delay);
2948
+ } else {
2949
+ // no mo retries
2950
+ _this.end(xhr.status);
2951
+ }
2952
+ } else {
2953
+ // success
2954
+ data.retry = 0;
2955
+
2956
+ if (data.end == data.size - 1) {
2957
+ // finished
2958
+ _this.end(xhr.status);
2959
+ } else {
2960
+ // next chunk
2961
+
2962
+ // shift position if server reports about the last known byte
2963
+ api.log("X-Last-Known-Byte: " + lkb);
2964
+ if (lkb) {
2965
+ data.end = lkb;
2966
+ }
2967
+ data.file.FileAPIReadPosition = data.end;
2968
+
2969
+ setTimeout(function () {
2970
+ _this._send(options, data);
2971
+ }, 0);
2972
+ }
2973
+ }
2974
+
2975
+ xhr = null;
2976
+ }
2977
+ };
2978
+
2979
+ data.start = data.end + 1;
2980
+ data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
2981
+
2982
+ // Retrieve a slice of file
2983
+ var
2984
+ file = data.file
2985
+ , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
2986
+ ;
2987
+
2988
+ if( data.size && !slice.size ){
2989
+ setTimeout(function (){
2990
+ _this.end(-1);
2991
+ });
2992
+ } else {
2993
+ xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
2994
+ xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
2995
+ xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
2996
+
2997
+ xhr.send(slice);
2998
+ }
2999
+
3000
+ file = slice = null;
3001
+ } else {
3002
+ // single piece upload
3003
+ if( xhr.upload ){
3004
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
3005
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
3006
+ options.progress(evt, _this, options);
3007
+ }, 100), false);
3008
+ }
3009
+
3010
+ xhr.onreadystatechange = function (){
3011
+ _this.status = xhr.status;
3012
+ _this.statusText = xhr.statusText;
3013
+ _this.readyState = xhr.readyState;
3014
+
3015
+ if( xhr.readyState == 4 ){
3016
+ for( var k in _xhrResponsePostfix ){
3017
+ _this['response'+k] = xhr['response'+k];
3018
+ }
3019
+ xhr.onreadystatechange = null;
3020
+
3021
+ if (!xhr.status || xhr.status > 201) {
3022
+ api.log("Error: " + xhr.status);
3023
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
3024
+ options.retry = (options.retry || 0) + 1;
3025
+ var delay = api.networkDownRetryTimeout;
3026
+
3027
+ // inform about recoverable problems
3028
+ options.pause(options.file, options);
3029
+
3030
+ setTimeout(function () {
3031
+ _this._send(options, data);
3032
+ }, delay);
3033
+ } else {
3034
+ //success
3035
+ _this.end(xhr.status);
3036
+ }
3037
+ } else {
3038
+ //success
3039
+ _this.end(xhr.status);
3040
+ }
3041
+
3042
+ xhr = null;
3043
+ }
3044
+ };
3045
+
3046
+ if( api.isArray(data) ){
3047
+ // multipart
3048
+ xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
3049
+ var rawData = data.join('') +'--_'+ api.expando +'--';
3050
+
3051
+ /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
3052
+ if( xhr.sendAsBinary ){
3053
+ xhr.sendAsBinary(rawData);
3054
+ }
3055
+ else {
3056
+ var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
3057
+ xhr.send(new Uint8Array(bytes).buffer);
3058
+
3059
+ }
3060
+ } else {
3061
+ // FormData
3062
+ xhr.send(data);
3063
+ }
3064
+ }
3065
+ }
3066
+ }
3067
+ };
3068
+
3069
+
3070
+ // @export
3071
+ api.XHR = XHR;
3072
+ })(window, FileAPI);
3073
+
3074
+ /**
3075
+ * @class FileAPI.Camera
3076
+ * @author RubaXa <trash@rubaxa.org>
3077
+ * @support Chrome 21+, FF 18+, Opera 12+
3078
+ */
3079
+
3080
+ /*global window, FileAPI, jQuery */
3081
+ /** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
3082
+ (function (window, api){
3083
+ "use strict";
3084
+
3085
+ var
3086
+ URL = window.URL || window.webkitURL,
3087
+
3088
+ document = window.document,
3089
+ navigator = window.navigator,
3090
+
3091
+ getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
3092
+
3093
+ html5 = !!getMedia
3094
+ ;
3095
+
3096
+
3097
+ // Support "media"
3098
+ api.support.media = html5;
3099
+
3100
+
3101
+ var Camera = function (video){
3102
+ this.video = video;
3103
+ };
3104
+
3105
+
3106
+ Camera.prototype = {
3107
+ isActive: function (){
3108
+ return !!this._active;
3109
+ },
3110
+
3111
+
3112
+ /**
3113
+ * Start camera streaming
3114
+ * @param {Function} callback
3115
+ */
3116
+ start: function (callback){
3117
+ var
3118
+ _this = this
3119
+ , video = _this.video
3120
+ , _successId
3121
+ , _failId
3122
+ , _complete = function (err){
3123
+ _this._active = !err;
3124
+ clearTimeout(_failId);
3125
+ clearTimeout(_successId);
3126
+ // api.event.off(video, 'loadedmetadata', _complete);
3127
+ callback && callback(err, _this);
3128
+ }
3129
+ ;
3130
+
3131
+ getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
3132
+ // Success
3133
+ _this.stream = stream;
3134
+
3135
+ // api.event.on(video, 'loadedmetadata', function (){
3136
+ // _complete(null);
3137
+ // });
3138
+
3139
+ // Set camera stream
3140
+ video.src = URL.createObjectURL(stream);
3141
+
3142
+ // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
3143
+ // See crbug.com/110938.
3144
+ _successId = setInterval(function (){
3145
+ if( _detectVideoSignal(video) ){
3146
+ _complete(null);
3147
+ }
3148
+ }, 1000);
3149
+
3150
+ _failId = setTimeout(function (){
3151
+ _complete('timeout');
3152
+ }, 5000);
3153
+
3154
+ // Go-go-go!
3155
+ video.play();
3156
+ }, _complete/*error*/);
3157
+ },
3158
+
3159
+
3160
+ /**
3161
+ * Stop camera streaming
3162
+ */
3163
+ stop: function (){
3164
+ try {
3165
+ this._active = false;
3166
+ this.video.pause();
3167
+ this.stream.stop();
3168
+ } catch( err ){ }
3169
+ },
3170
+
3171
+
3172
+ /**
3173
+ * Create screenshot
3174
+ * @return {FileAPI.Camera.Shot}
3175
+ */
3176
+ shot: function (){
3177
+ return new Shot(this.video);
3178
+ }
3179
+ };
3180
+
3181
+
3182
+ /**
3183
+ * Get camera element from container
3184
+ *
3185
+ * @static
3186
+ * @param {HTMLElement} el
3187
+ * @return {Camera}
3188
+ */
3189
+ Camera.get = function (el){
3190
+ return new Camera(el.firstChild);
3191
+ };
3192
+
3193
+
3194
+ /**
3195
+ * Publish camera element into container
3196
+ *
3197
+ * @static
3198
+ * @param {HTMLElement} el
3199
+ * @param {Object} options
3200
+ * @param {Function} [callback]
3201
+ */
3202
+ Camera.publish = function (el, options, callback){
3203
+ if( typeof options == 'function' ){
3204
+ callback = options;
3205
+ options = {};
3206
+ }
3207
+
3208
+ // Dimensions of "camera"
3209
+ options = api.extend({}, {
3210
+ width: '100%'
3211
+ , height: '100%'
3212
+ , start: true
3213
+ }, options);
3214
+
3215
+
3216
+ if( el.jquery ){
3217
+ // Extract first element, from jQuery collection
3218
+ el = el[0];
3219
+ }
3220
+
3221
+
3222
+ var doneFn = function (err){
3223
+ if( err ){
3224
+ callback(err);
3225
+ }
3226
+ else {
3227
+ // Get camera
3228
+ var cam = Camera.get(el);
3229
+ if( options.start ){
3230
+ cam.start(callback);
3231
+ }
3232
+ else {
3233
+ callback(null, cam);
3234
+ }
3235
+ }
3236
+ };
3237
+
3238
+
3239
+ el.style.width = _px(options.width);
3240
+ el.style.height = _px(options.height);
3241
+
3242
+
3243
+ if( api.html5 && html5 ){
3244
+ // Create video element
3245
+ var video = document.createElement('video');
3246
+
3247
+ // Set dimensions
3248
+ video.style.width = _px(options.width);
3249
+ video.style.height = _px(options.height);
3250
+
3251
+ // Clean container
3252
+ if( window.jQuery ){
3253
+ jQuery(el).empty();
3254
+ } else {
3255
+ el.innerHTML = '';
3256
+ }
3257
+
3258
+ // Add "camera" to container
3259
+ el.appendChild(video);
3260
+
3261
+ // end
3262
+ doneFn();
3263
+ }
3264
+ else {
3265
+ Camera.fallback(el, options, doneFn);
3266
+ }
3267
+ };
3268
+
3269
+
3270
+ Camera.fallback = function (el, options, callback){
3271
+ callback('not_support_camera');
3272
+ };
3273
+
3274
+
3275
+ /**
3276
+ * @class FileAPI.Camera.Shot
3277
+ */
3278
+ var Shot = function (video){
3279
+ var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
3280
+ var shot = api.Image(canvas);
3281
+ shot.type = 'image/png';
3282
+ shot.width = canvas.width;
3283
+ shot.height = canvas.height;
3284
+ shot.size = canvas.width * canvas.height * 4;
3285
+ return shot;
3286
+ };
3287
+
3288
+
3289
+ /**
3290
+ * Add "px" postfix, if value is a number
3291
+ *
3292
+ * @private
3293
+ * @param {*} val
3294
+ * @return {String}
3295
+ */
3296
+ function _px(val){
3297
+ return val >= 0 ? val + 'px' : val;
3298
+ }
3299
+
3300
+
3301
+ /**
3302
+ * @private
3303
+ * @param {HTMLVideoElement} video
3304
+ * @return {Boolean}
3305
+ */
3306
+ function _detectVideoSignal(video){
3307
+ var canvas = document.createElement('canvas'), ctx, res = false;
3308
+ try {
3309
+ ctx = canvas.getContext('2d');
3310
+ ctx.drawImage(video, 0, 0, 1, 1);
3311
+ res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
3312
+ }
3313
+ catch( e ){}
3314
+ return res;
3315
+ }
3316
+
3317
+
3318
+ // @export
3319
+ Camera.Shot = Shot;
3320
+ api.Camera = Camera;
3321
+ })(window, FileAPI);
3322
+
3323
+ /**
3324
+ * FileAPI fallback to Flash
3325
+ *
3326
+ * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
3327
+ */
3328
+
3329
+ /*global window, ActiveXObject, FileAPI */
3330
+ (function (window, jQuery, api) {
3331
+ "use strict";
3332
+
3333
+ var
3334
+ document = window.document
3335
+ , location = window.location
3336
+ , navigator = window.navigator
3337
+ , _each = api.each
3338
+ ;
3339
+
3340
+
3341
+ api.support.flash = (function (){
3342
+ var mime = navigator.mimeTypes, has = false;
3343
+
3344
+ if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
3345
+ has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
3346
+ }
3347
+ else {
3348
+ try {
3349
+ has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
3350
+ }
3351
+ catch(er){
3352
+ api.log('Flash -- does not supported.');
3353
+ }
3354
+ }
3355
+
3356
+ if( has && /^file:/i.test(location) ){
3357
+ api.log('[warn] Flash does not work on `file:` protocol.');
3358
+ }
3359
+
3360
+ return has;
3361
+ })();
3362
+
3363
+
3364
+ api.support.flash
3365
+ && (0
3366
+ || !api.html5 || !api.support.html5
3367
+ || (api.cors && !api.support.cors)
3368
+ || (api.media && !api.support.media)
3369
+ )
3370
+ && (function (){
3371
+ var
3372
+ _attr = api.uid()
3373
+ , _retry = 0
3374
+ , _files = {}
3375
+ , _rhttp = /^https?:/i
3376
+
3377
+ , flash = {
3378
+ _fn: {},
3379
+
3380
+
3381
+ /**
3382
+ * Initialization & preload flash object
3383
+ */
3384
+ init: function (){
3385
+ var child = document.body && document.body.firstChild;
3386
+
3387
+ if( child ){
3388
+ do {
3389
+ if( child.nodeType == 1 ){
3390
+ api.log('FlashAPI.state: awaiting');
3391
+
3392
+ var dummy = document.createElement('div');
3393
+
3394
+ dummy.id = '_' + _attr;
3395
+
3396
+ _css(dummy, {
3397
+ top: 1
3398
+ , right: 1
3399
+ , width: 5
3400
+ , height: 5
3401
+ , position: 'absolute'
3402
+ , zIndex: 1e6+'' // set max zIndex
3403
+ });
3404
+
3405
+ child.parentNode.insertBefore(dummy, child);
3406
+ flash.publish(dummy, _attr);
3407
+
3408
+ return;
3409
+ }
3410
+ }
3411
+ while( child = child.nextSibling );
3412
+ }
3413
+
3414
+ if( _retry < 10 ){
3415
+ setTimeout(flash.init, ++_retry*50);
3416
+ }
3417
+ },
3418
+
3419
+
3420
+ /**
3421
+ * Publish flash-object
3422
+ *
3423
+ * @param {HTMLElement} el
3424
+ * @param {String} id
3425
+ * @param {Object} [opts]
3426
+ */
3427
+ publish: function (el, id, opts){
3428
+ opts = opts || {};
3429
+ el.innerHTML = _makeFlashHTML({
3430
+ id: id
3431
+ , src: _getUrl(api.flashUrl, 'r=' + api.version)
3432
+ // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
3433
+ , wmode: opts.camera ? '' : 'transparent'
3434
+ , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
3435
+ + '&flashId='+ id
3436
+ + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
3437
+ + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
3438
+ + '&timeout='+api.flashAbortTimeout
3439
+ + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
3440
+ + '&debug='+(api.debug?"1":"")
3441
+ }, opts);
3442
+ },
3443
+
3444
+
3445
+ ready: function (){
3446
+ api.log('FlashAPI.state: ready');
3447
+
3448
+ flash.ready = api.F;
3449
+ flash.isReady = true;
3450
+ flash.patch();
3451
+ flash.patchCamera && flash.patchCamera();
3452
+ api.event.on(document, 'mouseover', flash.mouseover);
3453
+ api.event.on(document, 'click', function (evt){
3454
+ if( flash.mouseover(evt) ){
3455
+ evt.preventDefault
3456
+ ? evt.preventDefault()
3457
+ : (evt.returnValue = true)
3458
+ ;
3459
+ }
3460
+ });
3461
+ },
3462
+
3463
+
3464
+ getEl: function (){
3465
+ return document.getElementById('_'+_attr);
3466
+ },
3467
+
3468
+
3469
+ getWrapper: function (node){
3470
+ do {
3471
+ if( /js-fileapi-wrapper/.test(node.className) ){
3472
+ return node;
3473
+ }
3474
+ }
3475
+ while( (node = node.parentNode) && (node !== document.body) );
3476
+ },
3477
+
3478
+
3479
+ mouseover: function (evt){
3480
+ var target = api.event.fix(evt).target;
3481
+
3482
+ if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
3483
+ var
3484
+ state = target.getAttribute(_attr)
3485
+ , wrapper = flash.getWrapper(target)
3486
+ ;
3487
+
3488
+ if( api.multiFlash ){
3489
+ // check state:
3490
+ // i — published
3491
+ // i — initialization
3492
+ // r — ready
3493
+ if( state == 'i' || state == 'r' ){
3494
+ // publish fail
3495
+ return false;
3496
+ }
3497
+ else if( state != 'p' ){
3498
+ // set "init" state
3499
+ target.setAttribute(_attr, 'i');
3500
+
3501
+ var dummy = document.createElement('div');
3502
+
3503
+ if( !wrapper ){
3504
+ api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
3505
+ return;
3506
+ }
3507
+
3508
+ _css(dummy, {
3509
+ top: 0
3510
+ , left: 0
3511
+ , width: target.offsetWidth
3512
+ , height: target.offsetHeight
3513
+ , zIndex: 1e6+'' // set max zIndex
3514
+ , position: 'absolute'
3515
+ });
3516
+
3517
+ wrapper.appendChild(dummy);
3518
+ flash.publish(dummy, api.uid());
3519
+
3520
+ // set "publish" state
3521
+ target.setAttribute(_attr, 'p');
3522
+ }
3523
+
3524
+ return true;
3525
+ }
3526
+ else if( wrapper ){
3527
+ // Use one flash element
3528
+ var box = _getDimensions(wrapper);
3529
+
3530
+ _css(flash.getEl(), box);
3531
+
3532
+ // Set current input
3533
+ flash.curInp = target;
3534
+ }
3535
+ }
3536
+ else if( !/object|embed/i.test(target.nodeName) ){
3537
+ _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
3538
+ }
3539
+ },
3540
+
3541
+ onEvent: function (evt){
3542
+ var type = evt.type;
3543
+
3544
+ if( type == 'ready' ){
3545
+ try {
3546
+ // set "ready" state
3547
+ flash.getInput(evt.flashId).setAttribute(_attr, 'r');
3548
+ } catch (e){
3549
+ }
3550
+
3551
+ flash.ready();
3552
+ setTimeout(function (){ flash.mouseenter(evt); }, 50);
3553
+ return true;
3554
+ }
3555
+ else if( type === 'ping' ){
3556
+ api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
3557
+ }
3558
+ else if( type === 'log' ){
3559
+ api.log('(flash -> js).log:', evt.target);
3560
+ }
3561
+ else if( type in flash ){
3562
+ setTimeout(function (){
3563
+ api.log('FlashAPI.event.'+evt.type+':', evt);
3564
+ flash[type](evt);
3565
+ }, 1);
3566
+ }
3567
+ },
3568
+
3569
+
3570
+ mouseenter: function (evt){
3571
+ var node = flash.getInput(evt.flashId);
3572
+
3573
+ if( node ){
3574
+ // Set multiple mode
3575
+ flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
3576
+
3577
+
3578
+ // Set files filter
3579
+ var accept = [], exts = {};
3580
+
3581
+ _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
3582
+ api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
3583
+ exts[ext] = 1;
3584
+ });
3585
+ });
3586
+
3587
+ _each(exts, function (i, ext){
3588
+ accept.push( ext );
3589
+ });
3590
+
3591
+ flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
3592
+ }
3593
+ },
3594
+
3595
+
3596
+ get: function (id){
3597
+ return document[id] || window[id] || document.embeds[id];
3598
+ },
3599
+
3600
+
3601
+ getInput: function (id){
3602
+ if( api.multiFlash ){
3603
+ try {
3604
+ var node = flash.getWrapper(flash.get(id));
3605
+ if( node ){
3606
+ return node.getElementsByTagName('input')[0];
3607
+ }
3608
+ } catch (e){
3609
+ api.log('[err] Can not find "input" by flashId:', id, e);
3610
+ }
3611
+ } else {
3612
+ return flash.curInp;
3613
+ }
3614
+ },
3615
+
3616
+
3617
+ select: function (evt){
3618
+ var
3619
+ inp = flash.getInput(evt.flashId)
3620
+ , uid = api.uid(inp)
3621
+ , files = evt.target.files
3622
+ , event
3623
+ ;
3624
+
3625
+ _each(files, function (file){
3626
+ api.checkFileObj(file);
3627
+ });
3628
+
3629
+ _files[uid] = files;
3630
+
3631
+ if( document.createEvent ){
3632
+ event = document.createEvent('Event');
3633
+ event.files = files;
3634
+ event.initEvent('change', true, true);
3635
+ inp.dispatchEvent(event);
3636
+ }
3637
+ else if( jQuery ){
3638
+ jQuery(inp).trigger({ type: 'change', files: files });
3639
+ }
3640
+ else {
3641
+ event = document.createEventObject();
3642
+ event.files = files;
3643
+ inp.fireEvent('onchange', event);
3644
+ }
3645
+ },
3646
+
3647
+
3648
+ cmd: function (id, name, data, last){
3649
+ try {
3650
+ api.log('(js -> flash).'+name+':', data);
3651
+ return flash.get(id.flashId || id).cmd(name, data);
3652
+ } catch (e){
3653
+ api.log('(js -> flash).onError:', e);
3654
+ if( !last ){
3655
+ // try again
3656
+ setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
3657
+ }
3658
+ }
3659
+ },
3660
+
3661
+
3662
+ patch: function (){
3663
+ api.flashEngine = true;
3664
+
3665
+ // FileAPI
3666
+ _inherit(api, {
3667
+ getFiles: function (input, filter, callback){
3668
+ if( callback ){
3669
+ api.filterFiles(api.getFiles(input), filter, callback);
3670
+ return null;
3671
+ }
3672
+
3673
+ var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
3674
+
3675
+
3676
+ if( !files ){
3677
+ // Файлов нету, вызываем родительский метод
3678
+ return this.parent.apply(this, arguments);
3679
+ }
3680
+
3681
+
3682
+ if( filter ){
3683
+ filter = api.getFilesFilter(filter);
3684
+ files = api.filter(files, function (file){ return filter.test(file.name); });
3685
+ }
3686
+
3687
+ return files;
3688
+ },
3689
+
3690
+
3691
+ getInfo: function (file, fn){
3692
+ if( _isHtmlFile(file) ){
3693
+ this.parent.apply(this, arguments);
3694
+ }
3695
+ else if( file.isShot ){
3696
+ fn(null, file.info = {
3697
+ width: file.width,
3698
+ height: file.height
3699
+ });
3700
+ }
3701
+ else {
3702
+ if( !file.__info ){
3703
+ var defer = file.__info = api.defer();
3704
+
3705
+ flash.cmd(file, 'getFileInfo', {
3706
+ id: file.id
3707
+ , callback: _wrap(function _(err, info){
3708
+ _unwrap(_);
3709
+ defer.resolve(err, file.info = info);
3710
+ })
3711
+ });
3712
+ }
3713
+
3714
+ file.__info.then(fn);
3715
+ }
3716
+ }
3717
+ });
3718
+
3719
+
3720
+ // FileAPI.Image
3721
+ api.support.transform = true;
3722
+ api.Image && _inherit(api.Image.prototype, {
3723
+ get: function (fn, scaleMode){
3724
+ this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
3725
+ return this.parent(fn);
3726
+ },
3727
+
3728
+ _load: function (file, fn){
3729
+ api.log('FlashAPI.Image._load:', file);
3730
+
3731
+ if( _isHtmlFile(file) ){
3732
+ this.parent.apply(this, arguments);
3733
+ }
3734
+ else {
3735
+ var _this = this;
3736
+ api.getInfo(file, function (err){
3737
+ fn.call(_this, err, file);
3738
+ });
3739
+ }
3740
+ },
3741
+
3742
+ _apply: function (file, fn){
3743
+ api.log('FlashAPI.Image._apply:', file);
3744
+
3745
+ if( _isHtmlFile(file) ){
3746
+ this.parent.apply(this, arguments);
3747
+ }
3748
+ else {
3749
+ var m = this.getMatrix(file.info), doneFn = fn;
3750
+
3751
+ flash.cmd(file, 'imageTransform', {
3752
+ id: file.id
3753
+ , matrix: m
3754
+ , callback: _wrap(function _(err, base64){
3755
+ api.log('FlashAPI.Image._apply.callback:', err);
3756
+ _unwrap(_);
3757
+
3758
+ if( err ){
3759
+ doneFn(err);
3760
+ }
3761
+ else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
3762
+ _makeFlashImage({
3763
+ width: (m.deg % 180) ? m.dh : m.dw
3764
+ , height: (m.deg % 180) ? m.dw : m.dh
3765
+ , scale: m.scaleMode
3766
+ }, base64, doneFn);
3767
+ }
3768
+ else {
3769
+ if( m.filter ){
3770
+ doneFn = function (err, img){
3771
+ if( err ){
3772
+ fn(err);
3773
+ }
3774
+ else {
3775
+ api.Image.applyFilter(img, m.filter, function (){
3776
+ fn(err, this.canvas);
3777
+ });
3778
+ }
3779
+ };
3780
+ }
3781
+
3782
+ api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
3783
+ }
3784
+ })
3785
+ });
3786
+ }
3787
+ },
3788
+
3789
+ toData: function (fn){
3790
+ var
3791
+ file = this.file
3792
+ , info = file.info
3793
+ , matrix = this.getMatrix(info)
3794
+ ;
3795
+ api.log('FlashAPI.Image.toData');
3796
+
3797
+ if( _isHtmlFile(file) ){
3798
+ this.parent.apply(this, arguments);
3799
+ }
3800
+ else {
3801
+ if( matrix.deg == 'auto' ){
3802
+ matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
3803
+ }
3804
+
3805
+ fn.call(this, !file.info, {
3806
+ id: file.id
3807
+ , flashId: file.flashId
3808
+ , name: file.name
3809
+ , type: file.type
3810
+ , matrix: matrix
3811
+ });
3812
+ }
3813
+ }
3814
+ });
3815
+
3816
+
3817
+ api.Image && _inherit(api.Image, {
3818
+ fromDataURL: function (dataURL, size, callback){
3819
+ if( !api.support.dataURI || dataURL.length > 3e4 ){
3820
+ _makeFlashImage(
3821
+ api.extend({ scale: 'exactFit' }, size)
3822
+ , dataURL.replace(/^data:[^,]+,/, '')
3823
+ , function (err, el){ callback(el); }
3824
+ );
3825
+ }
3826
+ else {
3827
+ this.parent(dataURL, size, callback);
3828
+ }
3829
+ }
3830
+ });
3831
+
3832
+ // FileAPI.Form
3833
+ _inherit(api.Form.prototype, {
3834
+ toData: function (fn){
3835
+ var items = this.items, i = items.length;
3836
+
3837
+ for( ; i--; ){
3838
+ if( items[i].file && _isHtmlFile(items[i].blob) ){
3839
+ return this.parent.apply(this, arguments);
3840
+ }
3841
+ }
3842
+
3843
+ api.log('FlashAPI.Form.toData');
3844
+ fn(items);
3845
+ }
3846
+ });
3847
+
3848
+
3849
+ // FileAPI.XHR
3850
+ _inherit(api.XHR.prototype, {
3851
+ _send: function (options, formData){
3852
+ if(
3853
+ formData.nodeName
3854
+ || formData.append && api.support.html5
3855
+ || api.isArray(formData) && (typeof formData[0] === 'string')
3856
+ ){
3857
+ // HTML5, Multipart or IFrame
3858
+ return this.parent.apply(this, arguments);
3859
+ }
3860
+
3861
+
3862
+ var
3863
+ data = {}
3864
+ , files = {}
3865
+ , _this = this
3866
+ , flashId
3867
+ , fileId
3868
+ ;
3869
+
3870
+ _each(formData, function (item){
3871
+ if( item.file ){
3872
+ files[item.name] = item = _getFileDescr(item.blob);
3873
+ fileId = item.id;
3874
+ flashId = item.flashId;
3875
+ }
3876
+ else {
3877
+ data[item.name] = item.blob;
3878
+ }
3879
+ });
3880
+
3881
+ if( !fileId ){
3882
+ flashId = _attr;
3883
+ }
3884
+
3885
+ if( !flashId ){
3886
+ api.log('[err] FlashAPI._send: flashId -- undefined');
3887
+ return this.parent.apply(this, arguments);
3888
+ }
3889
+ else {
3890
+ api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
3891
+ }
3892
+
3893
+ _this.xhr = {
3894
+ headers: {},
3895
+ abort: function (){ flash.cmd(flashId, 'abort', { id: fileId }); },
3896
+ getResponseHeader: function (name){ return this.headers[name]; },
3897
+ getAllResponseHeaders: function (){ return this.headers; }
3898
+ };
3899
+
3900
+ var queue = api.queue(function (){
3901
+ flash.cmd(flashId, 'upload', {
3902
+ url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
3903
+ , data: data
3904
+ , files: fileId ? files : null
3905
+ , headers: options.headers || {}
3906
+ , callback: _wrap(function upload(evt){
3907
+ var type = evt.type, result = evt.result;
3908
+
3909
+ api.log('FlashAPI.upload.'+type);
3910
+
3911
+ if( type == 'progress' ){
3912
+ evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
3913
+ evt.lengthComputable = true;
3914
+ options.progress(evt);
3915
+ }
3916
+ else if( type == 'complete' ){
3917
+ _unwrap(upload);
3918
+
3919
+ if( typeof result == 'string' ){
3920
+ _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
3921
+ }
3922
+
3923
+ _this.end(evt.status || 200);
3924
+ }
3925
+ else if( type == 'abort' || type == 'error' ){
3926
+ _this.end(evt.status || 0, evt.message);
3927
+ _unwrap(upload);
3928
+ }
3929
+ })
3930
+ });
3931
+ });
3932
+
3933
+
3934
+ // #2174: FileReference.load() call while FileReference.upload() or vice versa
3935
+ _each(files, function (file){
3936
+ queue.inc();
3937
+ api.getInfo(file, queue.next);
3938
+ });
3939
+
3940
+ queue.check();
3941
+ }
3942
+ });
3943
+ }
3944
+ }
3945
+ ;
3946
+
3947
+
3948
+ function _makeFlashHTML(opts){
3949
+ return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
3950
+ + '<param name="movie" value="#src#" />'
3951
+ + '<param name="flashvars" value="#flashvars#" />'
3952
+ + '<param name="swliveconnect" value="true" />'
3953
+ + '<param name="allowscriptaccess" value="always" />'
3954
+ + '<param name="allownetworking" value="all" />'
3955
+ + '<param name="menu" value="false" />'
3956
+ + '<param name="wmode" value="#wmode#" />'
3957
+ + '<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>'
3958
+ + '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
3959
+ ;
3960
+ }
3961
+
3962
+
3963
+ function _css(el, css){
3964
+ if( el && el.style ){
3965
+ var key, val;
3966
+ for( key in css ){
3967
+ val = css[key];
3968
+ if( typeof val == 'number' ){
3969
+ val += 'px';
3970
+ }
3971
+ try { el.style[key] = val; } catch (e) {}
3972
+ }
3973
+ }
3974
+ }
3975
+
3976
+
3977
+ function _inherit(obj, methods){
3978
+ _each(methods, function (fn, name){
3979
+ var prev = obj[name];
3980
+ obj[name] = function (){
3981
+ this.parent = prev;
3982
+ return fn.apply(this, arguments);
3983
+ };
3984
+ });
3985
+ }
3986
+
3987
+ function _isHtmlFile(file){
3988
+ return file && !file.flashId;
3989
+ }
3990
+
3991
+ function _wrap(fn){
3992
+ var id = fn.wid = api.uid();
3993
+ flash._fn[id] = fn;
3994
+ return 'FileAPI.Flash._fn.'+id;
3995
+ }
3996
+
3997
+
3998
+ function _unwrap(fn){
3999
+ try {
4000
+ flash._fn[fn.wid] = null;
4001
+ delete flash._fn[fn.wid];
4002
+ }
4003
+ catch(e){}
4004
+ }
4005
+
4006
+
4007
+ function _getUrl(url, params){
4008
+ if( !_rhttp.test(url) ){
4009
+ if( /^\.\//.test(url) || '/' != url.charAt(0) ){
4010
+ var path = location.pathname;
4011
+ path = path.substr(0, path.lastIndexOf('/'));
4012
+ url = (path +'/'+ url).replace('/./', '/');
4013
+ }
4014
+
4015
+ if( '//' != url.substr(0, 2) ){
4016
+ url = '//' + location.host + url;
4017
+ }
4018
+
4019
+ if( !_rhttp.test(url) ){
4020
+ url = location.protocol + url;
4021
+ }
4022
+ }
4023
+
4024
+ if( params ){
4025
+ url += (/\?/.test(url) ? '&' : '?') + params;
4026
+ }
4027
+
4028
+ return url;
4029
+ }
4030
+
4031
+
4032
+ function _makeFlashImage(opts, base64, fn){
4033
+ var
4034
+ key
4035
+ , flashId = api.uid()
4036
+ , el = document.createElement('div')
4037
+ , attempts = 10
4038
+ ;
4039
+
4040
+ for( key in opts ){
4041
+ el.setAttribute(key, opts[key]);
4042
+ el[key] = opts[key];
4043
+ }
4044
+
4045
+ _css(el, opts);
4046
+
4047
+ opts.width = '100%';
4048
+ opts.height = '100%';
4049
+
4050
+ el.innerHTML = _makeFlashHTML(api.extend({
4051
+ id: flashId
4052
+ , src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
4053
+ , wmode: 'opaque'
4054
+ , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
4055
+ _unwrap(_);
4056
+ if( --attempts > 0 ){
4057
+ _setImage();
4058
+ }
4059
+ return true;
4060
+ })
4061
+ }, opts));
4062
+
4063
+ function _setImage(){
4064
+ try {
4065
+ // Get flash-object by id
4066
+ var img = flash.get(flashId);
4067
+ img.setImage(base64);
4068
+ } catch (e){
4069
+ api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
4070
+ }
4071
+ }
4072
+
4073
+ fn(false, el);
4074
+ el = null;
4075
+ }
4076
+
4077
+
4078
+ function _getFileDescr(file){
4079
+ return {
4080
+ id: file.id
4081
+ , name: file.name
4082
+ , matrix: file.matrix
4083
+ , flashId: file.flashId
4084
+ };
4085
+ }
4086
+
4087
+
4088
+ function _getDimensions(el){
4089
+ var
4090
+ box = el.getBoundingClientRect()
4091
+ , body = document.body
4092
+ , docEl = (el && el.ownerDocument).documentElement
4093
+ ;
4094
+
4095
+ return {
4096
+ top: box.top + (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || body.clientTop || 0)
4097
+ , left: box.left + (window.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || body.clientLeft || 0)
4098
+ , width: box.right - box.left
4099
+ , height: box.bottom - box.top
4100
+ };
4101
+ }
4102
+
4103
+ // @export
4104
+ api.Flash = flash;
4105
+
4106
+
4107
+ // Check dataURI support
4108
+ api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
4109
+ api.support.dataURI = !(img.width != 1 || img.height != 1);
4110
+ flash.init();
4111
+ });
4112
+ })();
4113
+ })(window, window.jQuery, FileAPI);
4114
+
4115
+ /**
4116
+ * FileAPI fallback to Flash
4117
+ *
4118
+ * @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
4119
+ */
4120
+
4121
+ /*global window, FileAPI */
4122
+ (function (window, jQuery, api) {
4123
+ "use strict";
4124
+
4125
+ var _each = api.each,
4126
+ _cameraQueue = [];
4127
+
4128
+
4129
+ if (api.support.flash && (api.media && !api.support.media)) {
4130
+ (function () {
4131
+
4132
+ function _wrap(fn) {
4133
+ var id = fn.wid = api.uid();
4134
+ api.Flash._fn[id] = fn;
4135
+ return 'FileAPI.Flash._fn.' + id;
4136
+ }
4137
+
4138
+
4139
+ function _unwrap(fn) {
4140
+ try {
4141
+ api.Flash._fn[fn.wid] = null;
4142
+ delete api.Flash._fn[fn.wid];
4143
+ } catch (e) {
4144
+ }
4145
+ }
4146
+
4147
+ var flash = api.Flash;
4148
+ api.extend(api.Flash, {
4149
+
4150
+ patchCamera: function () {
4151
+ api.Camera.fallback = function (el, options, callback) {
4152
+ var camId = api.uid();
4153
+ api.log('FlashAPI.Camera.publish: ' + camId);
4154
+ flash.publish(el, camId, api.extend(options, {
4155
+ camera: true,
4156
+ onEvent: _wrap(function _(evt) {
4157
+ if (evt.type === 'camera') {
4158
+ _unwrap(_);
4159
+
4160
+ if (evt.error) {
4161
+ api.log('FlashAPI.Camera.publish.error: ' + evt.error);
4162
+ callback(evt.error);
4163
+ } else {
4164
+ api.log('FlashAPI.Camera.publish.success: ' + camId);
4165
+ callback(null);
4166
+ }
4167
+ }
4168
+ })
4169
+ }));
4170
+ };
4171
+ // Run
4172
+ _each(_cameraQueue, function (args) {
4173
+ api.Camera.fallback.apply(api.Camera, args);
4174
+ });
4175
+ _cameraQueue = [];
4176
+
4177
+
4178
+ // FileAPI.Camera:proto
4179
+ api.extend(api.Camera.prototype, {
4180
+ _id: function () {
4181
+ return this.video.id;
4182
+ },
4183
+
4184
+ start: function (callback) {
4185
+ var _this = this;
4186
+ flash.cmd(this._id(), 'camera.on', {
4187
+ callback: _wrap(function _(evt) {
4188
+ _unwrap(_);
4189
+
4190
+ if (evt.error) {
4191
+ api.log('FlashAPI.camera.on.error: ' + evt.error);
4192
+ callback(evt.error, _this);
4193
+ } else {
4194
+ api.log('FlashAPI.camera.on.success: ' + _this._id());
4195
+ _this._active = true;
4196
+ callback(null, _this);
4197
+ }
4198
+ })
4199
+ });
4200
+ },
4201
+
4202
+ stop: function () {
4203
+ this._active = false;
4204
+ flash.cmd(this._id(), 'camera.off');
4205
+ },
4206
+
4207
+ shot: function () {
4208
+ api.log('FlashAPI.Camera.shot:', this._id());
4209
+
4210
+ var shot = api.Flash.cmd(this._id(), 'shot', {});
4211
+ shot.type = 'image/png';
4212
+ shot.flashId = this._id();
4213
+ shot.isShot = true;
4214
+
4215
+ return new api.Camera.Shot(shot);
4216
+ }
4217
+ });
4218
+ }
4219
+ });
4220
+
4221
+ api.Camera.fallback = function () {
4222
+ _cameraQueue.push(arguments);
4223
+ };
4224
+
4225
+ }());
4226
+ }
4227
+ }(window, window.jQuery, FileAPI));
4228
+ if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }