xstatic-angular-fileupload/xstatic/pkg/angular_fileupload/data/FileAPI.js

4314 lines
102 KiB
JavaScript

/*! FileAPI 2.0.7 - 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 || {},
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,
XMLHttpRequest = window.XMLHttpRequest,
jQuery = window.jQuery,
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:[^,]+,/,
_toString = {}.toString,
Math = window.Math,
_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
_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);
}
}
}
}
},
/**
* 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 = document.createElement('input');
input.setAttribute('type', "file");
return attr in input;
},
/**
* FileAPI (core object)
*/
api = {
version: '2.0.7',
cors: false,
html5: true,
media: false,
formData: true,
multiPassResize: true,
debug: false,
pingUrl: false,
multiFlash: false,
flashAbortTimeout: 0,
withCredentials: true,
staticPath: './dist/',
flashUrl: 0, // @default: './FileAPI.flash.swf'
flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
postNameConcat: function (name, idx){
return name + (idx != null ? '['+ idx +']' : '');
},
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 (){
// ngf fix for IE8 #1071
if( api.debug && api._supportConsoleLog ){
if( api._supportConsoleLogApply ){
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 = document.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;
},
isArray: _isArray,
support: {
dnd: cors && ('ondrop' in document.createElement('div')),
cors: cors,
html5: html5,
chunked: chunked,
dataURI: true,
accept: _supportInputAttr('accept'),
multiple: _supportInputAttr('multiple')
},
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: function (){},
parseJSON: function (str){
var json;
if( window.JSON && JSON.parse ){
json = JSON.parse(str);
}
else {
json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
}
return json;
},
trim: function (str){
str = String(str);
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
},
/**
* Simple Defer
* @return {Object}
*/
defer: function (){
var
list = []
, result
, error
, defer = {
resolve: function (err, res){
defer.resolve = noop;
error = err || false;
result = res;
while( res = list.shift() ){
res(error, result);
}
},
then: function (fn){
if( error !== undef ){
fn(error, result);
} else {
list.push(fn);
}
}
};
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,
/**
* 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?
* @param {File} file
* @return {Boolean}
*/
isFile: function (file){
return _toString.call(file) === '[object File]';
},
/**
* Is blob?
* @param {Blob} blob
* @returns {Boolean}
*/
isBlob: function (blob) {
return this.isFile(blob) || (_toString.call(blob) === '[object 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!
_readAs(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);
}, 'DataURL');
}
},
/**
* 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 ){
/** @namespace apiURL.createObjectURL */
var data = apiURL.createObjectURL(file);
if( data === undef ){
_emit(file, fn, 'error');
}
else {
api.readAsImage(data, fn, progress);
}
}
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 });
}
});
}
}
else if( api.isCanvas(file) ){
_emit(file, fn, 'load', file);
}
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 ){
/** @namespace apiURL.revokeObjectURL */
apiURL.revokeObjectURL(file.src);
}
_off(file, events, _fn);
_emit(file, fn, evt, file);
});
}
}
else if( file.iframe ){
_emit(file, fn, { type: 'error' });
}
else {
// Created image
var img = api.newImage(file.dataURL || file);
api.readAsImage(img, fn, progress);
}
},
/**
* Make file by name
*
* @param {String} name
* @return {Array}
*/
checkFileObj: function (name){
var file = {}, accept = api.accept;
if( typeof name == 'object' ){
file = name;
}
else {
file.name = (name + '').split(/\\|\//g).pop();
}
if( file.type == null ){
file.type = file.name.split('.').pop();
}
_each(accept, function (ext, type){
ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
if( ext.test(file.type) || api.ext2mime[file.type] ){
file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
}
});
return file;
},
/**
* 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]
* @return {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 = [api.checkFileObj(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 file size
* @param {Array} files
* @return {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(file.type) ){
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: function (options){
options = _extend({
jsonp: 'callback'
, prepare: api.F
, beforeupload: api.F
, upload: api.F
, fileupload: api.F
, fileprogress: api.F
, filecomplete: api.F
, progress: api.F
, complete: api.F
, pause: api.F
, imageOriginal: true
, chunkSize: api.chunkSize
, chunkUploadRetry: api.chunkUploadRetry
, uploadRetry: api.uploadRetry
}, options);
if( options.imageAutoOrientation && !options.imageTransform ){
options.imageTransform = { rotate: 'auto' };
}
var
proxyXHR = new api.XHR(options)
, dataArray = this._getFilesDataArray(options.files)
, _this = this
, _total = 0
, _loaded = 0
, _nextFile
, _complete = false
;
// calc total size
_each(dataArray, function (data){
_total += data.size;
});
// Array of files
proxyXHR.files = [];
_each(dataArray, function (data){
proxyXHR.files.push(data.file);
});
// Set upload status props
proxyXHR.total = _total;
proxyXHR.loaded = 0;
proxyXHR.filesLeft = dataArray.length;
// emit "beforeupload" event
options.beforeupload(proxyXHR, options);
// Upload by file
_nextFile = function (){
var
data = dataArray.shift()
, _file = data && data.file
, _fileLoaded = false
, _fileOptions = _simpleClone(options)
;
proxyXHR.filesLeft = dataArray.length;
if( _file && _file.name === api.expando ){
_file = null;
api.log('[warn] FileAPI.upload() — called without files');
}
if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
// Mark active job
_complete = false;
// Set current upload file
proxyXHR.currentFile = _file;
// Prepare file options
if (_file && options.prepare(_file, _fileOptions) === false) {
_nextFile.call(_this);
return;
}
_fileOptions.file = _file;
_this._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
options.fileupload(_file, xhr, _fileOptions);
} : noop,
progress: _file ? function (evt){
if( !_fileLoaded ){
// For ignore the double calls.
_fileLoaded = (evt.loaded === evt.total);
// emit "fileprogress" event
options.fileprogress({
type: 'progress'
, total: data.total = evt.total
, loaded: data.loaded = evt.loaded
}, _file, xhr, _fileOptions);
// emit "progress" event
options.progress({
type: 'progress'
, total: _total
, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
}, _file, xhr, _fileOptions);
}
} : noop,
complete: function (err){
_each(_xhrPropsExport, function (name){
proxyXHR[name] = xhr[name];
});
if( _file ){
data.total = (data.total || data.size);
data.loaded = data.total;
if( !err ) {
// emulate 100% "progress"
this.progress(data);
// fixed throttle event
_fileLoaded = true;
// bytes loaded
_loaded += data.size; // data.size != data.total, it's desirable fix this
proxyXHR.loaded = _loaded;
}
// emit "filecomplete" event
options.filecomplete(err, xhr, _file, _fileOptions);
}
// upload next file
setTimeout(function () {_nextFile.call(_this);}, 0);
}
})); // xhr
// ...
proxyXHR.abort = function (current){
if (!current) { dataArray.length = 0; }
this.current = current;
xhr.abort();
};
// Start upload
xhr.send(form);
});
}
else {
var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
// Mark done state
_complete = true;
}
};
// Next tick
setTimeout(_nextFile, 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) {
dataArray.unshift(data);
} else {
dataArray.push(data);
}
});
proxyXHR.statusText = "";
if( _complete ){
_nextFile.call(_this);
}
};
// Removes file from queue by file reference and returns it
proxyXHR.remove = function (file) {
var i = dataArray.length, _file;
while( i-- ){
if( dataArray[i].file == file ){
_file = dataArray.splice(i, 1);
_total -= _file.size;
}
}
return _file;
};
return proxyXHR;
},
_getFilesDataArray: function (data){
var files = [], oFiles = {};
if( isInputFile(data) ){
var tmp = api.getFiles(data);
oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
}
else if( _isArray(data) && isInputFile(data[0]) ){
_each(data, function (input){
oFiles[input.name || 'file'] = api.getFiles(input);
});
}
else {
oFiles = data;
}
_each(oFiles, function add(file, name){
if( _isArray(file) ){
_each(file, function (file){
add(file, name);
});
}
else if( file && (file.name || file.image) ){
files.push({
name: name
, file: file
, size: file.size
, total: file.size
, loaded: 0
});
}
});
if( !files.length ){
// Create fake `file` object
files.push({ file: { name: api.expando } });
}
return files;
},
_getFormData: function (options, data, fn){
var
file = data.file
, name = data.name
, filename = file.name
, filetype = file.type
, trans = api.support.transform && options.imageTransform
, Form = new api.Form
, queue = api.queue(function (){ fn(Form); })
, isOrignTrans = trans && _isOriginTransform(trans)
, postNameConcat = api.postNameConcat
;
// 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);
}
});
(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 = [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);
queue.check();
},
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 remote file
*
* @param {String} url
* @param {Function} fn
* @return {XMLHttpRequest}
*/
load: function (url, fn){
var xhr = api.getXHR();
if( xhr ){
xhr.open('GET', url, true);
if( xhr.overrideMimeType ){
xhr.overrideMimeType('text/plain; charset=x-user-defined');
}
_on(xhr, 'progress', function (/**Event*/evt){
/** @namespace evt.lengthComputable */
if( evt.lengthComputable ){
fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
}
});
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 ){
xhr.onreadystatechange = null;
if( xhr.status == 200 ){
url = url.split('/');
/** @namespace xhr.responseBody */
var file = {
name: url[url.length-1]
, size: xhr.getResponseHeader('Content-Length')
, type: xhr.getResponseHeader('Content-Type')
};
file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
fn({ type: 'load', result: file }, xhr);
}
else {
fn({ type: 'error' }, xhr);
}
}
};
xhr.send(null);
} else {
fn({ type: 'error' });
}
return xhr;
},
encode64: function (str){
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
if( typeof str !== 'string' ){
str = String(str);
}
while( i < str.length ){
//all three "& 0xff" added below are there to fix a known bug
//with bytes returned by xhr.responseText
var
byte1 = str.charCodeAt(i++) & 0xff
, byte2 = str.charCodeAt(i++) & 0xff
, byte3 = str.charCodeAt(i++) & 0xff
, enc1 = byte1 >> 2
, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
, enc3, enc4
;
if( isNaN(byte2) ){
enc3 = enc4 = 64;
} else {
enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
enc4 = isNaN(byte3) ? 64 : byte3 & 63;
}
outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
}
return outStr;
}
} // 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: 'FileReader_not_support_'+as });
}
}
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 _simpleClone(obj){
var copy = {};
_each(obj, function (val, key){
if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
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;
}
// 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 = api.F;
}
if( FileReader ){
// Hover
_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || 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);
}
});
// Drop
_on(el, 'drop', onDrop.ff = onDrop.ff || 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.ff);
_off(el, 'drop', onDrop.ff);
};
// 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
try {
api._supportConsoleLog = !!console.log;
api._supportConsoleLogApply = !!console.log.apply;
} catch (err) {}
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
, 7: 270
, 4: 180
, 5: 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
};
}
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;
while(m.multipass && 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), isFn = typeof params == 'function';
if( isFn ){
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({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
if( !isFn ){
ImgTrans.set({
deg: params.rotate
, overlay: params.overlay
, filter: params.filter
, quality: params.quality || 1
});
}
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);
});
}
};
/**
* For 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 (ex) {
api.log('renderImageToCanvas failed');
throw ex;
}
};
// @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
document = window.document
, FormData = window.FormData
, Form = function (){ this.items = []; }
, encodeURIComponent = window.encodeURIComponent
;
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;
blob.disabled = false;
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="'+ encodeURIComponent(file.file) +'"' : '')
+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
+ '\r\n'
+ '\r\n'+ (file.file ? blob : encodeURIComponent(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 = function (){}
, document = window.document
, XHR = function (options){
this.uid = api.uid();
this.xhr = {
abort: noop
, getResponseHeader: noop
, getAllResponseHeaders: noop
};
this.options = options;
},
_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, options = _this.options;
_this.end =
_this.abort = noop;
_this.status = status;
if( statusText ){
_this.statusText = statusText;
}
api.log('xhr.end:', status, statusText);
options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _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, onloadFuncName = _this.uid + "Load", 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);
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 = window[onloadFuncName] = 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);
window[onloadFuncName] = 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);
}
};
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;" onload="' + onloadFuncName + '()"></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;
// 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;
}
xhr = _this.xhr = api.getXHR();
if (data.params) {
url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
}
xhr.open('POST', url, true);
if( api.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
options.progress({
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){
options.progress(evt, _this, options);
}, 100), false);
}
xhr.onreadystatechange = function (){
_this.status = xhr.status;
_this.statusText = xhr.statusText;
_this.readyState = xhr.readyState;
if( xhr.readyState == 4 ){
for( var k in _xhrResponsePostfix ){
_this['response'+k] = xhr['response'+k];
}
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: {},
/**
* 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);
},
/**
* 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
, 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);
}
},
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) );
},
disableMouseover: false,
mouseover: function (evt){
if (!flash.disableMouseover) {
var target = api.event.fix(evt).target;
if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
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
, height: target.offsetHeight
, 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);
}
},
mouseDown: function(evt) {
flash.disableMouseover = true;
},
cancel: function(evt) {
flash.disableMouseover = false;
},
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){
try {
var
inp = flash.getInput(evt.flashId)
, uid = api.uid(inp)
, files = evt.target.files
, event
;
_each(files, function (file){
api.checkFileObj(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);
}
} finally {
flash.disableMouseover = false;
}
},
interval: null,
cmd: function (id, name, data, last) {
if (flash.uploadInProgress && flash.readInProgress) {
setTimeout(function() {
flash.cmd(id, name, data, last);
}, 100);
} else {
this.cmdFn(id, name, data, last);
}
},
cmdFn: 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.readInProgress = true;
flash.cmd(file, 'readAsBase64', {
id: file.id,
callback: _wrap(function _(err, base64){
flash.readInProgress = false;
_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);
// })
// });
defer.resolve(null, file.info = null);
}
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.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
getResponseHeader: function (name){ return this.headers[name]; },
getAllResponseHeaders: function (){ return this.headers; }
};
var queue = api.queue(function (){
flash.uploadInProgress = true;
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' ){
flash.uploadInProgress = false;
_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' ){
flash.uploadInProgress = false;
_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
;
function getOffset(obj) {
var left, top;
left = top = 0;
if (obj.offsetParent) {
do {
left += obj.offsetLeft;
top += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return {
left : left,
top : top
};
};
return {
top: getOffset(el).top
, left: getOffset(el).left
, width: el.offsetWidth
, height: el.offsetHeight
};
}
// @export
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,
_cameraQueue = [];
if (api.support.flash && (api.media && !api.support.media)) {
(function () {
function _wrap(fn) {
var id = fn.wid = api.uid();
api.Flash._fn[id] = fn;
return 'FileAPI.Flash._fn.' + id;
}
function _unwrap(fn) {
try {
api.Flash._fn[fn.wid] = null;
delete api.Flash._fn[fn.wid];
} catch (e) {
}
}
var flash = api.Flash;
api.extend(api.Flash, {
patchCamera: function () {
api.Camera.fallback = function (el, options, callback) {
var camId = api.uid();
api.log('FlashAPI.Camera.publish: ' + camId);
flash.publish(el, camId, api.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
api.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 = api.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; }); }