/*! FileAPI 2.1.0 - BSD | git://github.com/mailru/FileAPI.git
|
* 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.
|
*/
|
|
/*
|
* JavaScript Canvas to Blob 2.0.5
|
* https://github.com/blueimp/JavaScript-Canvas-to-Blob
|
*
|
* Copyright 2012, Sebastian Tschan
|
* https://blueimp.net
|
*
|
* Licensed under the MIT license:
|
* http://www.opensource.org/licenses/MIT
|
*
|
* Based on stackoverflow user Stoive's code snippet:
|
* http://stackoverflow.com/q/4998908
|
*/
|
|
/*jslint nomen: true, regexp: true */
|
/*global window, atob, Blob, ArrayBuffer, Uint8Array */
|
|
(function (window) {
|
'use strict';
|
var CanvasPrototype = window.HTMLCanvasElement &&
|
window.HTMLCanvasElement.prototype,
|
hasBlobConstructor = window.Blob && (function () {
|
try {
|
return Boolean(new Blob());
|
} catch (e) {
|
return false;
|
}
|
}()),
|
hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
|
(function () {
|
try {
|
return new Blob([new Uint8Array(100)]).size === 100;
|
} catch (e) {
|
return false;
|
}
|
}()),
|
BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
|
window.MozBlobBuilder || window.MSBlobBuilder,
|
dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
|
window.ArrayBuffer && window.Uint8Array && function (dataURI) {
|
var byteString,
|
arrayBuffer,
|
intArray,
|
i,
|
mimeString,
|
bb;
|
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
|
// Convert base64 to raw binary data held in a string:
|
byteString = atob(dataURI.split(',')[1]);
|
} else {
|
// Convert base64/URLEncoded data component to raw binary data:
|
byteString = decodeURIComponent(dataURI.split(',')[1]);
|
}
|
// Write the bytes of the string to an ArrayBuffer:
|
arrayBuffer = new ArrayBuffer(byteString.length);
|
intArray = new Uint8Array(arrayBuffer);
|
for (i = 0; i < byteString.length; i += 1) {
|
intArray[i] = byteString.charCodeAt(i);
|
}
|
// Separate out the mime component:
|
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
// Write the ArrayBuffer (or ArrayBufferView) to a blob:
|
if (hasBlobConstructor) {
|
return new Blob(
|
[hasArrayBufferViewSupport ? intArray : arrayBuffer],
|
{type: mimeString}
|
);
|
}
|
bb = new BlobBuilder();
|
bb.append(arrayBuffer);
|
return bb.getBlob(mimeString);
|
};
|
if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
|
if (CanvasPrototype.mozGetAsFile) {
|
CanvasPrototype.toBlob = function (callback, type, quality) {
|
if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
|
callback(dataURLtoBlob(this.toDataURL(type, quality)));
|
} else {
|
callback(this.mozGetAsFile('blob', type));
|
}
|
};
|
} else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
|
CanvasPrototype.toBlob = function (callback, type, quality) {
|
callback(dataURLtoBlob(this.toDataURL(type, quality)));
|
};
|
}
|
}
|
window.dataURLtoBlob = dataURLtoBlob;
|
})(window);
|
|
/*jslint evil: true */
|
/*global window, URL, webkitURL, ActiveXObject */
|
|
(function (window, undef){
|
'use strict';
|
|
var
|
gid = 1,
|
noop = function (){},
|
|
document = window.document,
|
doctype = document.doctype || {},
|
navigator = window.navigator,
|
userAgent = window.navigator.userAgent,
|
|
// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
|
apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
|
|
Blob = window.Blob,
|
File = window.File,
|
FileReader = window.FileReader,
|
FormData = window.FormData,
|
|
jQuery = window.jQuery,
|
XMLHttpRequest = window.XMLHttpRequest,
|
|
html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
|
&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
|
|
cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
|
|
chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
|
|
// https://github.com/blueimp/JavaScript-Canvas-to-Blob
|
dataURLtoBlob = window.dataURLtoBlob,
|
|
_rimg = /img/i,
|
_rcanvas = /canvas/i,
|
_rimgcanvas = /img|canvas/i,
|
_rinput = /input/i,
|
_rdata = /^data:[^,]+,/,
|
|
Math = window.Math,
|
setTimeout = window.setTimeout,
|
clearTimeout = window.clearTimeout,
|
|
_SIZE_CONST = function (pow){
|
pow = new window.Number(Math.pow(1024, pow));
|
pow.from = function (sz){ return Math.round(sz * this); };
|
return pow;
|
},
|
|
_elEvents = {}, // element event listeners
|
_infoReader = [], // list of file info processors
|
|
_readerEvents = 'abort progress error load loadend',
|
_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
|
|
currentTarget = 'currentTarget', // for minimize
|
preventDefault = 'preventDefault', // and this too
|
|
_createElement = function (name){
|
return document.createElement(name);
|
},
|
|
_isArray = function (ar) {
|
return ar && ('length' in ar);
|
},
|
|
/**
|
* Iterate over a object or array
|
*/
|
_each = function (obj, fn, ctx){
|
if( obj ){
|
if( _isArray(obj) ){
|
for( var i = 0, n = obj.length; i < n; i++ ){
|
if( i in obj ){
|
fn.call(ctx, obj[i], i, obj);
|
}
|
}
|
}
|
else {
|
for( var key in obj ){
|
if( obj.hasOwnProperty(key) ){
|
fn.call(ctx, obj[key], key, obj);
|
}
|
}
|
}
|
}
|
},
|
|
/**
|
* Search for a specified value within an array and return its index (or -1 if not found).
|
*/
|
_indexOf = function (arr, el){
|
var idx = -1, i = arr && arr.length;
|
while( i-- ){
|
if( arr[i] === el ){
|
idx = i;
|
break;
|
}
|
}
|
return idx;
|
},
|
|
/**
|
* Merge the contents of two or more objects together into the first object
|
*/
|
_extend = function (dst){
|
var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
|
for( ; i < args.length; i++ ){
|
_each(args[i], _ext);
|
}
|
return dst;
|
},
|
|
/**
|
* Add event listener
|
*/
|
_on = function (el, type, fn){
|
if( el ){
|
var uid = api.uid(el);
|
|
if( !_elEvents[uid] ){
|
_elEvents[uid] = {};
|
}
|
|
var isFileReader = (FileReader && el) && (el instanceof FileReader);
|
_each(type.split(/\s+/), function (type){
|
if( jQuery && !isFileReader){
|
jQuery.event.add(el, type, fn);
|
} else {
|
if( !_elEvents[uid][type] ){
|
_elEvents[uid][type] = [];
|
}
|
|
_elEvents[uid][type].push(fn);
|
|
if( el.addEventListener ){ el.addEventListener(type, fn, false); }
|
else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
|
else { el['on'+type] = fn; }
|
}
|
});
|
}
|
},
|
|
|
/**
|
* Remove event listener
|
*/
|
_off = function (el, type, fn){
|
if( el ){
|
var uid = api.uid(el), events = _elEvents[uid] || {};
|
|
var isFileReader = (FileReader && el) && (el instanceof FileReader);
|
_each(type.split(/\s+/), function (type){
|
if( jQuery && !isFileReader){
|
jQuery.event.remove(el, type, fn);
|
}
|
else {
|
var fns = events[type] || [], i = fns.length;
|
|
while( i-- ){
|
if( fns[i] === fn ){
|
fns.splice(i, 1);
|
break;
|
}
|
}
|
|
if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
|
else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
|
else { el['on'+type] = null; }
|
}
|
});
|
}
|
},
|
|
|
_one = function(el, type, fn){
|
_on(el, type, function _(evt){
|
_off(el, type, _);
|
fn(evt);
|
});
|
},
|
|
|
_fixEvent = function (evt){
|
if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
|
if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
|
return evt;
|
},
|
|
|
_supportInputAttr = function (attr){
|
var input = _createElement('input');
|
input.setAttribute('type', "file");
|
return attr in input;
|
},
|
|
|
|
/**
|
* FileAPI (core object)
|
*/
|
api = {
|
version: '2.1.0',
|
|
cors: false,
|
html5: true,
|
media: false,
|
formData: true,
|
multiPassResize: true,
|
|
debug: false,
|
pingUrl: false,
|
multiFlash: false,
|
flashAbortTimeout: 0,
|
withCredentials: true,
|
|
staticPath: './stemapp/libs/polyfills/fileAPI/',
|
|
flashUrl: 0, // @default: './FileAPI.flash.swf'
|
flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
|
|
postNameConcat: function (name, idx){
|
return name + (idx != null ? '['+ idx +']' : '');
|
},
|
|
support: {
|
dnd: cors && ('ondrop' in _createElement('div')),
|
cors: cors,
|
html5: html5,
|
chunked: chunked,
|
dataURI: true,
|
accept: _supportInputAttr('accept'),
|
multiple: _supportInputAttr('multiple'),
|
saveAs: !!navigator.msSaveBlob,
|
download: ('download' in _createElement('a'))
|
},
|
|
ext2mime: {
|
jpg: 'image/jpeg'
|
, tif: 'image/tiff'
|
, txt: 'text/plain'
|
},
|
|
// Fallback for flash
|
accept: {
|
'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'
|
, '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'
|
, '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'
|
},
|
|
uploadRetry : 0,
|
networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
|
|
chunkSize : 0,
|
chunkUploadRetry : 0,
|
chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
|
|
KB: _SIZE_CONST(1),
|
MB: _SIZE_CONST(2),
|
GB: _SIZE_CONST(3),
|
TB: _SIZE_CONST(4),
|
|
EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
|
|
expando: 'fileapi' + (new Date).getTime(),
|
|
uid: function (obj){
|
return obj
|
? (obj[api.expando] = obj[api.expando] || api.uid())
|
: (++gid, api.expando + gid)
|
;
|
},
|
|
log: function (){
|
if( api.debug && window.console && console.log ){
|
if( console.log.apply ){
|
console.log.apply(console, arguments);
|
}
|
else {
|
console.log([].join.call(arguments, ' '));
|
}
|
}
|
},
|
|
/**
|
* Create new image
|
*
|
* @param {String} [src]
|
* @param {Function} [fn] 1. error -- boolean, 2. img -- Image element
|
* @returns {HTMLElement}
|
*/
|
newImage: function (src, fn){
|
var img = _createElement('img');
|
if( fn ){
|
api.event.one(img, 'error load', function (evt){
|
fn(evt.type == 'error', img);
|
img = null;
|
});
|
}
|
img.src = src;
|
return img;
|
},
|
|
/**
|
* Get XHR
|
* @returns {XMLHttpRequest}
|
*/
|
getXHR: function (){
|
var xhr;
|
|
if( XMLHttpRequest ){
|
xhr = new XMLHttpRequest;
|
}
|
else if( window.ActiveXObject ){
|
try {
|
xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
|
} catch (e) {
|
xhr = new ActiveXObject('Microsoft.XMLHTTP');
|
}
|
}
|
|
return xhr;
|
},
|
|
/**
|
* Creates a URL representing the object given in parameter.
|
* @param {Blob|File} blob
|
* @returns {String|Null}
|
*/
|
createURL: function (blob){
|
return apiURL ? apiURL.createObjectURL(blob) : null;
|
},
|
|
/**
|
* Releases an existing object URL which was previously
|
* created by calling FileAPI.createURL()
|
* @param {String} url
|
*/
|
revokeURL: function (url){
|
return apiURL && apiURL.revokeObjectURL(url);
|
},
|
|
isArray: _isArray,
|
|
event: {
|
on: _on
|
, off: _off
|
, one: _one
|
, fix: _fixEvent
|
},
|
|
throttle: function(fn, delay) {
|
var id, args;
|
|
return function _throttle(){
|
args = arguments;
|
|
if( !id ){
|
fn.apply(window, args);
|
id = setTimeout(function (){
|
id = 0;
|
fn.apply(window, args);
|
}, delay);
|
}
|
};
|
},
|
|
F: noop,
|
|
/**
|
* Takes a well-formed JSON string and returns the resulting JavaScript object.
|
* @param {String} str
|
* @returns {*}
|
*/
|
parseJSON: function (str){
|
var json;
|
|
try {
|
if( window.JSON && JSON.parse ){
|
json = JSON.parse(str);
|
}
|
else {
|
console.error('window.JSON is not supported.');
|
return {};
|
// json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
|
}
|
}
|
catch( err ){
|
api.log('[err] FileAPI.parseJSON: ' + err);
|
}
|
|
return json;
|
},
|
|
/**
|
* Remove the whitespace from the beginning and end of a string.
|
* @param {String} str
|
* @returns {String}
|
*/
|
trim: function (str){
|
str = String(str);
|
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
|
},
|
|
/**
|
* Simple Defer
|
* @return {Object}
|
*/
|
defer: function (){
|
var
|
doneList = []
|
, failList = []
|
, progressList = []
|
|
, _push = function (x, fn){
|
fn && (x ? doneList : failList).push(fn);
|
return this;
|
}
|
|
, _complete = function (state, args){
|
var
|
list = state ? doneList : failList
|
, i = 0, n = list.length
|
;
|
|
_push = function (x, fn){
|
(x === state) && fn.apply(this, args);
|
};
|
|
_complete = noop;
|
|
doneList =
|
failList = null;
|
|
progressList = [];
|
|
for( ; i < n; i++ ){
|
list[i] && list[i].apply(this, args);
|
}
|
}
|
|
, defer = {
|
done: function (fn){
|
_push(1, fn);
|
return this;
|
},
|
|
fail: function (fn){
|
_push(0, fn);
|
return this;
|
},
|
|
resolve: function (){
|
_complete(1, arguments);
|
return this;
|
},
|
|
reject: function (){
|
_complete(0, arguments);
|
return this;
|
},
|
|
notify: function (){
|
var i = 0, n = progressList.length;
|
for( ; i< n; i++ ){
|
progressList[i].apply(this, arguments);
|
}
|
},
|
|
progress: function (fn){
|
fn && progressList.push(fn);
|
return this;
|
},
|
|
then: function (doneFn, failFn){
|
return this.done(doneFn).fail(failFn);
|
},
|
|
always: function (fn){
|
return this.then(fn, fn);
|
},
|
|
promise: function (){
|
return this;
|
}
|
};
|
|
return defer;
|
},
|
|
queue: function (fn){
|
var
|
_idx = 0
|
, _length = 0
|
, _fail = false
|
, _end = false
|
, queue = {
|
inc: function (){
|
_length++;
|
},
|
|
next: function (){
|
_idx++;
|
setTimeout(queue.check, 0);
|
},
|
|
check: function (){
|
(_idx >= _length) && !_fail && queue.end();
|
},
|
|
isFail: function (){
|
return _fail;
|
},
|
|
fail: function (){
|
!_fail && fn(_fail = true);
|
},
|
|
end: function (){
|
if( !_end ){
|
_end = true;
|
fn();
|
}
|
}
|
}
|
;
|
|
return queue;
|
},
|
|
|
/**
|
* For each object
|
*
|
* @param {Object|Array} obj
|
* @param {Function} fn
|
* @param {*} [ctx]
|
*/
|
each: _each,
|
|
|
/**
|
* Search for a specified value within an array and return its index (or -1 if not found).
|
*
|
* @param {Array} arr
|
* @param {*} el
|
* @returns {Number}
|
*/
|
indexOf: _indexOf,
|
|
|
/**
|
* Async for
|
* @param {Array} array
|
* @param {Function} callback
|
*/
|
afor: function (array, callback){
|
var i = 0, n = array.length;
|
|
if( _isArray(array) && n-- ){
|
(function _next(){
|
callback(n != i && _next, array[i], i++);
|
})();
|
}
|
else {
|
callback(false);
|
}
|
},
|
|
|
/**
|
* Merge the contents of two or more objects together into the first object
|
*
|
* @param {Object} dst
|
* @return {Object}
|
*/
|
extend: _extend,
|
|
|
/**
|
* Is file instance
|
*
|
* @param {File} file
|
* @return {Boolean}
|
*/
|
isFile: function (file){
|
return html5 && file && (file instanceof File || file instanceof Blob);
|
},
|
|
isBlob: function (blob) {
|
return html5 && blob && (blob instanceof Blob);
|
},
|
|
/**
|
* Is canvas element
|
*
|
* @param {HTMLElement} el
|
* @return {Boolean}
|
*/
|
isCanvas: function (el){
|
return el && _rcanvas.test(el.nodeName);
|
},
|
|
|
getFilesFilter: function (filter){
|
filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
|
return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
|
},
|
|
|
/**
|
* Read as DataURL
|
*
|
* @param {File|Element} file
|
* @param {Function} fn
|
*/
|
readAsDataURL: function (file, fn){
|
if( api.isCanvas(file) ){
|
_emit(file, fn, 'load', api.toDataURL(file));
|
}
|
else {
|
_readAs(file, fn, 'DataURL');
|
}
|
},
|
|
|
/**
|
* Read as Binary string
|
*
|
* @param {File} file
|
* @param {Function} fn
|
*/
|
readAsBinaryString: function (file, fn){
|
if( _hasSupportReadAs('BinaryString') ){
|
_readAs(file, fn, 'BinaryString');
|
} else {
|
// Hello IE10!
|
api.readAsDataURL(file, function (evt){
|
if( evt.type == 'load' ){
|
try {
|
// dataURL -> binaryString
|
evt.result = api.toBinaryString(evt.result);
|
} catch (e){
|
evt.type = 'error';
|
evt.message = e.toString();
|
}
|
}
|
fn(evt);
|
});
|
}
|
},
|
|
|
/**
|
* Read as ArrayBuffer
|
*
|
* @param {File} file
|
* @param {Function} fn
|
*/
|
readAsArrayBuffer: function(file, fn){
|
_readAs(file, fn, 'ArrayBuffer');
|
},
|
|
|
/**
|
* Read as text
|
*
|
* @param {File} file
|
* @param {String} encoding
|
* @param {Function} [fn]
|
*/
|
readAsText: function(file, encoding, fn){
|
if( !fn ){
|
fn = encoding;
|
encoding = 'utf-8';
|
}
|
|
_readAs(file, fn, 'Text', encoding);
|
},
|
|
|
/**
|
* Convert image or canvas to DataURL
|
*
|
* @param {Element} el Image or Canvas element
|
* @param {String} [type] mime-type
|
* @return {String}
|
*/
|
toDataURL: function (el, type){
|
if( typeof el == 'string' ){
|
return el;
|
}
|
else if( el.toDataURL ){
|
return el.toDataURL(type || 'image/png');
|
}
|
},
|
|
|
/**
|
* Canvert string, image or canvas to binary string
|
*
|
* @param {String|Element} val
|
* @return {String}
|
*/
|
toBinaryString: function (val){
|
return window.atob(api.toDataURL(val).replace(_rdata, ''));
|
},
|
|
|
/**
|
* Read file or DataURL as ImageElement
|
*
|
* @param {File|String} file
|
* @param {Function} fn
|
* @param {Boolean} [progress]
|
*/
|
readAsImage: function (file, fn, progress){
|
if( api.isFile(file) ){
|
if( apiURL ){
|
var src = api.createURL(file);
|
if( src ){
|
api.readAsImage(src, fn, progress);
|
}
|
else {
|
_emit(file, fn, 'error');
|
}
|
}
|
else {
|
api.readAsDataURL(file, function (evt){
|
if( evt.type == 'load' ){
|
api.readAsImage(evt.result, fn, progress);
|
}
|
else if( progress || evt.type == 'error' ){
|
_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
|
}
|
});
|
}
|
}
|
// Is canvas?
|
else if( api.isCanvas(file) ){
|
_emit(file, fn, 'load', file);
|
}
|
// Is image tag?
|
else if( _rimg.test(file.nodeName) ){
|
if( file.complete ){
|
_emit(file, fn, 'load', file);
|
}
|
else {
|
var events = 'error abort load';
|
_one(file, events, function _fn(evt){
|
if( evt.type == 'load' && apiURL ){
|
api.revokeURL(file.src);
|
}
|
|
_off(file, events, _fn);
|
_emit(file, fn, evt, file);
|
});
|
}
|
}
|
else if( file.iframe ){
|
_emit(file, fn, { type: 'error', message: 'is iframe' });
|
}
|
else {
|
// Created image
|
var img = api.newImage(file.dataURL || file);
|
api.readAsImage(img, fn, progress);
|
}
|
},
|
|
|
/**
|
* Get mime type by File or name
|
* @param {Object|String} file
|
* @returns {String}
|
*/
|
getMimeType: function (file){
|
var
|
mime = file && (file.type || String(file.name || file).split('.').pop())
|
, accept = api.accept
|
, ext, type
|
;
|
|
if( !/^[^/]+\/[^/]+$/.test(mime) ){
|
for( type in accept ){
|
ext = new RegExp(accept[type].replace(/\s/g, '|'), 'i');
|
|
if( ext.test(mime) || api.ext2mime[mime] ){
|
mime = api.ext2mime[mime] || (type.split('/')[0] +'/'+ mime);
|
break;
|
}
|
}
|
}
|
|
return mime;
|
},
|
|
|
/**
|
* Get drop files
|
*
|
* @param {Event} evt
|
* @param {Function} callback
|
*/
|
getDropFiles: function (evt, callback){
|
var
|
files = []
|
, dataTransfer = _getDataTransfer(evt)
|
, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
|
, queue = api.queue(function (){ callback(files); })
|
;
|
|
_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
|
queue.inc();
|
|
try {
|
if( entrySupport ){
|
_readEntryAsFiles(item, function (err, entryFiles){
|
if( err ){
|
api.log('[err] getDropFiles:', err);
|
} else {
|
files.push.apply(files, entryFiles);
|
}
|
queue.next();
|
});
|
}
|
else {
|
_isRegularFile(item, function (yes){
|
yes && files.push(item);
|
queue.next();
|
});
|
}
|
}
|
catch( err ){
|
queue.next();
|
api.log('[err] getDropFiles: ', err);
|
}
|
});
|
|
queue.check();
|
},
|
|
|
/**
|
* Get file list
|
*
|
* @param {HTMLInputElement|Event} input
|
* @param {String|Function} [filter]
|
* @param {Function} [callback]
|
* @returns {Array|Null}
|
*/
|
getFiles: function (input, filter, callback){
|
var files = [];
|
|
if( callback ){
|
api.filterFiles(api.getFiles(input), filter, callback);
|
return null;
|
}
|
|
if( input.jquery ){
|
// jQuery object
|
input.each(function (){
|
files = files.concat(api.getFiles(this));
|
});
|
input = files;
|
files = [];
|
}
|
|
if( typeof filter == 'string' ){
|
filter = api.getFilesFilter(filter);
|
}
|
|
if( input.originalEvent ){
|
// jQuery event
|
input = _fixEvent(input.originalEvent);
|
}
|
else if( input.srcElement ){
|
// IE Event
|
input = _fixEvent(input);
|
}
|
|
|
if( input.dataTransfer ){
|
// Drag'n'Drop
|
input = input.dataTransfer;
|
}
|
else if( input.target ){
|
// Event
|
input = input.target;
|
}
|
|
if( input.files ){
|
// Input[type="file"]
|
files = input.files;
|
|
if( !html5 ){
|
// Partial support for file api
|
files[0].blob = input;
|
files[0].iframe = true;
|
}
|
}
|
else if( !html5 && isInputFile(input) ){
|
if( api.trim(input.value) ){
|
files = [_toFileObject(input.value)];
|
files[0].blob = input;
|
files[0].iframe = true;
|
}
|
}
|
else if( _isArray(input) ){
|
files = input;
|
}
|
|
return api.filter(files, function (file){ return !filter || filter.test(file.name); });
|
},
|
|
|
/**
|
* Get total files size
|
* @param {Array} files
|
* @returns {Number}
|
*/
|
getTotalSize: function (files){
|
var size = 0, i = files && files.length;
|
while( i-- ){
|
size += files[i].size;
|
}
|
return size;
|
},
|
|
|
/**
|
* Get image information
|
*
|
* @param {File} file
|
* @param {Function} fn
|
*/
|
getInfo: function (file, fn){
|
var info = {}, readers = _infoReader.concat();
|
|
if( api.isFile(file) ){
|
(function _next(){
|
var reader = readers.shift();
|
if( reader ){
|
if( reader.test(api.getMimeType(file)) ){
|
reader(file, function (err, res){
|
if( err ){
|
fn(err);
|
}
|
else {
|
_extend(info, res);
|
_next();
|
}
|
});
|
}
|
else {
|
_next();
|
}
|
}
|
else {
|
fn(false, info);
|
}
|
})();
|
}
|
else {
|
fn('not_support_info', info);
|
}
|
},
|
|
|
/**
|
* Add information reader
|
*
|
* @param {RegExp} mime
|
* @param {Function} fn
|
*/
|
addInfoReader: function (mime, fn){
|
fn.test = function (type){ return mime.test(type); };
|
_infoReader.push(fn);
|
},
|
|
|
/**
|
* Filter of array
|
*
|
* @param {Array} input
|
* @param {Function} fn
|
* @return {Array}
|
*/
|
filter: function (input, fn){
|
var result = [], i = 0, n = input.length, val;
|
|
for( ; i < n; i++ ){
|
if( i in input ){
|
val = input[i];
|
if( fn.call(val, val, i, input) ){
|
result.push(val);
|
}
|
}
|
}
|
|
return result;
|
},
|
|
|
/**
|
* Filter files
|
*
|
* @param {Array} files
|
* @param {Function} eachFn
|
* @param {Function} resultFn
|
*/
|
filterFiles: function (files, eachFn, resultFn){
|
if( files.length ){
|
// HTML5 or Flash
|
var queue = files.concat(), file, result = [], deleted = [];
|
|
(function _next(){
|
if( queue.length ){
|
file = queue.shift();
|
api.getInfo(file, function (err, info){
|
(eachFn(file, err ? false : info) ? result : deleted).push(file);
|
_next();
|
});
|
}
|
else {
|
resultFn(result, deleted);
|
}
|
})();
|
}
|
else {
|
resultFn([], files);
|
}
|
},
|
|
|
/**
|
* Upload files on server
|
*
|
* @param {String|Object} url
|
* @param {*} [files]
|
* @param {Object} [options]
|
* @returns {FileAPI.XHR}
|
*/
|
upload: function (url, files, options){
|
if( files || typeof url == 'string' ){
|
options = _extend({}, options, { url: url, files: [].concat(files) });
|
}
|
else {
|
options = url;
|
}
|
|
|
options = _extend({
|
jsonp: 'callback'
|
, prepare: noop
|
, beforeupload: noop
|
, upload: noop
|
, fileupload: noop
|
, fileprogress: noop
|
, filecomplete: noop
|
, progress: noop
|
, complete: noop
|
, pause: noop
|
, serial: true
|
, parallel: 0
|
, postName: 'files'
|
, chunkSize: api.chunkSize
|
, imageOriginal: true
|
, chunkUploadRetry: api.chunkUploadRetry
|
, uploadRetry: api.uploadRetry
|
}, options);
|
|
|
if( !options.serial ){
|
if( options.chunkSize ){
|
options.chunkSize = 0;
|
api.log('[warn] FileAPI.upload: `chunkSize > 0` is not supported, if serial == false');
|
}
|
|
if( api.flashEngine ){
|
api.log('[warn] FileAPI.upload: `serial == false` is not supported in Flash.');
|
}
|
}
|
|
|
if( options.parallel > 0 ){
|
options.serial = true;
|
|
if( api.flashEngine ){
|
api.log('[warn] FileAPI.upload: `parallel > 0` is not supported in Flash.');
|
}
|
}
|
|
|
if( options.imageAutoOrientation && !options.imageTransform ){
|
options.imageTransform = { rotate: 'auto' };
|
}
|
|
|
var
|
proxyXHR = new api.XHR(options)
|
, filesData = _getUploadFiles(options.files, options.postName)
|
, defer = api.defer()
|
|
, _this = this
|
, _total = 0
|
, _loaded = 0
|
, _active = 0 // Counter for holding the number of active queries
|
|
, _serial = html5 && options.serial
|
, _parallel = _serial && options.parallel
|
|
, _withoutFiles = !filesData.length
|
|
, _processedData = []
|
|
// Array of active uploaded files
|
, _activeFiles = proxyXHR.activeFiles = []
|
;
|
|
// calc total size
|
_each(filesData, function (data){
|
_total += data.size;
|
});
|
|
// Array of files
|
proxyXHR.files = [];
|
_each(filesData, function (data){
|
proxyXHR.files.push(data.file);
|
});
|
|
// Set upload status props
|
proxyXHR.total = _total;
|
proxyXHR.loaded = 0;
|
proxyXHR.filesLeft = filesData.length;
|
|
// emit "beforeupload" event
|
options.beforeupload(proxyXHR, options);
|
|
// Subscribe to `defer`
|
defer.progress(options.progress);
|
defer.done(options.success);
|
defer.fail(options.error);
|
|
|
// Counting the amount of bytes uploaded files
|
function _getTotalLoadedSize(){
|
var size = 0, i = _processedData.length, data;
|
while( i-- ){
|
data = _processedData[i];
|
size += data.size * (data.loaded / data.total);
|
}
|
return size;
|
}
|
|
|
// Uploading files
|
function _uploadFiles(){
|
var
|
data = filesData.splice(0, _serial ? 1 : 1e5)
|
, _file = _serial ? data[0] && data[0].file : proxyXHR.files
|
, _dataSize = api.getTotalSize(data)
|
, _fileLoaded = false
|
, _fileOptions = _simpleClone(options)
|
;
|
|
// The actual value is set in "progress"
|
data.loaded = 0;
|
data.size = data.total = _dataSize;
|
|
_processedData.push(data);
|
|
if( _withoutFiles ){
|
_file = null;
|
api.log('[warn] FileAPI.upload() — called without files');
|
}
|
|
if( (proxyXHR.statusText != 'abort' || proxyXHR.current) && (data.length || _withoutFiles) ){
|
_withoutFiles = false;
|
|
// Increase the number of active requests
|
_active++;
|
|
// Set current upload file
|
proxyXHR.currentFile = _file;
|
|
// Prepare file options
|
if (_file && options.prepare(_file, _fileOptions) === false) {
|
_uploadFiles();
|
return;
|
}
|
_fileOptions.file = _file;
|
|
_getFormData(_fileOptions, data, function (form){
|
if( !_loaded ){
|
// emit "upload" event
|
options.upload(proxyXHR, options);
|
}
|
|
var xhr = new api.XHR(_extend({}, _fileOptions, {
|
|
upload: _file ? function (){
|
// emit "fileupload" event
|
if( _serial ){
|
_activeFiles.push(_file);
|
xhr.activeFiles = _activeFiles;
|
options.fileupload(_file, xhr, _fileOptions);
|
}
|
} : noop,
|
|
progress: _file ? function (evt){
|
if( !_fileLoaded ){
|
// For ignore the double calls.
|
_fileLoaded = (evt.loaded == evt.total);
|
|
// Set actual value
|
data.total = evt.total;
|
data.loaded = Math.min(evt.loaded, evt.total);
|
|
// emit "fileprogress" event
|
(_serial || _parallel) && options.fileprogress({
|
type: 'progress'
|
, total: data.total
|
, loaded: data.loaded
|
}, _file, xhr, _fileOptions);
|
|
// emit "progress" event
|
defer.notify({
|
type: 'progress'
|
, total: _total
|
, loaded: proxyXHR.loaded = _getTotalLoadedSize()
|
}, _file, xhr, _fileOptions);
|
}
|
} : noop,
|
|
complete: function (err){
|
_each(_xhrPropsExport, function (name){
|
proxyXHR[name] = xhr[name];
|
});
|
|
if( _file ){
|
// "loaded" and "total" set in "progress".
|
data.total = (data.total || data.size);
|
data.loaded = data.total;
|
|
// emulate 100% "progress"
|
this.progress(data);
|
|
// fixed "progress" throttle
|
_fileLoaded = true;
|
|
// bytes loaded
|
proxyXHR.loaded = _loaded = _getTotalLoadedSize();
|
|
// emit "filecomplete" event
|
if( _serial ){
|
_activeFiles.splice(_indexOf(_activeFiles, _file), 1);
|
options.filecomplete(err, xhr, _file, _fileOptions);
|
}
|
}
|
|
// Decrease the number of active requests
|
_active--;
|
|
// upload next file
|
setTimeout(function (){ _uploadFiles(); }, 0);
|
}
|
})); // xhr
|
|
|
// ...
|
proxyXHR.abort = function (current){
|
if( !current ){ filesData.length = 0; }
|
this.current = current;
|
xhr.abort();
|
};
|
|
// Start upload
|
xhr.send(form);
|
});
|
}
|
else if( !_active ){
|
var err = (proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204)
|
? false
|
: (proxyXHR.statusText || 'error');
|
|
if( err ){
|
defer.reject(err, proxyXHR, options);
|
} else {
|
defer.resolve(proxyXHR, options);
|
}
|
|
options.complete(err, proxyXHR, options);
|
}
|
}
|
|
|
// Start upload on next tick
|
setTimeout(function (){
|
for( var i = 0; i < Math.max(_parallel, 1); i++ ){
|
_uploadFiles();
|
}
|
}, 0);
|
|
|
// Append more files to the existing request
|
// first - add them to the queue head/tail
|
proxyXHR.append = function (files, first) {
|
files = api._getFilesDataArray([].concat(files));
|
|
_each(files, function (data) {
|
_total += data.size;
|
proxyXHR.files.push(data.file);
|
if( first ){
|
filesData.unshift(data);
|
} else {
|
filesData.push(data);
|
}
|
});
|
|
proxyXHR.statusText = "";
|
|
if( !_active ){
|
_uploadFiles.call(_this);
|
}
|
};
|
|
|
// Removes file from queue by file reference and returns it
|
proxyXHR.remove = function (file) {
|
var i = filesData.length, _file;
|
while( i-- ){
|
if( filesData[i].file == file ){
|
_file = filesData.splice(i, 1);
|
_total -= _file.size;
|
}
|
}
|
return _file;
|
};
|
|
proxyXHR.error = defer.fail;
|
proxyXHR.success = defer.done;
|
|
return _extend(proxyXHR, defer);
|
},
|
|
|
reset: function (inp, notRemove){
|
var parent, clone;
|
|
if( jQuery ){
|
clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
|
if( !notRemove ){
|
jQuery(inp).remove();
|
}
|
} else {
|
parent = inp.parentNode;
|
clone = parent.insertBefore(inp.cloneNode(true), inp);
|
clone.value = '';
|
|
if( !notRemove ){
|
parent.removeChild(inp);
|
}
|
|
_each(_elEvents[api.uid(inp)], function (fns, type){
|
_each(fns, function (fn){
|
_off(inp, type, fn);
|
_on(clone, type, fn);
|
});
|
});
|
}
|
|
return clone;
|
},
|
|
|
/**
|
* Load a remote file
|
*
|
* @param {String} url
|
* @param {Object} [options]
|
* @return {FileAPI.XHR}
|
*/
|
load: function (url, options){
|
var
|
xhr = new api.XHR(options = _extend(options || {}, {
|
url: url
|
, type: 'GET'
|
, cache: true
|
, responseType: 'blob'
|
}))
|
, resolve = xhr.defer.resolve
|
;
|
|
xhr.defer.resolve = function (xhr, opts){
|
var blob = xhr.response;
|
return (blob && Blob) && (blob instanceof Blob)
|
? resolve(blob, xhr, opts)
|
: xhr.defer.reject('load_not_supported', xhr, opts)
|
;
|
};
|
|
_getFormData(options, [], function (formData){
|
xhr.send(formData);
|
});
|
|
return xhr;
|
},
|
|
|
/**
|
* Save file on disk
|
* @param {String|File|Blob} blob
|
* @param {String} [name]
|
* @returns {FileAPI.defer}
|
*/
|
saveAs: function (blob, name){
|
var defer = api.defer();
|
|
if( typeof blob === 'string' ){
|
if( name === undef ){
|
name = blob.split('/').pop();
|
}
|
|
api.load(blob)
|
.progress(defer.notify)
|
.done(function (blob){
|
api.saveAs(blob, name).then(defer.resolve, defer.reject);
|
})
|
.fail(defer.reject)
|
;
|
}
|
else {
|
try {
|
if( _saveAs(blob, name) ){
|
defer.resolve();
|
}
|
else {
|
defer.reject('saveAs_not_support');
|
}
|
} catch (err){
|
api.log('[err] FileAPI.saveAs: '+err.toString());
|
defer.reject(err);
|
}
|
}
|
|
return defer;
|
}
|
|
} // api
|
;
|
|
|
function _emit(target, fn, name, res, ext){
|
var evt = {
|
type: name.type || name
|
, target: target
|
, result: res
|
};
|
_extend(evt, ext);
|
fn(evt);
|
}
|
|
|
function _hasSupportReadAs(as){
|
return FileReader && !!FileReader.prototype['readAs'+as];
|
}
|
|
|
function _readAs(file, fn, as, encoding){
|
if( api.isBlob(file) && _hasSupportReadAs(as) ){
|
var Reader = new FileReader;
|
|
// Add event listener
|
_on(Reader, _readerEvents, function _fn(evt){
|
var type = evt.type;
|
if( type == 'progress' ){
|
_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
|
}
|
else if( type == 'loadend' ){
|
_off(Reader, _readerEvents, _fn);
|
Reader = null;
|
}
|
else {
|
_emit(file, fn, evt, evt.target.result);
|
}
|
});
|
|
|
try {
|
// ReadAs ...
|
if( encoding ){
|
Reader['readAs'+as](file, encoding);
|
}
|
else {
|
Reader['readAs'+as](file);
|
}
|
}
|
catch (err){
|
_emit(file, fn, 'error', undef, { error: err.toString() });
|
}
|
}
|
else {
|
_emit(file, fn, 'error', undef, { error: 'filreader_not_support_'+as });
|
}
|
}
|
|
|
function _isLikeFile(obj){
|
return obj && (File && (obj instanceof File) || obj.blob || obj.image && obj.file || obj.flashId);
|
}
|
|
|
function _toFileObject(name){
|
return {
|
name: (name + '').split(/\\|\//g).pop()
|
, type: api.getMimeType(name)
|
};
|
}
|
|
|
function _isRegularFile(file, callback){
|
// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
|
if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
|
if( FileReader ){
|
try {
|
var Reader = new FileReader();
|
|
_one(Reader, _readerEvents, function (evt){
|
var isFile = evt.type != 'error';
|
callback(isFile);
|
if( isFile ){
|
Reader.abort();
|
}
|
});
|
|
Reader.readAsDataURL(file);
|
} catch( err ){
|
callback(false);
|
}
|
}
|
else {
|
callback(null);
|
}
|
}
|
else {
|
callback(true);
|
}
|
}
|
|
|
function _getAsEntry(item){
|
var entry;
|
if( item.getAsEntry ){ entry = item.getAsEntry(); }
|
else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
|
return entry;
|
}
|
|
|
function _readEntryAsFiles(entry, callback){
|
if( !entry ){
|
// error
|
callback('invalid entry');
|
}
|
else if( entry.isFile ){
|
// Read as file
|
entry.file(function(file){
|
// success
|
file.fullPath = entry.fullPath;
|
callback(false, [file]);
|
}, function (err){
|
// error
|
callback('FileError.code: '+err.code);
|
});
|
}
|
else if( entry.isDirectory ){
|
var reader = entry.createReader(), result = [];
|
|
reader.readEntries(function(entries){
|
// success
|
api.afor(entries, function (next, entry){
|
_readEntryAsFiles(entry, function (err, files){
|
if( err ){
|
api.log(err);
|
}
|
else {
|
result = result.concat(files);
|
}
|
|
if( next ){
|
next();
|
}
|
else {
|
callback(false, result);
|
}
|
});
|
});
|
}, function (err){
|
// error
|
callback('directory_reader: ' + err);
|
});
|
}
|
else {
|
_readEntryAsFiles(_getAsEntry(entry), callback);
|
}
|
}
|
|
|
function _getUploadFiles(data, postName){
|
var files = [], oFiles = {};
|
|
// Convert `data` to `oFiles`
|
if( isInputFile(data) ){
|
var tmp = api.getFiles(data);
|
oFiles[data.name || postName] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
|
}
|
else if( _isArray(data) ){
|
if( isInputFile(data[0]) ){
|
_each(data, function (input){
|
oFiles[input.name || postName] = api.getFiles(input);
|
});
|
} else if( _isLikeFile(data[0]) ){
|
oFiles[postName] = data;
|
}
|
}
|
else if( _isLikeFile(data) ){
|
oFiles[postName] = data;
|
}
|
else {
|
// `key` - post name, `value` - file object
|
oFiles = data;
|
}
|
|
|
// Convert `oFiles` to `files`
|
_each(oFiles, function add(file, name){
|
if( _isArray(file) ){
|
_each(file, function (file){
|
add(file, name);
|
});
|
}
|
else if( _isLikeFile(file) ){
|
files.push({
|
name: name // post name
|
, file: file
|
, size: file.size
|
, total: file.size
|
, loaded: 0
|
});
|
}
|
});
|
|
return files;
|
}
|
|
|
|
function _getFormData(options, filesData, fn){
|
var
|
Form = new api.Form
|
, queue = api.queue(function (){ fn(Form); })
|
, trans = api.support.transform && options.imageTransform
|
, isOrignTrans = trans && _isOriginTransform(trans)
|
, postNameConcat = api.postNameConcat
|
;
|
|
// Add files to `Form`
|
_each(filesData, function (data, idx){
|
var
|
file = data.file
|
, name = postNameConcat(data.name, options.serial || options.chunkSize ? null : idx)
|
, filename = file.name
|
, filetype = file.type
|
;
|
|
(function _addFile(file/**Object*/){
|
if( file.image ){ // This is a FileAPI.Image
|
queue.inc();
|
|
file.toData(function (err, image){
|
// @todo: error
|
filename = filename || (new Date).getTime()+'.png';
|
|
_addFile(image);
|
queue.next();
|
});
|
}
|
else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
|
queue.inc();
|
|
if( isOrignTrans ){
|
// Convert to array for transform function
|
trans = [].concat(trans);
|
}
|
|
api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
|
if( isOrignTrans && !err ){
|
if( !dataURLtoBlob && !api.flashEngine ){
|
// Canvas.toBlob or Flash not supported, use multipart
|
Form.multipart = true;
|
}
|
|
Form.append(name, images[0], filename, trans[0].type || filetype);
|
}
|
else {
|
var addOrigin = 0;
|
|
if( !err ){
|
_each(images, function (image, idx){
|
if( !dataURLtoBlob && !api.flashEngine ){
|
Form.multipart = true;
|
}
|
|
if( !trans[idx].postName ){
|
addOrigin = 1;
|
}
|
|
Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
|
});
|
}
|
|
if( err || options.imageOriginal ){
|
Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
|
}
|
}
|
|
queue.next();
|
});
|
}
|
else if( filename !== api.expando ){
|
Form.append(name, file, filename);
|
}
|
})(file);
|
});
|
|
|
// Append data
|
_each(options.data, function add(val, name){
|
if( typeof val == 'object' ){
|
_each(val, function (v, i){
|
add(v, postNameConcat(name, i));
|
});
|
}
|
else {
|
Form.append(name, val);
|
}
|
});
|
|
queue.check();
|
}
|
|
|
function _simpleClone(obj){
|
var copy = {};
|
_each(obj, function (val, key){
|
if( val && (typeof val === 'object') && (val.nodeType == null) ){
|
val = _extend({}, val);
|
}
|
copy[key] = val;
|
});
|
return copy;
|
}
|
|
|
function isInputFile(el){
|
return _rinput.test(el && el.tagName);
|
}
|
|
|
function _getDataTransfer(evt){
|
return (evt.originalEvent || evt || '').dataTransfer || {};
|
}
|
|
|
function _isOriginTransform(trans){
|
var key;
|
for( key in trans ){
|
if( trans.hasOwnProperty(key) ){
|
if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
|
function _saveAs(blob, name){
|
var ret = false;
|
|
if( navigator.msSaveBlob ){
|
ret = navigator.msSaveBlob(blob, name);
|
}
|
else if( api.support.download ){
|
var
|
url = api.createURL(blob)
|
, body = document.body
|
, transport = _createElement('a')
|
;
|
|
if( url ){
|
transport.href = url;
|
transport.download = name || blob.name;
|
transport.style.top = '-10000px';
|
transport.style.position = 'absolute';
|
|
body.appendChild(transport);
|
transport.click();
|
body.removeChild(transport);
|
|
ret = true;
|
setTimeout(function (){ api.revokeURL(url); }, 1);
|
}
|
}
|
|
return ret;
|
}
|
|
|
// Add default image info reader
|
api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
|
if( !file.__dimensions ){
|
var defer = file.__dimensions = api.defer();
|
|
api.readAsImage(file, function (evt){
|
var img = evt.target;
|
defer.resolve(evt.type == 'load' ? false : 'error', {
|
width: img.width
|
, height: img.height
|
});
|
img.src = api.EMPTY_PNG;
|
img = null;
|
});
|
}
|
|
file.__dimensions.then(callback);
|
});
|
|
|
/**
|
* Drag'n'Drop special event
|
*
|
* @param {HTMLElement} el
|
* @param {Function} onHover
|
* @param {Function} onDrop
|
*/
|
api.event.dnd = function (el, onHover, onDrop){
|
var _id, _type;
|
|
if( !onDrop ){
|
onDrop = onHover;
|
onHover = noop;
|
}
|
|
if( FileReader ){
|
_on(el, 'dragenter dragleave dragover', function (evt){
|
var
|
types = _getDataTransfer(evt).types
|
, i = types && types.length
|
, debounceTrigger = false
|
;
|
|
while( i-- ){
|
if( ~types[i].indexOf('File') ){
|
evt[preventDefault]();
|
|
if( _type !== evt.type ){
|
_type = evt.type; // Store current type of event
|
|
if( _type != 'dragleave' ){
|
onHover.call(evt[currentTarget], true, evt);
|
}
|
|
debounceTrigger = true;
|
}
|
|
break; // exit from "while"
|
}
|
}
|
|
if( debounceTrigger ){
|
clearTimeout(_id);
|
_id = setTimeout(function (){
|
onHover.call(evt[currentTarget], _type != 'dragleave', evt);
|
}, 50);
|
}
|
});
|
|
_on(el, 'drop', function (evt){
|
evt[preventDefault]();
|
|
_type = 0;
|
onHover.call(evt[currentTarget], false, evt);
|
|
api.getDropFiles(evt, function (files){
|
onDrop.call(evt[currentTarget], files, evt);
|
});
|
});
|
}
|
else {
|
api.log("Drag'n'Drop -- not supported");
|
}
|
};
|
|
|
/**
|
* Remove drag'n'drop
|
* @param {HTMLElement} el
|
* @param {Function} onHover
|
* @param {Function} onDrop
|
*/
|
api.event.dnd.off = function (el, onHover, onDrop){
|
_off(el, 'dragenter dragleave dragover', onHover);
|
_off(el, 'drop', onDrop);
|
};
|
|
|
// Support jQuery
|
if( jQuery && !jQuery.fn.dnd ){
|
jQuery.fn.dnd = function (onHover, onDrop){
|
return this.each(function (){
|
api.event.dnd(this, onHover, onDrop);
|
});
|
};
|
|
jQuery.fn.offdnd = function (onHover, onDrop){
|
return this.each(function (){
|
api.event.dnd.off(this, onHover, onDrop);
|
});
|
};
|
}
|
|
// @export
|
window.FileAPI = _extend(api, window.FileAPI);
|
|
|
// Debug info
|
api.log('FileAPI: ' + api.version);
|
api.log('protocol: ' + window.location.protocol);
|
api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
|
|
|
// @detect 'x-ua-compatible'
|
_each(document.getElementsByTagName('meta'), function (meta){
|
if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
|
api.log('meta.http-equiv: ' + meta.getAttribute('content'));
|
}
|
});
|
|
|
// @configuration
|
if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
|
if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
|
if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
|
})(window, void 0);
|
|
/*global window, FileAPI, document */
|
|
(function (api, document, undef) {
|
'use strict';
|
|
var
|
min = Math.min,
|
round = Math.round,
|
getCanvas = function () { return document.createElement('canvas'); },
|
support = false,
|
exifOrientation = {
|
8: 270
|
, 3: 180
|
, 6: 90
|
}
|
;
|
|
try {
|
support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
|
}
|
catch (e){}
|
|
|
function Image(file){
|
if( file instanceof Image ){
|
var img = new Image(file.file);
|
api.extend(img.matrix, file.matrix);
|
return img;
|
}
|
else if( !(this instanceof Image) ){
|
return new Image(file);
|
}
|
|
this.file = file;
|
this.size = file.size || 100;
|
|
this.matrix = {
|
sx: 0,
|
sy: 0,
|
sw: 0,
|
sh: 0,
|
dx: 0,
|
dy: 0,
|
dw: 0,
|
dh: 0,
|
resize: 0, // min, max OR preview
|
deg: 0,
|
quality: 1, // jpeg quality
|
filter: 0,
|
multipass: api.multiPassResize
|
};
|
}
|
|
|
Image.prototype = {
|
image: true,
|
constructor: Image,
|
|
set: function (attrs){
|
api.extend(this.matrix, attrs);
|
return this;
|
},
|
|
crop: function (x, y, w, h){
|
if( w === undef ){
|
w = x;
|
h = y;
|
x = y = 0;
|
}
|
return this.set({ sx: x, sy: y, sw: w, sh: h || w });
|
},
|
|
resize: function (w, h, strategy){
|
if( /min|max/.test(h) ){
|
strategy = h;
|
h = w;
|
}
|
|
return this.set({ dw: w, dh: h || w, resize: strategy });
|
},
|
|
preview: function (w, h){
|
return this.resize(w, h || w, 'preview');
|
},
|
|
rotate: function (deg){
|
return this.set({ deg: deg });
|
},
|
|
filter: function (filter){
|
return this.set({ filter: filter });
|
},
|
|
overlay: function (images){
|
return this.set({ overlay: images });
|
},
|
|
clone: function (){
|
return new Image(this);
|
},
|
|
_load: function (image, fn){
|
var self = this;
|
|
if( /img|video/i.test(image.nodeName) ){
|
fn.call(self, null, image);
|
}
|
else {
|
api.readAsImage(image, function (evt){
|
fn.call(self, evt.type != 'load', evt.result);
|
});
|
}
|
},
|
|
_apply: function (image, fn){
|
var
|
canvas = getCanvas()
|
, m = this.getMatrix(image)
|
, ctx = canvas.getContext('2d')
|
, width = image.videoWidth || image.width
|
, height = image.videoHeight || image.height
|
, deg = m.deg
|
, dw = m.dw
|
, dh = m.dh
|
, w = width
|
, h = height
|
, filter = m.filter
|
, copy // canvas copy
|
, buffer = image
|
, overlay = m.overlay
|
, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
|
, renderImageToCanvas = api.renderImageToCanvas
|
;
|
|
// Normalize angle
|
deg = deg - Math.floor(deg/360)*360;
|
|
// For `renderImageToCanvas`
|
image._type = this.file.type;
|
|
|
if( m.multipass ){
|
while( min(w/dw, h/dh) > 2 ){
|
w = (w/2 + 0.5)|0;
|
h = (h/2 + 0.5)|0;
|
|
copy = getCanvas();
|
copy.width = w;
|
copy.height = h;
|
|
if( buffer !== image ){
|
renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
|
buffer = copy;
|
}
|
else {
|
buffer = copy;
|
renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
|
m.sx = m.sy = m.sw = m.sh = 0;
|
}
|
}
|
}
|
|
|
canvas.width = (deg % 180) ? dh : dw;
|
canvas.height = (deg % 180) ? dw : dh;
|
|
canvas.type = m.type;
|
canvas.quality = m.quality;
|
|
ctx.rotate(deg * Math.PI / 180);
|
renderImageToCanvas(ctx.canvas, buffer
|
, m.sx, m.sy
|
, m.sw || buffer.width
|
, m.sh || buffer.height
|
, (deg == 180 || deg == 270 ? -dw : 0)
|
, (deg == 90 || deg == 180 ? -dh : 0)
|
, dw, dh
|
);
|
dw = canvas.width;
|
dh = canvas.height;
|
|
// Apply overlay
|
overlay && api.each([].concat(overlay), function (over){
|
queue.inc();
|
// preload
|
var img = new window.Image, fn = function (){
|
var
|
x = over.x|0
|
, y = over.y|0
|
, w = over.w || img.width
|
, h = over.h || img.height
|
, rel = over.rel
|
;
|
|
// center | right | left
|
x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
|
|
// center | bottom | top
|
y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
|
|
api.event.off(img, 'error load abort', fn);
|
|
try {
|
ctx.globalAlpha = over.opacity || 1;
|
ctx.drawImage(img, x, y, w, h);
|
}
|
catch (er){}
|
|
queue.next();
|
};
|
|
api.event.on(img, 'error load abort', fn);
|
img.src = over.src;
|
|
if( img.complete ){
|
fn();
|
}
|
});
|
|
if( filter ){
|
queue.inc();
|
Image.applyFilter(canvas, filter, queue.next);
|
}
|
|
queue.check();
|
},
|
|
getMatrix: function (image){
|
var
|
m = api.extend({}, this.matrix)
|
, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
|
, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
|
, dw = m.dw = m.dw || sw
|
, dh = m.dh = m.dh || sh
|
, sf = sw/sh, df = dw/dh
|
, strategy = m.resize
|
;
|
|
if( strategy == 'preview' ){
|
if( dw != sw || dh != sh ){
|
// Make preview
|
var w, h;
|
|
if( df >= sf ){
|
w = sw;
|
h = w / df;
|
} else {
|
h = sh;
|
w = h * df;
|
}
|
|
if( w != sw || h != sh ){
|
m.sx = ~~((sw - w)/2);
|
m.sy = ~~((sh - h)/2);
|
sw = w;
|
sh = h;
|
}
|
}
|
}
|
else if( strategy ){
|
if( !(sw > dw || sh > dh) ){
|
dw = sw;
|
dh = sh;
|
}
|
else if( strategy == 'min' ){
|
dw = round(sf < df ? min(sw, dw) : dh*sf);
|
dh = round(sf < df ? dw/sf : min(sh, dh));
|
}
|
else {
|
dw = round(sf >= df ? min(sw, dw) : dh*sf);
|
dh = round(sf >= df ? dw/sf : min(sh, dh));
|
}
|
}
|
|
m.sw = sw;
|
m.sh = sh;
|
m.dw = dw;
|
m.dh = dh;
|
m.multipass = api.multiPassResize;
|
return m;
|
},
|
|
_trans: function (fn){
|
this._load(this.file, function (err, image){
|
if( err ){
|
fn(err);
|
}
|
else {
|
try {
|
this._apply(image, fn);
|
} catch (err){
|
api.log('[err] FileAPI.Image.fn._apply:', err);
|
fn(err);
|
}
|
}
|
});
|
},
|
|
|
get: function (fn){
|
if( api.support.transform ){
|
var _this = this, matrix = _this.matrix;
|
|
if( matrix.deg == 'auto' ){
|
api.getInfo(_this.file, function (err, info){
|
// rotate by exif orientation
|
matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
|
_this._trans(fn);
|
});
|
}
|
else {
|
_this._trans(fn);
|
}
|
}
|
else {
|
fn('not_support_transform');
|
}
|
|
return this;
|
},
|
|
|
toData: function (fn){
|
return this.get(fn);
|
}
|
|
};
|
|
|
Image.exifOrientation = exifOrientation;
|
|
|
Image.transform = function (file, transform, autoOrientation, fn){
|
function _transform(err, img){
|
// img -- info object
|
var
|
images = {}
|
, queue = api.queue(function (err){
|
fn(err, images);
|
})
|
;
|
|
if( !err ){
|
api.each(transform, function (params, name){
|
if( !queue.isFail() ){
|
var ImgTrans = new Image(img.nodeType ? img : file);
|
|
if( typeof params == 'function' ){
|
params(img, ImgTrans);
|
}
|
else if( params.width ){
|
ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
|
}
|
else {
|
if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
|
ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
|
}
|
}
|
|
if( params.crop ){
|
var crop = params.crop;
|
ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
|
}
|
|
if( params.rotate === undef && autoOrientation ){
|
params.rotate = 'auto';
|
}
|
|
ImgTrans.set({
|
deg: params.rotate
|
, type: params.type || file.type || 'image/png'
|
, quality: params.quality || 1
|
, overlay: params.overlay
|
, filter: params.filter
|
});
|
|
queue.inc();
|
ImgTrans.toData(function (err, image){
|
if( err ){
|
queue.fail();
|
}
|
else {
|
images[name] = image;
|
queue.next();
|
}
|
});
|
}
|
});
|
}
|
else {
|
queue.fail();
|
}
|
}
|
|
|
// @todo: Оло-ло, нужно рефакторить это место
|
if( file.width ){
|
_transform(false, file);
|
} else {
|
api.getInfo(file, _transform);
|
}
|
};
|
|
|
// @const
|
api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
|
api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
|
Image[x+'_'+y] = i*3 + j;
|
Image[y+'_'+x] = i*3 + j;
|
});
|
});
|
|
|
/**
|
* Trabsform element to canvas
|
*
|
* @param {Image|HTMLVideoElement} el
|
* @returns {Canvas}
|
*/
|
Image.toCanvas = function(el){
|
var canvas = document.createElement('canvas');
|
canvas.width = el.videoWidth || el.width;
|
canvas.height = el.videoHeight || el.height;
|
canvas.getContext('2d').drawImage(el, 0, 0);
|
return canvas;
|
};
|
|
|
/**
|
* Create image from DataURL
|
* @param {String} dataURL
|
* @param {Object} size
|
* @param {Function} callback
|
*/
|
Image.fromDataURL = function (dataURL, size, callback){
|
var img = api.newImage(dataURL);
|
api.extend(img, size);
|
callback(img);
|
};
|
|
|
/**
|
* Apply filter (caman.js)
|
*
|
* @param {Canvas|Image} canvas
|
* @param {String|Function} filter
|
* @param {Function} doneFn
|
*/
|
Image.applyFilter = function (canvas, filter, doneFn){
|
if( typeof filter == 'function' ){
|
filter(canvas, doneFn);
|
}
|
else if( window.Caman ){
|
// http://camanjs.com/guides/
|
window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
|
if( typeof filter == 'string' ){
|
this[filter]();
|
}
|
else {
|
api.each(filter, function (val, method){
|
this[method](val);
|
}, this);
|
}
|
this.render(doneFn);
|
});
|
}
|
};
|
|
|
/**
|
* @Override in load-image-ios.js
|
*/
|
api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
|
try {
|
return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
|
} catch (err) {
|
api.log('renderImageToCanvas failed:', err);
|
throw err;
|
}
|
};
|
|
|
// @export
|
api.support.canvas = api.support.transform = support;
|
api.Image = Image;
|
})(FileAPI, document);
|
|
/*
|
* JavaScript Load Image iOS scaling fixes 1.0.3
|
* https://github.com/blueimp/JavaScript-Load-Image
|
*
|
* Copyright 2013, Sebastian Tschan
|
* https://blueimp.net
|
*
|
* iOS image scaling fixes based on
|
* https://github.com/stomita/ios-imagefile-megapixel
|
*
|
* Licensed under the MIT license:
|
* http://www.opensource.org/licenses/MIT
|
*/
|
|
/*jslint nomen: true, bitwise: true */
|
/*global FileAPI, window, document */
|
|
(function (factory) {
|
'use strict';
|
factory(FileAPI);
|
}(function (loadImage) {
|
'use strict';
|
|
// Only apply fixes on the iOS platform:
|
if (!window.navigator || !window.navigator.platform ||
|
!(/iP(hone|od|ad)/).test(window.navigator.platform)) {
|
return;
|
}
|
|
var originalRenderMethod = loadImage.renderImageToCanvas;
|
|
// Detects subsampling in JPEG images:
|
loadImage.detectSubsampling = function (img) {
|
var canvas,
|
context;
|
if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
|
canvas = document.createElement('canvas');
|
canvas.width = canvas.height = 1;
|
context = canvas.getContext('2d');
|
context.drawImage(img, -img.width + 1, 0);
|
// subsampled image becomes half smaller in rendering size.
|
// check alpha channel value to confirm image is covering edge pixel or not.
|
// if alpha value is 0 image is not covering, hence subsampled.
|
return context.getImageData(0, 0, 1, 1).data[3] === 0;
|
}
|
return false;
|
};
|
|
// Detects vertical squash in JPEG images:
|
loadImage.detectVerticalSquash = function (img, subsampled) {
|
var naturalHeight = img.naturalHeight || img.height,
|
canvas = document.createElement('canvas'),
|
context = canvas.getContext('2d'),
|
data,
|
sy,
|
ey,
|
py,
|
alpha;
|
if (subsampled) {
|
naturalHeight /= 2;
|
}
|
canvas.width = 1;
|
canvas.height = naturalHeight;
|
context.drawImage(img, 0, 0);
|
data = context.getImageData(0, 0, 1, naturalHeight).data;
|
// search image edge pixel position in case it is squashed vertically:
|
sy = 0;
|
ey = naturalHeight;
|
py = naturalHeight;
|
while (py > sy) {
|
alpha = data[(py - 1) * 4 + 3];
|
if (alpha === 0) {
|
ey = py;
|
} else {
|
sy = py;
|
}
|
py = (ey + sy) >> 1;
|
}
|
return (py / naturalHeight) || 1;
|
};
|
|
// Renders image to canvas while working around iOS image scaling bugs:
|
// https://github.com/blueimp/JavaScript-Load-Image/issues/13
|
loadImage.renderImageToCanvas = function (
|
canvas,
|
img,
|
sourceX,
|
sourceY,
|
sourceWidth,
|
sourceHeight,
|
destX,
|
destY,
|
destWidth,
|
destHeight
|
) {
|
if (img._type === 'image/jpeg') {
|
var context = canvas.getContext('2d'),
|
tmpCanvas = document.createElement('canvas'),
|
tileSize = 1024,
|
tmpContext = tmpCanvas.getContext('2d'),
|
subsampled,
|
vertSquashRatio,
|
tileX,
|
tileY;
|
tmpCanvas.width = tileSize;
|
tmpCanvas.height = tileSize;
|
context.save();
|
subsampled = loadImage.detectSubsampling(img);
|
if (subsampled) {
|
sourceX /= 2;
|
sourceY /= 2;
|
sourceWidth /= 2;
|
sourceHeight /= 2;
|
}
|
vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
|
if (subsampled || vertSquashRatio !== 1) {
|
sourceY *= vertSquashRatio;
|
destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
|
destHeight = Math.ceil(
|
tileSize * destHeight / sourceHeight / vertSquashRatio
|
);
|
destY = 0;
|
tileY = 0;
|
while (tileY < sourceHeight) {
|
destX = 0;
|
tileX = 0;
|
while (tileX < sourceWidth) {
|
tmpContext.clearRect(0, 0, tileSize, tileSize);
|
tmpContext.drawImage(
|
img,
|
sourceX,
|
sourceY,
|
sourceWidth,
|
sourceHeight,
|
-tileX,
|
-tileY,
|
sourceWidth,
|
sourceHeight
|
);
|
context.drawImage(
|
tmpCanvas,
|
0,
|
0,
|
tileSize,
|
tileSize,
|
destX,
|
destY,
|
destWidth,
|
destHeight
|
);
|
tileX += tileSize;
|
destX += destWidth;
|
}
|
tileY += tileSize;
|
destY += destHeight;
|
}
|
context.restore();
|
return canvas;
|
}
|
}
|
return originalRenderMethod(
|
canvas,
|
img,
|
sourceX,
|
sourceY,
|
sourceWidth,
|
sourceHeight,
|
destX,
|
destY,
|
destWidth,
|
destHeight
|
);
|
};
|
|
}));
|
|
/*global window, FileAPI */
|
|
(function (api, window){
|
"use strict";
|
|
var
|
FormData = window.FormData
|
|
, document = window.document
|
, unescape = window.unescape
|
, encodeURIComponent = window.encodeURIComponent
|
;
|
|
|
/**
|
* @class FileAPI.Form
|
* @constructor
|
*/
|
function Form(){
|
this.items = [];
|
}
|
|
Form.prototype = {
|
|
append: function (name, blob, file, type){
|
this.items.push({
|
name: name
|
, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
|
, file: blob && (file || blob.name)
|
, type: blob && (type || blob.type)
|
});
|
},
|
|
each: function (fn){
|
var i = 0, n = this.items.length;
|
for( ; i < n; i++ ){
|
fn.call(this, this.items[i]);
|
}
|
},
|
|
toData: function (fn, options){
|
// allow chunked transfer if we have only one file to send
|
// flag is used below and in XHR._send
|
options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
|
|
if( !api.support.html5 ){
|
api.log('FileAPI.Form.toHtmlData');
|
this.toHtmlData(fn);
|
}
|
else if( !api.formData || this.multipart || !FormData ){
|
api.log('FileAPI.Form.toMultipartData');
|
this.toMultipartData(fn);
|
}
|
else if( options._chunked ){
|
api.log('FileAPI.Form.toPlainData');
|
this.toPlainData(fn);
|
}
|
else {
|
api.log('FileAPI.Form.toFormData');
|
this.toFormData(fn);
|
}
|
},
|
|
_to: function (data, complete, next, arg){
|
var queue = api.queue(function (){
|
complete(data);
|
});
|
|
this.each(function (file){
|
next(file, data, queue, arg);
|
});
|
|
queue.check();
|
},
|
|
|
toHtmlData: function (fn){
|
this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
|
var blob = file.blob, hidden;
|
|
if( file.file ){
|
api.reset(blob, true);
|
// set new name
|
blob.name = file.name;
|
data.appendChild(blob);
|
}
|
else {
|
hidden = document.createElement('input');
|
hidden.name = file.name;
|
hidden.type = 'hidden';
|
hidden.value = blob;
|
data.appendChild(hidden);
|
}
|
});
|
},
|
|
toPlainData: function (fn){
|
this._to({}, fn, function (file, data, queue){
|
if( file.file ){
|
data.type = file.file;
|
}
|
|
if( file.blob.toBlob ){
|
// canvas
|
queue.inc();
|
_convertFile(file, function (file, blob){
|
data.name = file.name;
|
data.file = blob;
|
data.size = blob.length;
|
data.type = file.type;
|
queue.next();
|
});
|
}
|
else if( file.file ){
|
// file
|
data.name = file.blob.name;
|
data.file = file.blob;
|
data.size = file.blob.size;
|
data.type = file.type;
|
}
|
else {
|
// additional data
|
if( !data.params ){
|
data.params = [];
|
}
|
data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
|
}
|
|
data.start = -1;
|
data.end = data.file && data.file.FileAPIReadPosition || -1;
|
data.retry = 0;
|
});
|
},
|
|
toFormData: function (fn){
|
this._to(new FormData, fn, function (file, data, queue){
|
if( file.blob && file.blob.toBlob ){
|
queue.inc();
|
_convertFile(file, function (file, blob){
|
data.append(file.name, blob, file.file);
|
queue.next();
|
});
|
}
|
else if( file.file ){
|
data.append(file.name, file.blob, file.file);
|
}
|
else {
|
data.append(file.name, file.blob);
|
}
|
|
if( file.file ){
|
data.append('_'+file.name, file.file);
|
}
|
});
|
},
|
|
|
toMultipartData: function (fn){
|
this._to([], fn, function (file, data, queue, boundary){
|
queue.inc();
|
_convertFile(file, function (file, blob){
|
data.push(
|
'--_' + boundary
|
+ '\r\nContent-Disposition: form-data; name="'+ file.name +'"'
|
+ (file.file ? '; filename="'+ unescape( encodeURIComponent(file.file) ) +'"' : '') // `unescape + encodeURIComponent` -- for сyrillic characters and similar.
|
+ (file.file ? '\r\nContent-Type: ' + (file.type || 'application/octet-stream') : '')
|
+ '\r\n'
|
+ '\r\n' + blob
|
+ '\r\n'
|
);
|
queue.next();
|
}, true);
|
}, api.expando);
|
}
|
};
|
|
|
function _convertFile(file, fn, useBinaryString){
|
var blob = file.blob, filename = file.file;
|
|
if( filename ){
|
if( !blob.toDataURL ){
|
// The Blob is not an image.
|
api.readAsBinaryString(blob, function (evt){
|
if( evt.type == 'load' ){
|
fn(file, evt.result);
|
}
|
});
|
return;
|
}
|
|
var
|
mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
|
, type = mime[file.type] ? file.type : 'image/png'
|
, ext = mime[type] || '.png'
|
, quality = blob.quality || 1
|
;
|
|
if( !filename.match(new RegExp(ext+'$', 'i')) ){
|
// Does not change the current extension, but add a new one.
|
filename += ext.replace('?', '');
|
}
|
|
file.file = filename;
|
file.type = type;
|
|
if( !useBinaryString && blob.toBlob ){
|
blob.toBlob(function (blob){
|
fn(file, blob);
|
}, type, quality);
|
}
|
else {
|
fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
|
}
|
}
|
else {
|
fn(file, blob);
|
}
|
}
|
|
|
// @export
|
api.Form = Form;
|
})(FileAPI, window);
|
|
/*global window, FileAPI, Uint8Array */
|
|
(function (window, api){
|
"use strict";
|
|
var
|
noop = api.F
|
, document = window.document
|
|
, XHR = function (options){
|
var defer = api.defer().progress(options.progress);
|
|
this.uid = api.uid();
|
|
this.xhr = {
|
abort: noop
|
, getResponseHeader: noop
|
, getAllResponseHeaders: noop
|
};
|
|
this.defer = defer;
|
this.options = api.extend({
|
upload: noop
|
, complete: noop
|
, withCredentials: api.withCredentials
|
}, options);
|
|
this.done = this.success = defer.done;
|
this.fail = this.error = defer.fail;
|
this.notify = defer.notify;
|
this.progress = defer.progress;
|
},
|
|
_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
|
;
|
|
|
XHR.prototype = {
|
status: 0,
|
statusText: '',
|
constructor: XHR,
|
|
getResponseHeader: function (name){
|
return this.xhr.getResponseHeader(name);
|
},
|
|
getAllResponseHeaders: function (){
|
return this.xhr.getAllResponseHeaders() || {};
|
},
|
|
end: function (status, statusText){
|
var
|
_this = this
|
, defer = _this.defer
|
, options = _this.options
|
, err = (status == 200 || status == 201 ? false : statusText || 'unknown')
|
;
|
|
_this.end =
|
_this.abort = noop;
|
_this.status = status;
|
|
if( statusText ){
|
_this.statusText = statusText;
|
}
|
|
api.log('xhr.end:', status, statusText);
|
|
if( err ){
|
defer.reject(err, _this, options);
|
} else {
|
defer.resolve(_this, options);
|
}
|
|
options.complete(err, _this);
|
|
if( _this.xhr && _this.xhr.node ){
|
setTimeout(function (){
|
var node = _this.xhr.node;
|
try { node.parentNode.removeChild(node); } catch (e){}
|
try { delete window[_this.uid]; } catch (e){}
|
window[_this.uid] = _this.xhr.node = null;
|
}, 9);
|
}
|
},
|
|
abort: function (){
|
this.end(0, 'abort');
|
|
if( this.xhr ){
|
this.xhr.aborted = true;
|
this.xhr.abort();
|
}
|
},
|
|
send: function (FormData){
|
var _this = this, options = this.options;
|
|
FormData.toData(function (data){
|
// Start uploading
|
options.upload(options, _this);
|
_this._send.call(_this, options, data);
|
}, options);
|
},
|
|
_send: function (options, data){
|
var _this = this, xhr, uid = _this.uid, url = options.url;
|
|
api.log('XHR._send:', data);
|
|
if( !options.cache ){
|
// No cache
|
url += (~url.indexOf('?') ? '&' : '?') + api.uid();
|
}
|
|
if( data.nodeName ){
|
var jsonp = options.jsonp;
|
|
// prepare callback in GET
|
url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
|
|
// legacy
|
options.upload(options, _this);
|
|
xhr = document.createElement('div');
|
xhr.innerHTML = '<form target="'+ uid +'" action="'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
|
+ '<iframe name="'+ uid +'" src="javascript:false;"></iframe>'
|
+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
|
+ '</form>'
|
;
|
|
// get form-data & transport
|
var
|
form = xhr.getElementsByTagName('form')[0]
|
, transport = xhr.getElementsByTagName('iframe')[0]
|
;
|
|
form.appendChild(data);
|
|
api.log(form.parentNode.innerHTML);
|
|
// append to DOM
|
document.body.appendChild(xhr);
|
|
// keep a reference to node-transport
|
_this.xhr.node = xhr;
|
|
var
|
onPostMessage = function (evt){
|
if( ~url.indexOf(evt.origin) ){
|
try {
|
var result = api.parseJSON(evt.data);
|
if( result.id == uid ){
|
complete(result.status, result.statusText, result.response);
|
}
|
} catch( err ){
|
complete(0, err.message);
|
}
|
}
|
},
|
|
// jsonp-callack
|
complete = window[uid] = function (status, statusText, response){
|
_this.readyState = 4;
|
_this.responseText = response;
|
_this.end(status, statusText);
|
|
api.event.off(window, 'message', onPostMessage);
|
window[uid] = xhr = transport = transport.onload = null;
|
}
|
;
|
|
_this.xhr.abort = function (){
|
try {
|
if( transport.stop ){ transport.stop(); }
|
else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
|
else { transport.contentWindow.document.execCommand('Stop'); }
|
}
|
catch (er) {}
|
complete(0, "abort");
|
};
|
|
api.event.on(window, 'message', onPostMessage);
|
|
transport.onload = function (){
|
try {
|
var
|
win = transport.contentWindow
|
, doc = win.document
|
, result = win.result || api.parseJSON(doc.body.innerHTML)
|
;
|
complete(result.status, result.statusText, result.response);
|
} catch (e){
|
api.log('[transport.onload]', e);
|
}
|
};
|
|
// send
|
_this.readyState = 2; // loaded
|
form.submit();
|
form = null;
|
}
|
else {
|
// Clean url
|
url = url.replace(/([a-z]+)=(\?)&?/i, '');
|
|
// html5
|
if( this.xhr && this.xhr.aborted ){
|
api.log("Error: already aborted");
|
return;
|
}
|
|
if (data.params) {
|
url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
|
}
|
|
xhr = _this.xhr = api.getXHR();
|
|
xhr.open(options.type || 'POST', url, true);
|
|
if( options.responseType ){
|
xhr.responseType = options.responseType;
|
}
|
|
if( options.withCredentials ){
|
xhr.withCredentials = "true";
|
}
|
|
if( !options.headers || !options.headers['X-Requested-With'] ){
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
}
|
|
api.each(options.headers, function (val, key){
|
xhr.setRequestHeader(key, val);
|
});
|
|
|
if ( options._chunked ) {
|
// chunked upload
|
if( xhr.upload ){
|
xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
|
if (!data.retry) {
|
// show progress only for correct chunk uploads
|
_this.notify({
|
type: evt.type
|
, total: data.size
|
, loaded: data.start + evt.loaded
|
, totalSize: data.size
|
}, _this, options);
|
}
|
}, 100), false);
|
}
|
|
xhr.onreadystatechange = function (){
|
var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
|
|
_this.status = xhr.status;
|
_this.statusText = xhr.statusText;
|
_this.readyState = xhr.readyState;
|
|
if( xhr.readyState == 4 ){
|
try {
|
for( var k in _xhrResponsePostfix ){
|
_this['response'+k] = xhr['response'+k];
|
}
|
} catch (_){}
|
|
xhr.onreadystatechange = null;
|
|
if (!xhr.status || xhr.status - 201 > 0) {
|
api.log("Error: " + xhr.status);
|
// some kind of error
|
// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
|
// up - server error
|
if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
|
// let's try again the same chunk
|
// only applicable for recoverable error codes 500 && 416
|
var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
|
|
// inform about recoverable problems
|
options.pause(data.file, options);
|
|
// smart restart if server reports about the last known byte
|
api.log("X-Last-Known-Byte: " + lkb);
|
if (lkb) {
|
data.end = lkb;
|
} else {
|
data.end = data.start - 1;
|
if (416 == xhr.status) {
|
data.end = data.end - options.chunkSize;
|
}
|
}
|
|
setTimeout(function () {
|
_this._send(options, data);
|
}, delay);
|
} else {
|
// no mo retries
|
_this.end(xhr.status);
|
}
|
} else {
|
// success
|
data.retry = 0;
|
|
if (data.end == data.size - 1) {
|
// finished
|
_this.end(xhr.status);
|
} else {
|
// next chunk
|
|
// shift position if server reports about the last known byte
|
api.log("X-Last-Known-Byte: " + lkb);
|
if (lkb) {
|
data.end = lkb;
|
}
|
data.file.FileAPIReadPosition = data.end;
|
|
setTimeout(function () {
|
_this._send(options, data);
|
}, 0);
|
}
|
}
|
|
xhr = null;
|
}
|
};
|
|
data.start = data.end + 1;
|
data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
|
|
// Retrieve a slice of file
|
var
|
file = data.file
|
, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
|
;
|
|
if( data.size && !slice.size ){
|
setTimeout(function (){
|
_this.end(-1);
|
});
|
} else {
|
xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
|
xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
|
xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
|
|
xhr.send(slice);
|
}
|
|
file = slice = null;
|
} else {
|
// single piece upload
|
if( xhr.upload ){
|
// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
|
xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
|
_this.notify(evt, _this, options);
|
}, 100), false);
|
|
api.event.on(xhr, 'progress', function (/**Event*/evt){
|
_this.notify(evt, _this, options);
|
});
|
}
|
|
xhr.onreadystatechange = function (){
|
_this.status = xhr.status;
|
_this.statusText = xhr.statusText;
|
_this.readyState = xhr.readyState;
|
|
if( xhr.readyState == 4 ){
|
try {
|
for( var k in _xhrResponsePostfix ){
|
_this['response'+k] = xhr['response'+k];
|
}
|
} catch (_){}
|
xhr.onreadystatechange = null;
|
|
if (!xhr.status || xhr.status > 201) {
|
api.log("Error: " + xhr.status);
|
if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
|
options.retry = (options.retry || 0) + 1;
|
var delay = api.networkDownRetryTimeout;
|
|
// inform about recoverable problems
|
options.pause(options.file, options);
|
|
setTimeout(function () {
|
_this._send(options, data);
|
}, delay);
|
} else {
|
//success
|
_this.end(xhr.status);
|
}
|
} else {
|
//success
|
_this.end(xhr.status);
|
}
|
|
xhr = null;
|
}
|
};
|
|
if( api.isArray(data) ){
|
// multipart
|
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
|
var rawData = data.join('') +'--_'+ api.expando +'--';
|
|
/** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
|
if( xhr.sendAsBinary ){
|
xhr.sendAsBinary(rawData);
|
}
|
else {
|
var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
|
xhr.send(new Uint8Array(bytes).buffer);
|
|
}
|
} else {
|
// FormData
|
xhr.send(data);
|
}
|
}
|
}
|
}
|
};
|
|
|
// @export
|
api.XHR = XHR;
|
})(window, FileAPI);
|
|
/**
|
* @class FileAPI.Camera
|
* @author RubaXa <trash@rubaxa.org>
|
* @support Chrome 21+, FF 18+, Opera 12+
|
*/
|
|
/*global window, FileAPI, jQuery */
|
/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
|
(function (window, api){
|
"use strict";
|
|
var
|
URL = window.URL || window.webkitURL,
|
|
document = window.document,
|
navigator = window.navigator,
|
|
getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
|
|
html5 = !!getMedia
|
;
|
|
|
// Support "media"
|
api.support.media = html5;
|
|
|
var Camera = function (video){
|
this.video = video;
|
};
|
|
|
Camera.prototype = {
|
isActive: function (){
|
return !!this._active;
|
},
|
|
|
/**
|
* Start camera streaming
|
* @param {Function} callback
|
*/
|
start: function (callback){
|
var
|
_this = this
|
, video = _this.video
|
, _successId
|
, _failId
|
, _complete = function (err){
|
_this._active = !err;
|
clearTimeout(_failId);
|
clearTimeout(_successId);
|
// api.event.off(video, 'loadedmetadata', _complete);
|
callback && callback(err, _this);
|
}
|
;
|
|
getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
|
// Success
|
_this.stream = stream;
|
|
// api.event.on(video, 'loadedmetadata', function (){
|
// _complete(null);
|
// });
|
|
// Set camera stream
|
video.src = URL.createObjectURL(stream);
|
|
// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
|
// See crbug.com/110938.
|
_successId = setInterval(function (){
|
if( _detectVideoSignal(video) ){
|
_complete(null);
|
}
|
}, 1000);
|
|
_failId = setTimeout(function (){
|
_complete('timeout');
|
}, 5000);
|
|
// Go-go-go!
|
video.play();
|
}, _complete/*error*/);
|
},
|
|
|
/**
|
* Stop camera streaming
|
*/
|
stop: function (){
|
try {
|
this._active = false;
|
this.video.pause();
|
this.stream.stop();
|
} catch( err ){ }
|
},
|
|
|
/**
|
* Create screenshot
|
* @return {FileAPI.Camera.Shot}
|
*/
|
shot: function (){
|
return new Shot(this.video);
|
}
|
};
|
|
|
/**
|
* Get camera element from container
|
*
|
* @static
|
* @param {HTMLElement} el
|
* @return {Camera}
|
*/
|
Camera.get = function (el){
|
return new Camera(el.firstChild);
|
};
|
|
|
/**
|
* Publish camera element into container
|
*
|
* @static
|
* @param {HTMLElement} el
|
* @param {Object} options
|
* @param {Function} [callback]
|
*/
|
Camera.publish = function (el, options, callback){
|
if( typeof options == 'function' ){
|
callback = options;
|
options = {};
|
}
|
|
// Dimensions of "camera"
|
options = api.extend({}, {
|
width: '100%'
|
, height: '100%'
|
, start: true
|
}, options);
|
|
|
if( el.jquery ){
|
// Extract first element, from jQuery collection
|
el = el[0];
|
}
|
|
|
var doneFn = function (err){
|
if( err ){
|
callback(err);
|
}
|
else {
|
// Get camera
|
var cam = Camera.get(el);
|
if( options.start ){
|
cam.start(callback);
|
}
|
else {
|
callback(null, cam);
|
}
|
}
|
};
|
|
|
el.style.width = _px(options.width);
|
el.style.height = _px(options.height);
|
|
|
if( api.html5 && html5 ){
|
// Create video element
|
var video = document.createElement('video');
|
|
// Set dimensions
|
video.style.width = _px(options.width);
|
video.style.height = _px(options.height);
|
|
// Clean container
|
if( window.jQuery ){
|
jQuery(el).empty();
|
} else {
|
el.innerHTML = '';
|
}
|
|
// Add "camera" to container
|
el.appendChild(video);
|
|
// end
|
doneFn();
|
}
|
else {
|
Camera.fallback(el, options, doneFn);
|
}
|
};
|
|
|
Camera.fallback = function (el, options, callback){
|
callback('not_support_camera');
|
};
|
|
|
/**
|
* @class FileAPI.Camera.Shot
|
*/
|
var Shot = function (video){
|
var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
|
var shot = api.Image(canvas);
|
shot.type = 'image/png';
|
shot.width = canvas.width;
|
shot.height = canvas.height;
|
shot.size = canvas.width * canvas.height * 4;
|
return shot;
|
};
|
|
|
/**
|
* Add "px" postfix, if value is a number
|
*
|
* @private
|
* @param {*} val
|
* @return {String}
|
*/
|
function _px(val){
|
return val >= 0 ? val + 'px' : val;
|
}
|
|
|
/**
|
* @private
|
* @param {HTMLVideoElement} video
|
* @return {Boolean}
|
*/
|
function _detectVideoSignal(video){
|
var canvas = document.createElement('canvas'), ctx, res = false;
|
try {
|
ctx = canvas.getContext('2d');
|
ctx.drawImage(video, 0, 0, 1, 1);
|
res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
|
}
|
catch( e ){}
|
return res;
|
}
|
|
|
// @export
|
Camera.Shot = Shot;
|
api.Camera = Camera;
|
})(window, FileAPI);
|
|
/**
|
* FileAPI fallback to Flash
|
*
|
* @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
|
*/
|
|
/*global window, ActiveXObject, FileAPI */
|
(function (window, jQuery, api) {
|
"use strict";
|
|
var
|
document = window.document
|
, location = window.location
|
, navigator = window.navigator
|
, _each = api.each
|
;
|
|
|
api.support.flash = (function (){
|
var mime = navigator.mimeTypes, has = false;
|
|
if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
|
has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
|
}
|
else {
|
try {
|
has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
|
}
|
catch(er){
|
api.log('Flash -- does not supported.');
|
}
|
}
|
|
if( has && /^file:/i.test(location) ){
|
api.log('[warn] Flash does not work on `file:` protocol.');
|
}
|
|
return has;
|
})();
|
|
|
api.support.flash
|
&& (0
|
|| !api.html5 || !api.support.html5
|
|| (api.cors && !api.support.cors)
|
|| (api.media && !api.support.media)
|
)
|
&& (function (){
|
var
|
_attr = api.uid()
|
, _retry = 0
|
, _files = {}
|
, _rhttp = /^https?:/i
|
|
, flash = {
|
_fn: {},
|
|
|
/**
|
* Initialization & preload flash object
|
*/
|
init: function (){
|
var child = document.body && document.body.firstChild;
|
|
if( child ){
|
do {
|
if( child.nodeType == 1 ){
|
api.log('FlashAPI.state: awaiting');
|
|
var dummy = document.createElement('div');
|
|
dummy.id = '_' + _attr;
|
|
_css(dummy, {
|
top: 1
|
// , right: 1 // fixed issues #1752 on devtopia.
|
, width: 5
|
, height: 5
|
, position: 'absolute'
|
, zIndex: 1e6+'' // set max zIndex
|
});
|
|
child.parentNode.insertBefore(dummy, child);
|
flash.publish(dummy, _attr);
|
|
return;
|
}
|
}
|
while( child = child.nextSibling );
|
}
|
|
if( _retry < 10 ){
|
setTimeout(flash.init, ++_retry*50);
|
}
|
},
|
|
|
/**
|
* Publish flash-object
|
*
|
* @param {HTMLElement} el
|
* @param {String} id
|
* @param {Object} [opts]
|
*/
|
publish: function (el, id, opts){
|
// opts = opts || {};
|
// el.innerHTML = _makeFlashHTML({
|
// id: id
|
// , src: _getUrl(api.flashUrl, 'r=' + api.version)
|
// // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
|
// , wmode: opts.camera ? '' : 'transparent'
|
// , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
|
// + '&flashId='+ id
|
// + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
|
// + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
|
// + '&timeout='+api.flashAbortTimeout
|
// + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
|
// + '&debug='+(api.debug?"1":"")
|
// }, opts);
|
},
|
|
|
ready: function (){
|
api.log('FlashAPI.state: ready');
|
|
flash.ready = api.F;
|
flash.isReady = true;
|
flash.patch();
|
flash.patchCamera && flash.patchCamera();
|
api.event.on(document, 'mouseover', flash.mouseover);
|
api.event.on(document, 'click', function (evt){
|
if( flash.mouseover(evt) ){
|
evt.preventDefault
|
? evt.preventDefault()
|
: (evt.returnValue = true)
|
;
|
}
|
});
|
},
|
|
|
getEl: function (){
|
return document.getElementById('_'+_attr);
|
},
|
|
|
getWrapper: function (node){
|
do {
|
if( /js-fileapi-wrapper/.test(node.className) ){
|
return node;
|
}
|
}
|
while( (node = node.parentNode) && (node !== document.body) );
|
},
|
|
|
mouseover: function (evt){
|
var target = api.event.fix(evt).target;
|
|
if( /input/i.test(target.nodeName) && target.type == 'file' ){
|
var
|
state = target.getAttribute(_attr)
|
, wrapper = flash.getWrapper(target)
|
;
|
|
if( api.multiFlash ){
|
// check state:
|
// i — published
|
// i — initialization
|
// r — ready
|
if( state == 'i' || state == 'r' ){
|
// publish fail
|
return false;
|
}
|
else if( state != 'p' ){
|
// set "init" state
|
target.setAttribute(_attr, 'i');
|
|
var dummy = document.createElement('div');
|
|
if( !wrapper ){
|
api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
|
return;
|
}
|
|
_css(dummy, {
|
top: 0
|
, left: 0
|
, width: target.offsetWidth + 100
|
, height: target.offsetHeight + 100
|
, zIndex: 1e6+'' // set max zIndex
|
, position: 'absolute'
|
});
|
|
wrapper.appendChild(dummy);
|
flash.publish(dummy, api.uid());
|
|
// set "publish" state
|
target.setAttribute(_attr, 'p');
|
}
|
|
return true;
|
}
|
else if( wrapper ){
|
// Use one flash element
|
var box = _getDimensions(wrapper);
|
|
_css(flash.getEl(), box);
|
|
// Set current input
|
flash.curInp = target;
|
}
|
}
|
else if( !/object|embed/i.test(target.nodeName) ){
|
_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
|
}
|
},
|
|
onEvent: function (evt){
|
var type = evt.type;
|
|
if( type == 'ready' ){
|
try {
|
// set "ready" state
|
flash.getInput(evt.flashId).setAttribute(_attr, 'r');
|
} catch (e){
|
}
|
|
flash.ready();
|
setTimeout(function (){ flash.mouseenter(evt); }, 50);
|
return true;
|
}
|
else if( type === 'ping' ){
|
api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
|
}
|
else if( type === 'log' ){
|
api.log('(flash -> js).log:', evt.target);
|
}
|
else if( type in flash ){
|
setTimeout(function (){
|
api.log('FlashAPI.event.'+evt.type+':', evt);
|
flash[type](evt);
|
}, 1);
|
}
|
},
|
|
|
mouseenter: function (evt){
|
var node = flash.getInput(evt.flashId);
|
|
if( node ){
|
// Set multiple mode
|
flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
|
|
|
// Set files filter
|
var accept = [], exts = {};
|
|
_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
|
api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
|
exts[ext] = 1;
|
});
|
});
|
|
_each(exts, function (i, ext){
|
accept.push( ext );
|
});
|
|
flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
|
}
|
},
|
|
|
get: function (id){
|
return document[id] || window[id] || document.embeds[id];
|
},
|
|
|
getInput: function (id){
|
if( api.multiFlash ){
|
try {
|
var node = flash.getWrapper(flash.get(id));
|
if( node ){
|
return node.getElementsByTagName('input')[0];
|
}
|
} catch (e){
|
api.log('[err] Can not find "input" by flashId:', id, e);
|
}
|
} else {
|
return flash.curInp;
|
}
|
},
|
|
|
select: function (evt){
|
var
|
inp = flash.getInput(evt.flashId)
|
, uid = api.uid(inp)
|
, files = evt.target.files
|
, event
|
;
|
|
_each(files, function (file){
|
file.type = api.getMimeType(file);
|
});
|
|
_files[uid] = files;
|
|
if( document.createEvent ){
|
event = document.createEvent('Event');
|
event.files = files;
|
event.initEvent('change', true, true);
|
inp.dispatchEvent(event);
|
}
|
else if( jQuery ){
|
jQuery(inp).trigger({ type: 'change', files: files });
|
}
|
else {
|
event = document.createEventObject();
|
event.files = files;
|
inp.fireEvent('onchange', event);
|
}
|
},
|
|
|
cmd: function (id, name, data, last){
|
try {
|
api.log('(js -> flash).'+name+':', data);
|
return flash.get(id.flashId || id).cmd(name, data);
|
} catch (e){
|
api.log('(js -> flash).onError:', e);
|
if( !last ){
|
// try again
|
setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
|
}
|
}
|
},
|
|
|
patch: function (){
|
api.flashEngine = true;
|
|
// FileAPI
|
_inherit(api, {
|
readAsDataURL: function (file, callback){
|
if( _isHtmlFile(file) ){
|
this.parent.apply(this, arguments);
|
}
|
else {
|
api.log('FlashAPI.readAsBase64');
|
|
flash.cmd(file, 'readAsBase64', {
|
id: file.id,
|
callback: _wrap(function _(err, base64){
|
_unwrap(_);
|
|
api.log('FlashAPI.readAsBase64:', err);
|
|
callback({
|
type: err ? 'error' : 'load'
|
, error: err
|
, result: 'data:'+ file.type +';base64,'+ base64
|
});
|
})
|
});
|
}
|
},
|
|
readAsText: function (file, encoding, callback){
|
if( callback ){
|
api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
|
} else {
|
callback = encoding;
|
}
|
|
api.readAsDataURL(file, function (evt){
|
if( evt.type == 'load' ){
|
try {
|
evt.result = window.atob(evt.result.split(';base64,')[1]);
|
} catch( err ){
|
evt.type = 'error';
|
evt.error = err.toString();
|
}
|
}
|
callback(evt);
|
});
|
},
|
|
getFiles: function (input, filter, callback){
|
if( callback ){
|
api.filterFiles(api.getFiles(input), filter, callback);
|
return null;
|
}
|
|
var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
|
|
|
if( !files ){
|
// Файлов нету, вызываем родительский метод
|
return this.parent.apply(this, arguments);
|
}
|
|
|
if( filter ){
|
filter = api.getFilesFilter(filter);
|
files = api.filter(files, function (file){ return filter.test(file.name); });
|
}
|
|
return files;
|
},
|
|
|
getInfo: function (file, fn){
|
if( _isHtmlFile(file) ){
|
this.parent.apply(this, arguments);
|
}
|
else if( file.isShot ){
|
fn(null, file.info = {
|
width: file.width,
|
height: file.height
|
});
|
}
|
else {
|
if( !file.__info ){
|
var defer = file.__info = api.defer();
|
|
flash.cmd(file, 'getFileInfo', {
|
id: file.id
|
, callback: _wrap(function _(err, info){
|
_unwrap(_);
|
defer.resolve(err, file.info = info);
|
})
|
});
|
}
|
|
file.__info.then(fn);
|
}
|
}
|
});
|
|
|
// FileAPI.Image
|
api.support.transform = true;
|
api.Image && _inherit(api.Image.prototype, {
|
get: function (fn, scaleMode){
|
this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
|
return this.parent(fn);
|
},
|
|
_load: function (file, fn){
|
api.log('FlashAPI.Image._load:', file);
|
|
if( _isHtmlFile(file) ){
|
this.parent.apply(this, arguments);
|
}
|
else {
|
var _this = this;
|
api.getInfo(file, function (err){
|
fn.call(_this, err, file);
|
});
|
}
|
},
|
|
_apply: function (file, fn){
|
api.log('FlashAPI.Image._apply:', file);
|
|
if( _isHtmlFile(file) ){
|
this.parent.apply(this, arguments);
|
}
|
else {
|
var m = this.getMatrix(file.info), doneFn = fn;
|
|
flash.cmd(file, 'imageTransform', {
|
id: file.id
|
, matrix: m
|
, callback: _wrap(function _(err, base64){
|
api.log('FlashAPI.Image._apply.callback:', err);
|
_unwrap(_);
|
|
if( err ){
|
doneFn(err);
|
}
|
else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
|
_makeFlashImage({
|
width: (m.deg % 180) ? m.dh : m.dw
|
, height: (m.deg % 180) ? m.dw : m.dh
|
, scale: m.scaleMode
|
}, base64, doneFn);
|
}
|
else {
|
if( m.filter ){
|
doneFn = function (err, img){
|
if( err ){
|
fn(err);
|
}
|
else {
|
api.Image.applyFilter(img, m.filter, function (){
|
fn(err, this.canvas);
|
});
|
}
|
};
|
}
|
|
api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
|
}
|
})
|
});
|
}
|
},
|
|
toData: function (fn){
|
var
|
file = this.file
|
, info = file.info
|
, matrix = this.getMatrix(info)
|
;
|
api.log('FlashAPI.Image.toData');
|
|
if( _isHtmlFile(file) ){
|
this.parent.apply(this, arguments);
|
}
|
else {
|
if( matrix.deg == 'auto' ){
|
matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
|
}
|
|
fn.call(this, !file.info, {
|
id: file.id
|
, flashId: file.flashId
|
, name: file.name
|
, type: file.type
|
, matrix: matrix
|
});
|
}
|
}
|
});
|
|
|
api.Image && _inherit(api.Image, {
|
fromDataURL: function (dataURL, size, callback){
|
if( !api.support.dataURI || dataURL.length > 3e4 ){
|
_makeFlashImage(
|
api.extend({ scale: 'exactFit' }, size)
|
, dataURL.replace(/^data:[^,]+,/, '')
|
, function (err, el){ callback(el); }
|
);
|
}
|
else {
|
this.parent(dataURL, size, callback);
|
}
|
}
|
});
|
|
|
// FileAPI.Form
|
_inherit(api.Form.prototype, {
|
toData: function (fn){
|
var items = this.items, i = items.length;
|
|
for( ; i--; ){
|
if( items[i].file && _isHtmlFile(items[i].blob) ){
|
return this.parent.apply(this, arguments);
|
}
|
}
|
|
api.log('FlashAPI.Form.toData');
|
fn(items);
|
}
|
});
|
|
|
// FileAPI.XHR
|
_inherit(api.XHR.prototype, {
|
_send: function (options, formData){
|
if(
|
formData.nodeName
|
|| formData.append && api.support.html5
|
|| api.isArray(formData) && (typeof formData[0] === 'string')
|
){
|
// HTML5, Multipart or IFrame
|
return this.parent.apply(this, arguments);
|
}
|
|
|
var
|
data = {}
|
, files = {}
|
, _this = this
|
, flashId
|
, fileId
|
;
|
|
_each(formData, function (item){
|
if( item.file ){
|
files[item.name] = item = _getFileDescr(item.blob);
|
fileId = item.id;
|
flashId = item.flashId;
|
}
|
else {
|
data[item.name] = item.blob;
|
}
|
});
|
|
if( !fileId ){
|
flashId = _attr;
|
}
|
|
if( !flashId ){
|
api.log('[err] FlashAPI._send: flashId -- undefined');
|
return this.parent.apply(this, arguments);
|
}
|
else {
|
api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
|
}
|
|
_this.xhr = {
|
headers: {},
|
abort: function (){ flash.cmd(flashId, 'abort', { id: fileId }); },
|
getResponseHeader: function (name){ return this.headers[name]; },
|
getAllResponseHeaders: function (){ return this.headers; }
|
};
|
|
var queue = api.queue(function (){
|
flash.cmd(flashId, 'upload', {
|
url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
|
, data: data
|
, files: fileId ? files : null
|
, headers: options.headers || {}
|
, callback: _wrap(function upload(evt){
|
var type = evt.type, result = evt.result;
|
|
api.log('FlashAPI.upload.'+type);
|
|
if( type == 'progress' ){
|
evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
|
evt.lengthComputable = true;
|
options.progress(evt);
|
}
|
else if( type == 'complete' ){
|
_unwrap(upload);
|
|
if( typeof result == 'string' ){
|
_this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
|
}
|
|
_this.end(evt.status || 200);
|
}
|
else if( type == 'abort' || type == 'error' ){
|
_this.end(evt.status || 0, evt.message);
|
_unwrap(upload);
|
}
|
})
|
});
|
});
|
|
|
// #2174: FileReference.load() call while FileReference.upload() or vice versa
|
_each(files, function (file){
|
queue.inc();
|
api.getInfo(file, queue.next);
|
});
|
|
queue.check();
|
}
|
});
|
}
|
}
|
;
|
|
|
function _makeFlashHTML(opts){
|
return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
|
+ '<param name="movie" value="#src#" />'
|
+ '<param name="flashvars" value="#flashvars#" />'
|
+ '<param name="swliveconnect" value="true" />'
|
+ '<param name="allowscriptaccess" value="always" />'
|
+ '<param name="allownetworking" value="all" />'
|
+ '<param name="menu" value="false" />'
|
+ '<param name="wmode" value="#wmode#" />'
|
+ '<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>'
|
+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
|
;
|
}
|
|
|
function _css(el, css){
|
if( el && el.style ){
|
var key, val;
|
for( key in css ){
|
val = css[key];
|
if( typeof val == 'number' ){
|
val += 'px';
|
}
|
try { el.style[key] = val; } catch (e) {}
|
}
|
}
|
}
|
|
|
function _inherit(obj, methods){
|
_each(methods, function (fn, name){
|
var prev = obj[name];
|
obj[name] = function (){
|
this.parent = prev;
|
return fn.apply(this, arguments);
|
};
|
});
|
}
|
|
function _isHtmlFile(file){
|
return file && !file.flashId;
|
}
|
|
function _wrap(fn){
|
var id = fn.wid = api.uid();
|
flash._fn[id] = fn;
|
return 'FileAPI.Flash._fn.'+id;
|
}
|
|
|
function _unwrap(fn){
|
try {
|
flash._fn[fn.wid] = null;
|
delete flash._fn[fn.wid];
|
}
|
catch(e){}
|
}
|
|
|
function _getUrl(url, params){
|
if( !_rhttp.test(url) ){
|
if( /^\.\//.test(url) || '/' != url.charAt(0) ){
|
var path = location.pathname;
|
path = path.substr(0, path.lastIndexOf('/'));
|
url = (path +'/'+ url).replace('/./', '/');
|
}
|
|
if( '//' != url.substr(0, 2) ){
|
url = '//' + location.host + url;
|
}
|
|
if( !_rhttp.test(url) ){
|
url = location.protocol + url;
|
}
|
}
|
|
if( params ){
|
url += (/\?/.test(url) ? '&' : '?') + params;
|
}
|
|
return url;
|
}
|
|
|
function _makeFlashImage(opts, base64, fn){
|
// var
|
// key
|
// , flashId = api.uid()
|
// , el = document.createElement('div')
|
// , attempts = 10
|
// ;
|
|
// for( key in opts ){
|
// el.setAttribute(key, opts[key]);
|
// el[key] = opts[key];
|
// }
|
|
// _css(el, opts);
|
|
// opts.width = '100%';
|
// opts.height = '100%';
|
|
// el.innerHTML = _makeFlashHTML(api.extend({
|
// id: flashId
|
// , src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
|
// , wmode: 'opaque'
|
// , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
|
// _unwrap(_);
|
// if( --attempts > 0 ){
|
// _setImage();
|
// }
|
// return true;
|
// })
|
// }, opts));
|
|
// function _setImage(){
|
// try {
|
// // Get flash-object by id
|
// var img = flash.get(flashId);
|
// img.setImage(base64);
|
// } catch (e){
|
// api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
|
// }
|
// }
|
|
// fn(false, el);
|
// el = null;
|
}
|
|
|
function _getFileDescr(file){
|
return {
|
id: file.id
|
, name: file.name
|
, matrix: file.matrix
|
, flashId: file.flashId
|
};
|
}
|
|
|
function _getDimensions(el){
|
var
|
box = el.getBoundingClientRect()
|
, body = document.body
|
, docEl = (el && el.ownerDocument).documentElement
|
;
|
|
return {
|
top: box.top + (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || body.clientTop || 0)
|
, left: box.left + (window.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || body.clientLeft || 0)
|
, width: box.right - box.left
|
, height: box.bottom - box.top
|
};
|
}
|
|
|
// @export
|
flash.wrap = _wrap;
|
flash.unwrap = _unwrap;
|
api.Flash = flash;
|
|
|
// Check dataURI support
|
api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
|
api.support.dataURI = !(img.width != 1 || img.height != 1);
|
flash.init();
|
});
|
})();
|
})(window, window.jQuery, FileAPI);
|
|
/**
|
* FileAPI fallback to Flash
|
*
|
* @flash-developer "Vladimir Demidov" <v.demidov@corp.mail.ru>
|
*/
|
|
/*global window, FileAPI */
|
(function (window, jQuery, api) {
|
"use strict";
|
|
var
|
_each = api.each
|
, _extend = api.extend
|
, _cameraQueue = []
|
|
, Flash = api.Flash
|
, _wrap = Flash.wrap
|
, _unwrap = Flash.unwrap
|
;
|
|
|
if( api.support.flash && (api.media && !api.support.media) ){
|
api.extend(Flash, {
|
|
patchCamera: function (){
|
api.Camera.fallback = function (el, options, callback){
|
var camId = api.uid();
|
api.log('FlashAPI.Camera.publish: ' + camId);
|
Flash.publish(el, camId, _extend(options, {
|
camera: true,
|
onEvent: _wrap(function _(evt){
|
if( evt.type === 'camera' ){
|
_unwrap(_);
|
|
if( evt.error ){
|
api.log('FlashAPI.Camera.publish.error: ' + evt.error);
|
callback(evt.error);
|
} else {
|
api.log('FlashAPI.Camera.publish.success: ' + camId);
|
callback(null);
|
}
|
}
|
})
|
}));
|
};
|
|
|
// Run
|
_each(_cameraQueue, function (args){
|
api.Camera.fallback.apply(api.Camera, args);
|
});
|
_cameraQueue = [];
|
|
|
// FileAPI.Camera:proto
|
_extend(api.Camera.prototype, {
|
_id: function (){
|
return this.video.id;
|
},
|
|
start: function (callback){
|
var _this = this;
|
Flash.cmd(this._id(), 'camera.on', {
|
callback: _wrap(function _(evt){
|
_unwrap(_);
|
|
if( evt.error ){
|
api.log('FlashAPI.camera.on.error: ' + evt.error);
|
callback(evt.error, _this);
|
} else {
|
api.log('FlashAPI.camera.on.success: ' + _this._id());
|
_this._active = true;
|
callback(null, _this);
|
}
|
})
|
});
|
},
|
|
stop: function (){
|
this._active = false;
|
Flash.cmd(this._id(), 'camera.off');
|
},
|
|
shot: function (){
|
api.log('FlashAPI.Camera.shot:', this._id());
|
|
var shot = Flash.cmd(this._id(), 'shot', {});
|
shot.type = 'image/png';
|
shot.flashId = this._id();
|
shot.isShot = true;
|
|
return new api.Camera.Shot(shot);
|
}
|
});
|
}
|
});
|
|
api.Camera.fallback = function (){
|
_cameraQueue.push(arguments);
|
};
|
}
|
})(window, window.jQuery, FileAPI);
|
if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
|