promethee 1.3.26 → 1.4.0

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