电子档案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4248 lines
100 KiB

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