mindoc/static/webuploader/webuploader.nolog.js

8047 lines
272 KiB
Go
Raw Permalink Normal View History

2017-04-21 18:20:35 +08:00
/*! WebUploader 0.1.6 */
/**
* @fileOverview [amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
*
* AMD API WebUploader
*/
(function( root, factory ) {
var modules = {},
// 内部require, 简单不完全实现。
// https://github.com/amdjs/amdjs-api/wiki/require
_require = function( deps, callback ) {
var args, len, i;
// 如果deps不是数组则直接返回指定module
if ( typeof deps === 'string' ) {
return getModule( deps );
} else {
args = [];
for( len = deps.length, i = 0; i < len; i++ ) {
args.push( getModule( deps[ i ] ) );
}
return callback.apply( null, args );
}
},
// 内部define暂时不支持不指定id.
_define = function( id, deps, factory ) {
if ( arguments.length === 2 ) {
factory = deps;
deps = null;
}
_require( deps || [], function() {
setModule( id, factory, arguments );
});
},
// 设置module, 兼容CommonJs写法。
setModule = function( id, factory, args ) {
var module = {
exports: factory
},
returned;
if ( typeof factory === 'function' ) {
args.length || (args = [ _require, module.exports, module ]);
returned = factory.apply( null, args );
returned !== undefined && (module.exports = returned);
}
modules[ id ] = module.exports;
},
// 根据id获取module
getModule = function( id ) {
var module = modules[ id ] || root[ id ];
if ( !module ) {
throw new Error( '`' + id + '` is undefined' );
}
return module;
},
// 将所有modules将路径ids装换成对象。
exportsTo = function( obj ) {
var key, host, parts, part, last, ucFirst;
// make the first character upper case.
ucFirst = function( str ) {
return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
};
for ( key in modules ) {
host = obj;
if ( !modules.hasOwnProperty( key ) ) {
continue;
}
parts = key.split('/');
last = ucFirst( parts.pop() );
while( (part = ucFirst( parts.shift() )) ) {
host[ part ] = host[ part ] || {};
host = host[ part ];
}
host[ last ] = modules[ key ];
}
return obj;
},
makeExport = function( dollar ) {
root.__dollar = dollar;
// exports every module.
return exportsTo( factory( root, _define, _require ) );
},
origin;
if ( typeof module === 'object' && typeof module.exports === 'object' ) {
// For CommonJS and CommonJS-like environments where a proper window is present,
module.exports = makeExport();
} else if ( typeof define === 'function' && define.amd ) {
// Allow using this built library as an AMD module
// in another project. That other project will only
// see this AMD call, not the internal modules in
// the closure below.
define([ 'jquery' ], makeExport );
} else {
// Browser globals case. Just assign the
// result to a property on the global.
origin = root.WebUploader;
root.WebUploader = makeExport();
root.WebUploader.noConflict = function() {
root.WebUploader = origin;
};
}
})( window, function( window, define, require ) {
/**
* @fileOverview jQuery or Zepto
* @require "jquery"
* @require "zepto"
*/
define('dollar-third',[],function() {
var req = window.require;
var $ = window.__dollar ||
window.jQuery ||
window.Zepto ||
req('jquery') ||
req('zepto');
if ( !$ ) {
throw new Error('jQuery or Zepto not found!');
}
return $;
});
/**
* @fileOverview Dom
*/
define('dollar',[
'dollar-third'
], function( _ ) {
return _;
});
/**
* @fileOverview 使jQueryPromise
*/
define('promise-third',[
'dollar'
], function( $ ) {
return {
Deferred: $.Deferred,
when: $.when,
isPromise: function( anything ) {
return anything && typeof anything.then === 'function';
}
};
});
/**
* @fileOverview Promise/A+
*/
define('promise',[
'promise-third'
], function( _ ) {
return _;
});
/**
* @fileOverview
*/
/**
* Web Uploader`WebUploader`访
*
* As you know, Web Uploader[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
* module idWebUploader
*
* * module `base`WebUploader.Base
* * module `file`: WebUploader.File
* * module `lib/dnd`: WebUploader.Lib.Dnd
* * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
*
*
* 使`WebUploader`
* @module WebUploader
* @title WebUploader API
*/
define('base',[
'dollar',
'promise'
], function( $, promise ) {
var noop = function() {},
call = Function.call;
// http://jsperf.com/uncurrythis
// 反科里化
function uncurryThis( fn ) {
return function() {
return call.apply( fn, arguments );
};
}
function bindFn( fn, context ) {
return function() {
return fn.apply( context, arguments );
};
}
function createObject( proto ) {
var f;
if ( Object.create ) {
return Object.create( proto );
} else {
f = function() {};
f.prototype = proto;
return new f();
}
}
/**
*
* @class Base
*/
return {
/**
* @property {String} version
*/
version: '0.1.6',
/**
* @property {jQuery|Zepto} $ jQueryZepto
*/
$: $,
Deferred: promise.Deferred,
isPromise: promise.isPromise,
when: promise.when,
/**
* @description
*
* * `webkit` webkitwebkit`undefined`
* * `chrome` chromechrome`undefined`
* * `ie` ieie`undefined`**ie10+**
* * `firefox` firefoxfirefox`undefined`
* * `safari` safarisafari`undefined`
* * `opera` operaopera`undefined`
*
* @property {Object} [browser]
*/
browser: (function( ua ) {
var ret = {},
webkit = ua.match( /WebKit\/([\d.]+)/ ),
chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
ua.match( /CriOS\/([\d.]+)/ ),
ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ),
firefox = ua.match( /Firefox\/([\d.]+)/ ),
safari = ua.match( /Safari\/([\d.]+)/ ),
opera = ua.match( /OPR\/([\d.]+)/ );
webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
ie && (ret.ie = parseFloat( ie[ 1 ] ));
firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
safari && (ret.safari = parseFloat( safari[ 1 ] ));
opera && (ret.opera = parseFloat( opera[ 1 ] ));
return ret;
})( navigator.userAgent ),
/**
* @description
*
* * `android` androidandroid`undefined`
* * `ios` iosios`undefined`
* @property {Object} [os]
*/
os: (function( ua ) {
var ret = {},
// osx = !!ua.match( /\(Macintosh\; Intel / ),
android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
// osx && (ret.osx = true);
android && (ret.android = parseFloat( android[ 1 ] ));
ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
return ret;
})( navigator.userAgent ),
/**
*
* @method inherits
* @grammar Base.inherits( super ) => child
* @grammar Base.inherits( super, protos ) => child
* @grammar Base.inherits( super, protos, statics ) => child
* @param {Class} super
* @param {Object | Function} [protos] constructor
* @param {Function} [protos.constructor]
* @param {Object} [statics]
* @return {Class}
* @example
* function Person() {
* console.log( 'Super' );
* }
* Person.prototype.hello = function() {
* console.log( 'hello' );
* };
*
* var Manager = Base.inherits( Person, {
* world: function() {
* console.log( 'World' );
* }
* });
*
* // 因为没有指定构造器,父类的构造器将会执行。
* var instance = new Manager(); // => Super
*
* // 继承子父类的方法
* instance.hello(); // => hello
* instance.world(); // => World
*
* // 子类的__super__属性指向父类
* console.log( Manager.__super__ === Person ); // => true
*/
inherits: function( Super, protos, staticProtos ) {
var child;
if ( typeof protos === 'function' ) {
child = protos;
protos = null;
} else if ( protos && protos.hasOwnProperty('constructor') ) {
child = protos.constructor;
} else {
child = function() {
return Super.apply( this, arguments );
};
}
// 复制静态方法
$.extend( true, child, Super, staticProtos || {} );
/* jshint camelcase: false */
// 让子类的__super__属性指向父类。
child.__super__ = Super.prototype;
// 构建原型,添加原型方法或属性。
// 暂时用Object.create实现。
child.prototype = createObject( Super.prototype );
protos && $.extend( true, child.prototype, protos );
return child;
},
/**
* callback.
* @method noop
*/
noop: noop,
/**
* `context`
* @grammar Base.bindFn( fn, context ) => Function
* @method bindFn
* @example
* var doSomething = function() {
* console.log( this.name );
* },
* obj = {
* name: 'Object Name'
* },
* aliasFn = Base.bind( doSomething, obj );
*
* aliasFn(); // => Object Name
*
*/
bindFn: bindFn,
/**
* Console.log[noop](#WebUploader:Base.noop)
* @grammar Base.log( args... ) => undefined
* @method log
*/
log: (function() {
if ( window.console ) {
return bindFn( console.log, console );
}
return noop;
})(),
nextTick: (function() {
return function( cb ) {
setTimeout( cb, 1 );
};
// @bug 当浏览器不在当前窗口时就停了。
// var next = window.requestAnimationFrame ||
// window.webkitRequestAnimationFrame ||
// window.mozRequestAnimationFrame ||
// function( cb ) {
// window.setTimeout( cb, 1000 / 60 );
// };
// // fix: Uncaught TypeError: Illegal invocation
// return bindFn( next, window );
})(),
/**
* [uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
*
* @grammar Base.slice( target, start[, end] ) => Array
* @method slice
* @example
* function doSomthing() {
* var args = Base.slice( arguments, 1 );
* console.log( args );
* }
*
* doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"]
*/
slice: uncurryThis( [].slice ),
/**
* ID
* @method guid
* @grammar Base.guid() => String
* @grammar Base.guid( prefx ) => String
*/
guid: (function() {
var counter = 0;
return function( prefix ) {
var guid = (+new Date()).toString( 32 ),
i = 0;
for ( ; i < 5; i++ ) {
guid += Math.floor( Math.random() * 65535 ).toString( 32 );
}
return (prefix || 'wu_') + guid + (counter++).toString( 32 );
};
})(),
/**
* ,
* @method formatSize
* @grammar Base.formatSize( size ) => String
* @grammar Base.formatSize( size, pointLength ) => String
* @grammar Base.formatSize( size, pointLength, units ) => String
* @param {Number} size
* @param {Number} [pointLength=2]
* @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] K()M, K.
* @example
* console.log( Base.formatSize( 100 ) ); // => 100B
* console.log( Base.formatSize( 1024 ) ); // => 1.00K
* console.log( Base.formatSize( 1024, 0 ) ); // => 1K
* console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M
* console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G
* console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB
*/
formatSize: function( size, pointLength, units ) {
var unit;
units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
while ( (unit = units.shift()) && size > 1024 ) {
size = size / 1024;
}
return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
unit;
}
};
});
/**
* 使使
* @fileOverview Mediator
*/
define('mediator',[
'base'
], function( Base ) {
var $ = Base.$,
slice = [].slice,
separator = /\s+/,
protos;
// 根据条件过滤出事件handlers.
function findHandlers( arr, name, callback, context ) {
return $.grep( arr, function( handler ) {
return handler &&
(!name || handler.e === name) &&
(!callback || handler.cb === callback ||
handler.cb._cb === callback) &&
(!context || handler.ctx === context);
});
}
function eachEvent( events, callback, iterator ) {
// 不支持对象只支持多个event用空格隔开
$.each( (events || '').split( separator ), function( _, key ) {
iterator( key, callback );
});
}
function triggerHanders( events, args ) {
var stoped = false,
i = -1,
len = events.length,
handler;
while ( ++i < len ) {
handler = events[ i ];
if ( handler.cb.apply( handler.ctx2, args ) === false ) {
stoped = true;
break;
}
}
return !stoped;
}
protos = {
/**
*
*
* `callback`argumentstrigger
* ```javascript
* var obj = {};
*
* // 使得obj有事件行为
* Mediator.installTo( obj );
*
* obj.on( 'testa', function( arg1, arg2 ) {
* console.log( arg1, arg2 ); // => 'arg1', 'arg2'
* });
*
* obj.trigger( 'testa', 'arg1', 'arg2' );
* ```
*
* `callback``return false``callback`
* `trigger``false`
*
* `on``all`, `callback`arguments
* `type``callback``callback`
* ```javascript
* obj.on( 'all', function( type, arg1, arg2 ) {
* console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
* });
* ```
*
* @method on
* @grammar on( name, callback[, context] ) => self
* @param {String} name
* @param {Function} callback
* @param {Object} [context]
* @return {self} 便
* @chainable
* @class Mediator
*/
on: function( name, callback, context ) {
var me = this,
set;
if ( !callback ) {
return this;
}
set = this._events || (this._events = []);
eachEvent( name, callback, function( name, callback ) {
var handler = { e: name };
handler.cb = callback;
handler.ctx = context;
handler.ctx2 = context || me;
handler.id = set.length;
set.push( handler );
});
return this;
},
/**
* handler
* @method once
* @grammar once( name, callback[, context] ) => self
* @param {String} name
* @param {Function} callback
* @param {Object} [context]
* @return {self} 便
* @chainable
*/
once: function( name, callback, context ) {
var me = this;
if ( !callback ) {
return me;
}
eachEvent( name, callback, function( name, callback ) {
var once = function() {
me.off( name, once );
return callback.apply( context || me, arguments );
};
once._cb = callback;
me.on( name, once, context );
});
return me;
},
/**
*
* @method off
* @grammar off( [name[, callback[, context] ] ] ) => self
* @param {String} [name]
* @param {Function} [callback]
* @param {Object} [context]
* @return {self} 便
* @chainable
*/
off: function( name, cb, ctx ) {
var events = this._events;
if ( !events ) {
return this;
}
if ( !name && !cb && !ctx ) {
this._events = [];
return this;
}
eachEvent( name, cb, function( name, cb ) {
$.each( findHandlers( events, name, cb, ctx ), function() {
delete events[ this.id ];
});
});
return this;
},
/**
*
* @method trigger
* @grammar trigger( name[, args...] ) => self
* @param {String} type
* @param {*} [...]
* @return {Boolean} handlerreturn falsefalse, true
*/
trigger: function( type ) {
var args, events, allEvents;
if ( !this._events || !type ) {
return this;
}
args = slice.call( arguments, 1 );
events = findHandlers( this._events, type );
allEvents = findHandlers( this._events, 'all' );
return triggerHanders( events, args ) &&
triggerHanders( allEvents, arguments );
}
};
/**
* [installTo](#WebUploader:Mediator:installTo)使
*
*
* @class Mediator
*/
return $.extend({
/**
* 使
* @method installTo
* @param {Object} obj
* @return {Object} obj.
*/
installTo: function( obj ) {
return $.extend( obj, protos );
}
}, protos );
});
/**
* @fileOverview Uploader
*/
define('uploader',[
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$;
/**
*
* @class Uploader
* @constructor
* @grammar new Uploader( opts ) => Uploader
* @example
* var uploader = WebUploader.Uploader({
* swf: 'path_of_swf/Uploader.swf',
*
* // 开起分片上传。
* chunked: true
* });
*/
function Uploader( opts ) {
this.options = $.extend( true, {}, Uploader.options, opts );
this._init( this.options );
}
// default Options
// widgets中有相应扩展
Uploader.options = {};
Mediator.installTo( Uploader.prototype );
// 批量添加纯命令式方法。
$.each({
upload: 'start-upload',
stop: 'stop-upload',
getFile: 'get-file',
getFiles: 'get-files',
addFile: 'add-file',
addFiles: 'add-file',
sort: 'sort-files',
removeFile: 'remove-file',
cancelFile: 'cancel-file',
skipFile: 'skip-file',
retry: 'retry',
isInProgress: 'is-in-progress',
makeThumb: 'make-thumb',
md5File: 'md5-file',
getDimension: 'get-dimension',
addButton: 'add-btn',
predictRuntimeType: 'predict-runtime-type',
refresh: 'refresh',
disable: 'disable',
enable: 'enable',
reset: 'reset'
}, function( fn, command ) {
Uploader.prototype[ fn ] = function() {
return this.request( command, arguments );
};
});
$.extend( Uploader.prototype, {
state: 'pending',
_init: function( opts ) {
var me = this;
me.request( 'init', opts, function() {
me.state = 'ready';
me.trigger('ready');
});
},
/**
* Uploader
* @method option
* @grammar option( key ) => *
* @grammar option( key, val ) => self
* @example
*
* // 初始状态图片上传前不会压缩
* var uploader = new WebUploader.Uploader({
* compress: null;
* });
*
* // 修改后图片上传前尝试将图片压缩到1600 * 1600
* uploader.option( 'compress', {
* width: 1600,
* height: 1600
* });
*/
option: function( key, val ) {
var opts = this.options;
// setter
if ( arguments.length > 1 ) {
if ( $.isPlainObject( val ) &&
$.isPlainObject( opts[ key ] ) ) {
$.extend( opts[ key ], val );
} else {
opts[ key ] = val;
}
} else { // getter
return key ? opts[ key ] : opts;
}
},
/**
*
* * `successNum`
* * `progressNum`
* * `cancelNum`
* * `invalidNum`
* * `uploadFailNum`
* * `queueNum`
* * `interruptNum`
* @method getStats
* @grammar getStats() => Object
*/
getStats: function() {
// return this._mgr.getStats.apply( this._mgr, arguments );
var stats = this.request('get-stats');
return stats ? {
successNum: stats.numOfSuccess,
progressNum: stats.numOfProgress,
// who care?
// queueFailNum: 0,
cancelNum: stats.numOfCancel,
invalidNum: stats.numOfInvalid,
uploadFailNum: stats.numOfUploadFailed,
queueNum: stats.numOfQueue,
interruptNum: stats.numofInterrupt
} : {};
},
// 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
trigger: function( type/*, args...*/ ) {
var args = [].slice.call( arguments, 1 ),
opts = this.options,
name = 'on' + type.substring( 0, 1 ).toUpperCase() +
type.substring( 1 );
if (
// 调用通过on方法注册的handler.
Mediator.trigger.apply( this, arguments ) === false ||
// 调用opts.onEvent
$.isFunction( opts[ name ] ) &&
opts[ name ].apply( this, args ) === false ||
// 调用this.onEvent
$.isFunction( this[ name ] ) &&
this[ name ].apply( this, args ) === false ||
// 广播所有uploader的事件。
Mediator.trigger.apply( Mediator,
[ this, type ].concat( args ) ) === false ) {
return false;
}
return true;
},
/**
* webuploader
* @method destroy
* @grammar destroy() => undefined
*/
destroy: function() {
this.request( 'destroy', arguments );
this.off();
},
// widgets/widget.js将补充此方法的详细文档。
request: Base.noop
});
/**
* Uploadernew Uploader( opts );
* @method create
* @class Base
* @static
* @grammar Base.create( opts ) => Uploader
*/
Base.create = Uploader.create = function( opts ) {
return new Uploader( opts );
};
// 暴露Uploader可以通过它来扩展业务逻辑。
Base.Uploader = Uploader;
return Uploader;
});
/**
* @fileOverview RuntimeRuntime,
*/
define('runtime/runtime',[
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$,
factories = {},
// 获取对象的第一个key
getFirstKey = function( obj ) {
for ( var key in obj ) {
if ( obj.hasOwnProperty( key ) ) {
return key;
}
}
return null;
};
// 接口类。
function Runtime( options ) {
this.options = $.extend({
container: document.body
}, options );
this.uid = Base.guid('rt_');
}
$.extend( Runtime.prototype, {
getContainer: function() {
var opts = this.options,
parent, container;
if ( this._container ) {
return this._container;
}
parent = $( opts.container || document.body );
container = $( document.createElement('div') );
container.attr( 'id', 'rt_' + this.uid );
container.css({
position: 'absolute',
top: '0px',
left: '0px',
width: '1px',
height: '1px',
overflow: 'hidden'
});
parent.append( container );
parent.addClass('webuploader-container');
this._container = container;
this._parent = parent;
return container;
},
init: Base.noop,
exec: Base.noop,
destroy: function() {
this._container && this._container.remove();
this._parent && this._parent.removeClass('webuploader-container');
this.off();
}
});
Runtime.orders = 'html5,flash';
/**
* Runtime
* @param {String} type
* @param {Runtime} factory Runtime
*/
Runtime.addRuntime = function( type, factory ) {
factories[ type ] = factory;
};
Runtime.hasRuntime = function( type ) {
return !!(type ? factories[ type ] : getFirstKey( factories ));
};
Runtime.create = function( opts, orders ) {
var type, runtime;
orders = orders || Runtime.orders;
$.each( orders.split( /\s*,\s*/g ), function() {
if ( factories[ this ] ) {
type = this;
return false;
}
});
type = type || getFirstKey( factories );
if ( !type ) {
throw new Error('Runtime Error');
}
runtime = new factories[ type ]( opts );
return runtime;
};
Mediator.installTo( Runtime.prototype );
return Runtime;
});
/**
* @fileOverview RuntimeRuntime,
*/
define('runtime/client',[
'base',
'mediator',
'runtime/runtime'
], function( Base, Mediator, Runtime ) {
var cache;
cache = (function() {
var obj = {};
return {
add: function( runtime ) {
obj[ runtime.uid ] = runtime;
},
get: function( ruid, standalone ) {
var i;
if ( ruid ) {
return obj[ ruid ];
}
for ( i in obj ) {
// 有些类型不能重用比如filepicker.
if ( standalone && obj[ i ].__standalone ) {
continue;
}
return obj[ i ];
}
return null;
},
remove: function( runtime ) {
delete obj[ runtime.uid ];
}
};
})();
function RuntimeClient( component, standalone ) {
var deferred = Base.Deferred(),
runtime;
this.uid = Base.guid('client_');
// 允许runtime没有初始化之前注册一些方法在初始化后执行。
this.runtimeReady = function( cb ) {
return deferred.done( cb );
};
this.connectRuntime = function( opts, cb ) {
// already connected.
if ( runtime ) {
throw new Error('already connected!');
}
deferred.done( cb );
if ( typeof opts === 'string' && cache.get( opts ) ) {
runtime = cache.get( opts );
}
// 像filePicker只能独立存在不能公用。
runtime = runtime || cache.get( null, standalone );
// 需要创建
if ( !runtime ) {
runtime = Runtime.create( opts, opts.runtimeOrder );
runtime.__promise = deferred.promise();
runtime.once( 'ready', deferred.resolve );
runtime.init();
cache.add( runtime );
runtime.__client = 1;
} else {
// 来自cache
Base.$.extend( runtime.options, opts );
runtime.__promise.then( deferred.resolve );
runtime.__client++;
}
standalone && (runtime.__standalone = standalone);
return runtime;
};
this.getRuntime = function() {
return runtime;
};
this.disconnectRuntime = function() {
if ( !runtime ) {
return;
}
runtime.__client--;
if ( runtime.__client <= 0 ) {
cache.remove( runtime );
delete runtime.__promise;
runtime.destroy();
}
runtime = null;
};
this.exec = function() {
if ( !runtime ) {
return;
}
var args = Base.slice( arguments );
component && args.unshift( component );
return runtime.exec.apply( this, args );
};
this.getRuid = function() {
return runtime && runtime.uid;
};
this.destroy = (function( destroy ) {
return function() {
destroy && destroy.apply( this, arguments );
this.trigger('destroy');
this.off();
this.exec('destroy');
this.disconnectRuntime();
};
})( this.destroy );
}
Mediator.installTo( RuntimeClient.prototype );
return RuntimeClient;
});
/**
* @fileOverview
*/
define('lib/dnd',[
'base',
'mediator',
'runtime/client'
], function( Base, Mediator, RuntimeClent ) {
var $ = Base.$;
function DragAndDrop( opts ) {
opts = this.options = $.extend({}, DragAndDrop.options, opts );
opts.container = $( opts.container );
if ( !opts.container.length ) {
return;
}
RuntimeClent.call( this, 'DragAndDrop' );
}
DragAndDrop.options = {
accept: null,
disableGlobalDnd: false
};
Base.inherits( RuntimeClent, {
constructor: DragAndDrop,
init: function() {
var me = this;
me.connectRuntime( me.options, function() {
me.exec('init');
me.trigger('ready');
});
}
});
Mediator.installTo( DragAndDrop.prototype );
return DragAndDrop;
});
/**
* @fileOverview
*/
define('widgets/widget',[
'base',
'uploader'
], function( Base, Uploader ) {
var $ = Base.$,
_init = Uploader.prototype._init,
_destroy = Uploader.prototype.destroy,
IGNORE = {},
widgetClass = [];
function isArrayLike( obj ) {
if ( !obj ) {
return false;
}
var length = obj.length,
type = $.type( obj );
if ( obj.nodeType === 1 && length ) {
return true;
}
return type === 'array' || type !== 'function' && type !== 'string' &&
(length === 0 || typeof length === 'number' && length > 0 &&
(length - 1) in obj);
}
function Widget( uploader ) {
this.owner = uploader;
this.options = uploader.options;
}
$.extend( Widget.prototype, {
init: Base.noop,
// 类Backbone的事件监听声明监听uploader实例上的事件
// widget直接无法监听事件事件只能通过uploader来传递
invoke: function( apiName, args ) {
/*
{
'make-thumb': 'makeThumb'
}
*/
var map = this.responseMap;
// 如果无API响应声明则忽略
if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
!$.isFunction( this[ map[ apiName ] ] ) ) {
return IGNORE;
}
return this[ map[ apiName ] ].apply( this, args );
},
/**
* `callback``handler``promise``handler`promise`promise`
* @method request
* @grammar request( command, args ) => * | Promise
* @grammar request( command, args, callback ) => Promise
* @for Uploader
*/
request: function() {
return this.owner.request.apply( this.owner, arguments );
}
});
// 扩展Uploader.
$.extend( Uploader.prototype, {
/**
* @property {String | Array} [disableWidgets=undefined]
* @namespace options
* @for Uploader
* @description Uploader.register widget option
*/
// 覆写_init用来初始化widgets
_init: function() {
var me = this,
widgets = me._widgets = [],
deactives = me.options.disableWidgets || '';
$.each( widgetClass, function( _, klass ) {
(!deactives || !~deactives.indexOf( klass._name )) &&
widgets.push( new klass( me ) );
});
return _init.apply( me, arguments );
},
request: function( apiName, args, callback ) {
var i = 0,
widgets = this._widgets,
len = widgets && widgets.length,
rlts = [],
dfds = [],
widget, rlt, promise, key;
args = isArrayLike( args ) ? args : [ args ];
for ( ; i < len; i++ ) {
widget = widgets[ i ];
rlt = widget.invoke( apiName, args );
if ( rlt !== IGNORE ) {
// Deferred对象
if ( Base.isPromise( rlt ) ) {
dfds.push( rlt );
} else {
rlts.push( rlt );
}
}
}
// 如果有callback则用异步方式。
if ( callback || dfds.length ) {
promise = Base.when.apply( Base, dfds );
key = promise.pipe ? 'pipe' : 'then';
// 很重要不能删除。删除了会死循环。
// 保证执行顺序。让callback总是在下一个 tick 中执行。
return promise[ key ](function() {
var deferred = Base.Deferred(),
args = arguments;
if ( args.length === 1 ) {
args = args[ 0 ];
}
setTimeout(function() {
deferred.resolve( args );
}, 1 );
return deferred.promise();
})[ callback ? key : 'done' ]( callback || Base.noop );
} else {
return rlts[ 0 ];
}
},
destroy: function() {
_destroy.apply( this, arguments );
this._widgets = null;
}
});
/**
*
* @grammar Uploader.register(proto);
* @grammar Uploader.register(map, proto);
* @param {object} responseMap API
* @param {object} proto constructor
* @method Uploader.register
* @for Uploader
* @example
* Uploader.register({
* 'make-thumb': 'makeThumb'
* }, {
* init: function( options ) {},
* makeThumb: function() {}
* });
*
* Uploader.register({
* 'make-thumb': function() {
*
* }
* });
*/
Uploader.register = Widget.register = function( responseMap, widgetProto ) {
var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
klass;
if ( arguments.length === 1 ) {
widgetProto = responseMap;
// 自动生成 map 表。
$.each(widgetProto, function(key) {
if ( key[0] === '_' || key === 'name' ) {
key === 'name' && (map.name = widgetProto.name);
return;
}
map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
});
} else {
map = $.extend( map, responseMap );
}
widgetProto.responseMap = map;
klass = Base.inherits( Widget, widgetProto );
klass._name = map.name;
widgetClass.push( klass );
return klass;
};
/**
*
* @grammar Uploader.unRegister(name);
* @param {string} name
* @method Uploader.unRegister
* @for Uploader
* @example
*
* Uploader.register({
* name: 'custom',
*
* 'make-thumb': function() {
*
* }
* });
*
* Uploader.unRegister('custom');
*/
Uploader.unRegister = Widget.unRegister = function( name ) {
if ( !name || name === 'anonymous' ) {
return;
}
// 删除指定的插件。
for ( var i = widgetClass.length; i--; ) {
if ( widgetClass[i]._name === name ) {
widgetClass.splice(i, 1)
}
}
};
return Widget;
});
/**
* @fileOverview DragAndDrop Widget
*/
define('widgets/filednd',[
'base',
'uploader',
'lib/dnd',
'widgets/widget'
], function( Base, Uploader, Dnd ) {
var $ = Base.$;
Uploader.options.dnd = '';
/**
* @property {Selector} [dnd=undefined] Drag And Drop
* @namespace options
* @for Uploader
*/
/**
* @property {Selector} [disableGlobalDnd=false]
* @namespace options
* @for Uploader
*/
/**
* @event dndAccept
* @param {DataTransferItemList} items DataTransferItem
* @description chrome API mime-type
* @for Uploader
*/
return Uploader.register({
name: 'dnd',
init: function( opts ) {
if ( !opts.dnd ||
this.request('predict-runtime-type') !== 'html5' ) {
return;
}
var me = this,
deferred = Base.Deferred(),
options = $.extend({}, {
disableGlobalDnd: opts.disableGlobalDnd,
container: opts.dnd,
accept: opts.accept
}),
dnd;
this.dnd = dnd = new Dnd( options );
dnd.once( 'ready', deferred.resolve );
dnd.on( 'drop', function( files ) {
me.request( 'add-file', [ files ]);
});
// 检测文件是否全部允许添加。
dnd.on( 'accept', function( items ) {
return me.owner.trigger( 'dndAccept', items );
});
dnd.init();
return deferred.promise();
},
destroy: function() {
this.dnd && this.dnd.destroy();
}
});
});
/**
* @fileOverview
*/
define('lib/filepaste',[
'base',
'mediator',
'runtime/client'
], function( Base, Mediator, RuntimeClent ) {
var $ = Base.$;
function FilePaste( opts ) {
opts = this.options = $.extend({}, opts );
opts.container = $( opts.container || document.body );
RuntimeClent.call( this, 'FilePaste' );
}
Base.inherits( RuntimeClent, {
constructor: FilePaste,
init: function() {
var me = this;
me.connectRuntime( me.options, function() {
me.exec('init');
me.trigger('ready');
});
}
});
Mediator.installTo( FilePaste.prototype );
return FilePaste;
});
/**
* @fileOverview
*/
define('widgets/filepaste',[
'base',
'uploader',
'lib/filepaste',
'widgets/widget'
], function( Base, Uploader, FilePaste ) {
var $ = Base.$;
/**
* @property {Selector} [paste=undefined] paste`document.body`.
* @namespace options
* @for Uploader
*/
return Uploader.register({
name: 'paste',
init: function( opts ) {
if ( !opts.paste ||
this.request('predict-runtime-type') !== 'html5' ) {
return;
}
var me = this,
deferred = Base.Deferred(),
options = $.extend({}, {
container: opts.paste,
accept: opts.accept
}),
paste;
this.paste = paste = new FilePaste( options );
paste.once( 'ready', deferred.resolve );
paste.on( 'paste', function( files ) {
me.owner.request( 'add-file', [ files ]);
});
paste.init();
return deferred.promise();
},
destroy: function() {
this.paste && this.paste.destroy();
}
});
});
/**
* @fileOverview Blob
*/
define('lib/blob',[
'base',
'runtime/client'
], function( Base, RuntimeClient ) {
function Blob( ruid, source ) {
var me = this;
me.source = source;
me.ruid = ruid;
this.size = source.size || 0;
// 如果没有指定 mimetype, 但是知道文件后缀。
if ( !source.type && this.ext &&
~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) {
this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);
} else {
this.type = source.type || 'application/octet-stream';
}
RuntimeClient.call( me, 'Blob' );
this.uid = source.uid || this.uid;
if ( ruid ) {
me.connectRuntime( ruid );
}
}
Base.inherits( RuntimeClient, {
constructor: Blob,
slice: function( start, end ) {
return this.exec( 'slice', start, end );
},
getSource: function() {
return this.source;
}
});
return Blob;
});
/**
* FlashFileHTML5File
* FlashFileHTML5File
* @fileOverview File
*/
define('lib/file',[
'base',
'lib/blob'
], function( Base, Blob ) {
var uid = 1,
rExt = /\.([^.]+)$/;
function File( ruid, file ) {
var ext;
this.name = file.name || ('untitled' + uid++);
ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
// todo 支持其他类型文件的转换。
// 如果有 mimetype, 但是文件名里面没有找出后缀规律
if ( !ext && file.type ) {
ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ?
RegExp.$1.toLowerCase() : '';
this.name += '.' + ext;
}
this.ext = ext;
this.lastModifiedDate = file.lastModifiedDate ||
(new Date()).toLocaleString();
Blob.apply( this, arguments );
}
return Base.inherits( Blob, File );
});
/**
* @fileOverview
*/
define('lib/filepicker',[
'base',
'runtime/client',
'lib/file'
], function( Base, RuntimeClient, File ) {
var $ = Base.$;
function FilePicker( opts ) {
opts = this.options = $.extend({}, FilePicker.options, opts );
opts.container = $( opts.id );
if ( !opts.container.length ) {
throw new Error('');
}
opts.innerHTML = opts.innerHTML || opts.label ||
opts.container.html() || '';
opts.button = $( opts.button || document.createElement('div') );
opts.button.html( opts.innerHTML );
opts.container.html( opts.button );
RuntimeClient.call( this, 'FilePicker', true );
}
FilePicker.options = {
button: null,
container: null,
label: null,
innerHTML: null,
multiple: true,
accept: null,
name: 'file',
style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick"
};
Base.inherits( RuntimeClient, {
constructor: FilePicker,
init: function() {
var me = this,
opts = me.options,
button = opts.button,
style = opts.style;
if (style)
button.addClass('webuploader-pick');
me.on( 'all', function( type ) {
var files;
switch ( type ) {
case 'mouseenter':
if (style)
button.addClass('webuploader-pick-hover');
break;
case 'mouseleave':
if (style)
button.removeClass('webuploader-pick-hover');
break;
case 'change':
files = me.exec('getFiles');
me.trigger( 'select', $.map( files, function( file ) {
file = new File( me.getRuid(), file );
// 记录来源。
file._refer = opts.container;
return file;
}), opts.container );
break;
}
});
me.connectRuntime( opts, function() {
me.refresh();
me.exec( 'init', opts );
me.trigger('ready');
});
this._resizeHandler = Base.bindFn( this.refresh, this );
$( window ).on( 'resize', this._resizeHandler );
},
refresh: function() {
var shimContainer = this.getRuntime().getContainer(),
button = this.options.button,
width = button.outerWidth ?
button.outerWidth() : button.width(),
height = button.outerHeight ?
button.outerHeight() : button.height(),
pos = button.offset();
width && height && shimContainer.css({
bottom: 'auto',
right: 'auto',
width: width + 'px',
height: height + 'px'
}).offset( pos );
},
enable: function() {
var btn = this.options.button;
btn.removeClass('webuploader-pick-disable');
this.refresh();
},
disable: function() {
var btn = this.options.button;
this.getRuntime().getContainer().css({
top: '-99999px'
});
btn.addClass('webuploader-pick-disable');
},
destroy: function() {
var btn = this.options.button;
$( window ).off( 'resize', this._resizeHandler );
btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' +
'webuploader-pick');
}
});
return FilePicker;
});
/**
* @fileOverview
*/
define('widgets/filepicker',[
'base',
'uploader',
'lib/filepicker',
'widgets/widget'
], function( Base, Uploader, FilePicker ) {
var $ = Base.$;
$.extend( Uploader.options, {
/**
* @property {Selector | Object} [pick=undefined]
* @namespace options
* @for Uploader
* @description
*
* * `id` {Seletor|dom} **** id, id, class, dom
* * `label` {String} `innerHTML`
* * `innerHTML` {String}
* * `multiple` {Boolean}
*/
pick: null,
/**
* @property {Arroy} [accept=null]
* @namespace options
* @for Uploader
* @description extmimeType
*
* * `title` {String}
* * `extensions` {String}
* * `mimeTypes` {String}
*
*
*
* ```
* {
* title: 'Images',
* extensions: 'gif,jpg,jpeg,bmp,png',
* mimeTypes: 'image/*'
* }
* ```
*/
accept: null/*{
title: 'Images',
extensions: 'gif,jpg,jpeg,bmp,png',
mimeTypes: 'image/*'
}*/
});
return Uploader.register({
name: 'picker',
init: function( opts ) {
this.pickers = [];
return opts.pick && this.addBtn( opts.pick );
},
refresh: function() {
$.each( this.pickers, function() {
this.refresh();
});
},
/**
* @method addBtn
* @for Uploader
* @grammar addBtn( pick ) => Promise
* @description
* [options.pick](#WebUploader:Uploader:options)
* @example
* uploader.addBtn({
* id: '#btnContainer',
* innerHTML: ''
* });
*/
addBtn: function( pick ) {
var me = this,
opts = me.options,
accept = opts.accept,
promises = [];
if ( !pick ) {
return;
}
$.isPlainObject( pick ) || (pick = {
id: pick
});
$( pick.id ).each(function() {
var options, picker, deferred;
deferred = Base.Deferred();
options = $.extend({}, pick, {
accept: $.isPlainObject( accept ) ? [ accept ] : accept,
swf: opts.swf,
runtimeOrder: opts.runtimeOrder,
id: this
});
picker = new FilePicker( options );
picker.once( 'ready', deferred.resolve );
picker.on( 'select', function( files ) {
me.owner.request( 'add-file', [ files ]);
});
picker.on('dialogopen', function() {
me.owner.trigger('dialogOpen', picker.button);
});
picker.init();
me.pickers.push( picker );
promises.push( deferred.promise() );
});
return Base.when.apply( Base, promises );
},
disable: function() {
$.each( this.pickers, function() {
this.disable();
});
},
enable: function() {
$.each( this.pickers, function() {
this.enable();
});
},
destroy: function() {
$.each( this.pickers, function() {
this.destroy();
});
this.pickers = null;
}
});
});
/**
* @fileOverview Image
*/
define('lib/image',[
'base',
'runtime/client',
'lib/blob'
], function( Base, RuntimeClient, Blob ) {
var $ = Base.$;
// 构造器。
function Image( opts ) {
this.options = $.extend({}, Image.options, opts );
RuntimeClient.call( this, 'Image' );
this.on( 'load', function() {
this._info = this.exec('info');
this._meta = this.exec('meta');
});
}
// 默认选项。
Image.options = {
// 默认的图片处理质量
quality: 90,
// 是否裁剪
crop: false,
// 是否保留头部信息
preserveHeaders: false,
// 是否允许放大。
allowMagnify: false
};
// 继承RuntimeClient.
Base.inherits( RuntimeClient, {
constructor: Image,
info: function( val ) {
// setter
if ( val ) {
this._info = val;
return this;
}
// getter
return this._info;
},
meta: function( val ) {
// setter
if ( val ) {
this._meta = val;
return this;
}
// getter
return this._meta;
},
loadFromBlob: function( blob ) {
var me = this,
ruid = blob.getRuid();
this.connectRuntime( ruid, function() {
me.exec( 'init', me.options );
me.exec( 'loadFromBlob', blob );
});
},
resize: function() {
var args = Base.slice( arguments );
return this.exec.apply( this, [ 'resize' ].concat( args ) );
},
crop: function() {
var args = Base.slice( arguments );
return this.exec.apply( this, [ 'crop' ].concat( args ) );
},
getAsDataUrl: function( type ) {
return this.exec( 'getAsDataUrl', type );
},
getAsBlob: function( type ) {
var blob = this.exec( 'getAsBlob', type );
return new Blob( this.getRuid(), blob );
}
});
return Image;
});
/**
* @fileOverview ,
*/
define('widgets/image',[
'base',
'uploader',
'lib/image',
'widgets/widget'
], function( Base, Uploader, Image ) {
var $ = Base.$,
throttle;
// 根据要处理的文件大小来节流,一次不能处理太多,会卡。
throttle = (function( max ) {
var occupied = 0,
waiting = [],
tick = function() {
var item;
while ( waiting.length && occupied < max ) {
item = waiting.shift();
occupied += item[ 0 ];
item[ 1 ]();
}
};
return function( emiter, size, cb ) {
waiting.push([ size, cb ]);
emiter.once( 'destroy', function() {
occupied -= size;
setTimeout( tick, 1 );
});
setTimeout( tick, 1 );
};
})( 5 * 1024 * 1024 );
$.extend( Uploader.options, {
/**
* @property {Object} [thumb]
* @namespace options
* @for Uploader
* @description
*
*
*
* ```javascript
* {
* width: 110,
* height: 110,
*
* // 图片质量只有type为`image/jpeg`的时候才有效。
* quality: 70,
*
* // 是否允许放大如果想要生成小图的时候不失真此选项应该设置为false.
* allowMagnify: true,
*
* // 是否允许裁剪。
* crop: true,
*
* // 为空的话则保留原有图片格式。
* // 否则强制转换成指定的类型。
* type: 'image/jpeg'
* }
* ```
*/
thumb: {
width: 110,
height: 110,
quality: 70,
allowMagnify: true,
crop: true,
preserveHeaders: false,
// 为空的话则保留原有图片格式。
// 否则强制转换成指定的类型。
// IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
// 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
type: 'image/jpeg'
},
/**
* @property {Object} [compress]
* @namespace options
* @for Uploader
* @description `false`,
*
*
*
* ```javascript
* {
* width: 1600,
* height: 1600,
*
* // 图片质量只有type为`image/jpeg`的时候才有效。
* quality: 90,
*
* // 是否允许放大如果想要生成小图的时候不失真此选项应该设置为false.
* allowMagnify: false,
*
* // 是否允许裁剪。
* crop: false,
*
* // 是否保留头部meta信息。
* preserveHeaders: true,
*
* // 如果发现压缩后文件大小比原来还大,则使用原来图片
* // 此属性可能会影响图片自动纠正功能
* noCompressIfLarger: false,
*
* // 单位字节,如果图片大小小于此值,不会采用压缩。
* compressSize: 0
* }
* ```
*/
compress: {
width: 1600,
height: 1600,
quality: 90,
allowMagnify: false,
crop: false,
preserveHeaders: true
}
});
return Uploader.register({
name: 'image',
/**
* `callback`
*
*
* width height 0 - 1 使
*
* `callback`
* * errorerror
* * ret, Data URL
*
* ****
* Date URLIE6/7
* base64
*
* @method makeThumb
* @grammar makeThumb( file, callback ) => undefined
* @grammar makeThumb( file, callback, width, height ) => undefined
* @for Uploader
* @example
*
* uploader.on( 'fileQueued', function( file ) {
* var $li = ...;
*
* uploader.makeThumb( file, function( error, ret ) {
* if ( error ) {
* $li.text('');
* } else {
* $li.append('<img alt="" src="' + ret + '" />');
* }
* });
*
* });
*/
makeThumb: function( file, cb, width, height ) {
var opts, image;
file = this.request( 'get-file', file );
// 只预览图片格式。
if ( !file.type.match( /^image/ ) ) {
cb( true );
return;
}
opts = $.extend({}, this.options.thumb );
// 如果传入的是object.
if ( $.isPlainObject( width ) ) {
opts = $.extend( opts, width );
width = null;
}
width = width || opts.width;
height = height || opts.height;
image = new Image( opts );
image.once( 'load', function() {
file._info = file._info || image.info();
file._meta = file._meta || image.meta();
// 如果 width 的值介于 0 - 1
// 说明设置的是百分比。
if ( width <= 1 && width > 0 ) {
width = file._info.width * width;
}
// 同样的规则应用于 height
if ( height <= 1 && height > 0 ) {
height = file._info.height * height;
}
image.resize( width, height );
});
// 当 resize 完后
image.once( 'complete', function() {
cb( false, image.getAsDataUrl( opts.type ) );
image.destroy();
});
image.once( 'error', function( reason ) {
cb( reason || true );
image.destroy();
});
throttle( image, file.source.size, function() {
file._info && image.info( file._info );
file._meta && image.meta( file._meta );
image.loadFromBlob( file.source );
});
},
beforeSendFile: function( file ) {
var opts = this.options.compress || this.options.resize,
compressSize = opts && opts.compressSize || 0,
noCompressIfLarger = opts && opts.noCompressIfLarger || false,
image, deferred;
file = this.request( 'get-file', file );
// 只压缩 jpeg 图片格式。
// gif 可能会丢失针
// bmp png 基本上尺寸都不大,且压缩比比较小。
if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||
file.size < compressSize ||
file._compressed ) {
return;
}
opts = $.extend({}, opts );
deferred = Base.Deferred();
image = new Image( opts );
deferred.always(function() {
image.destroy();
image = null;
});
image.once( 'error', deferred.reject );
image.once( 'load', function() {
var width = opts.width,
height = opts.height;
file._info = file._info || image.info();
file._meta = file._meta || image.meta();
// 如果 width 的值介于 0 - 1
// 说明设置的是百分比。
if ( width <= 1 && width > 0 ) {
width = file._info.width * width;
}
// 同样的规则应用于 height
if ( height <= 1 && height > 0 ) {
height = file._info.height * height;
}
image.resize( width, height );
});
image.once( 'complete', function() {
var blob, size;
// 移动端 UC / qq 浏览器的无图模式下
// ctx.getImageData 处理大图的时候会报 Exception
// INDEX_SIZE_ERR: DOM Exception 1
try {
blob = image.getAsBlob( opts.type );
size = file.size;
// 如果压缩后,比原来还大则不用压缩后的。
if ( !noCompressIfLarger || blob.size < size ) {
// file.source.destroy && file.source.destroy();
file.source = blob;
file.size = blob.size;
file.trigger( 'resize', blob.size, size );
}
// 标记,避免重复压缩。
file._compressed = true;
deferred.resolve();
} catch ( e ) {
// 出错了直接继续,让其上传原始图片
deferred.resolve();
}
});
file._info && image.info( file._info );
file._meta && image.meta( file._meta );
image.loadFromBlob( file.source );
return deferred.promise();
}
});
});
/**
* @fileOverview
*/
define('file',[
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$,
idPrefix = 'WU_FILE_',
idSuffix = 0,
rExt = /\.([^.]+)$/,
statusMap = {};
function gid() {
return idPrefix + idSuffix++;
}
/**
*
* @class File
* @constructor
* @grammar new File( source ) => File
* @param {Lib.File} source [lib.File](#Lib.File), sourceRuntime
*/
function WUFile( source ) {
/**
*
* @property name
* @type {string}
*/
this.name = source.name || 'Untitled';
/**
*
* @property size
* @type {uint}
* @default 0
*/
this.size = source.size || 0;
/**
* MIMETYPE[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
* @property type
* @type {string}
* @default 'application/octet-stream'
*/
this.type = source.type || 'application/octet-stream';
/**
*
* @property lastModifiedDate
* @type {int}
* @default
*/
this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
/**
* IDID
* @property id
* @type {string}
*/
this.id = gid();
/**
* test.pngpng
* @property ext
* @type {string}
*/
this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
/**
* status
* @property statusText
* @type {string}
*/
this.statusText = '';
// 存储文件状态,防止通过属性直接修改
statusMap[ this.id ] = WUFile.Status.INITED;
this.source = source;
this.loaded = 0;
this.on( 'error', function( msg ) {
this.setStatus( WUFile.Status.ERROR, msg );
});
}
$.extend( WUFile.prototype, {
/**
* `change`
* @method setStatus
* @grammar setStatus( status[, statusText] );
* @param {File.Status|String} status [](#WebUploader:File:File.Status)
* @param {String} [statusText=''] error使http, abort,server
*/
setStatus: function( status, text ) {
var prevStatus = statusMap[ this.id ];
typeof text !== 'undefined' && (this.statusText = text);
if ( status !== prevStatus ) {
statusMap[ this.id ] = status;
/**
*
* @event statuschange
*/
this.trigger( 'statuschange', status, prevStatus );
}
},
/**
*
* @return {File.Status}
* @example
{
// 初始化
INITED: 0,
// 已入队列
QUEUED: 1,
// 正在上传
PROGRESS: 2,
// 上传出错
ERROR: 3,
// 上传成功
COMPLETE: 4,
// 上传取消
CANCELLED: 5
}
*/
getStatus: function() {
return statusMap[ this.id ];
},
/**
*
* @return {*}
*/
getSource: function() {
return this.source;
},
destroy: function() {
this.off();
delete statusMap[ this.id ];
}
});
Mediator.installTo( WUFile.prototype );
/**
*
* * `inited`
* * `queued` ,
* * `progress`
* * `complete`
* * `error`
* * `interrupt`
* * `invalid`
* * `cancelled`
* @property {Object} Status
* @namespace File
* @class File
* @static
*/
WUFile.Status = {
INITED: 'inited', // 初始状态
QUEUED: 'queued', // 已经进入队列, 等待上传
PROGRESS: 'progress', // 上传中
ERROR: 'error', // 上传出错,可重试
COMPLETE: 'complete', // 上传完成。
CANCELLED: 'cancelled', // 上传取消。
INTERRUPT: 'interrupt', // 上传中断,可续传。
INVALID: 'invalid' // 文件不合格,不能重试上传。
};
return WUFile;
});
/**
* @fileOverview
*/
define('queue',[
'base',
'mediator',
'file'
], function( Base, Mediator, WUFile ) {
var $ = Base.$,
STATUS = WUFile.Status;
/**
* ,
* @class Queue
* @extends Mediator
*/
function Queue() {
/**
*
* * `numOfQueue`
* * `numOfSuccess`
* * `numOfCancel`
* * `numOfProgress`
* * `numOfUploadFailed`
* * `numOfInvalid`
* * `numofDeleted`
* @property {Object} stats
*/
this.stats = {
numOfQueue: 0,
numOfSuccess: 0,
numOfCancel: 0,
numOfProgress: 0,
numOfUploadFailed: 0,
numOfInvalid: 0,
numofDeleted: 0,
numofInterrupt: 0
};
// 上传队列,仅包括等待上传的文件
this._queue = [];
// 存储所有文件
this._map = {};
}
$.extend( Queue.prototype, {
/**
*
*
* @method append
* @param {File} file
*/
append: function( file ) {
this._queue.push( file );
this._fileAdded( file );
return this;
},
/**
*
*
* @method prepend
* @param {File} file
*/
prepend: function( file ) {
this._queue.unshift( file );
this._fileAdded( file );
return this;
},
/**
*
*
* @method getFile
* @param {String} fileId ID
* @return {File}
*/
getFile: function( fileId ) {
if ( typeof fileId !== 'string' ) {
return fileId;
}
return this._map[ fileId ];
},
/**
*
* @grammar fetch( status ) => File
* @method fetch
* @param {String} status [](#WebUploader:File:File.Status)
* @return {File} [File](#WebUploader:File)
*/
fetch: function( status ) {
var len = this._queue.length,
i, file;
status = status || STATUS.QUEUED;
for ( i = 0; i < len; i++ ) {
file = this._queue[ i ];
if ( status === file.getStatus() ) {
return file;
}
}
return null;
},
/**
*
* @grammar sort( fn ) => undefined
* @method sort
* @param {Function} fn
*/
sort: function( fn ) {
if ( typeof fn === 'function' ) {
this._queue.sort( fn );
}
},
/**
* , [File](#WebUploader:File)
* @grammar getFiles( [status1[, status2 ...]] ) => Array
* @method getFiles
* @param {String} [status] [](#WebUploader:File:File.Status)
*/
getFiles: function() {
var sts = [].slice.call( arguments, 0 ),
ret = [],
i = 0,
len = this._queue.length,
file;
for ( ; i < len; i++ ) {
file = this._queue[ i ];
if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
continue;
}
ret.push( file );
}
return ret;
},
/**
*
* @grammar removeFile( file ) => Array
* @method removeFile
* @param {File}
*/
removeFile: function( file ) {
var me = this,
existing = this._map[ file.id ];
if ( existing ) {
delete this._map[ file.id ];
file.destroy();
this.stats.numofDeleted++;
}
},
_fileAdded: function( file ) {
var me = this,
existing = this._map[ file.id ];
if ( !existing ) {
this._map[ file.id ] = file;
file.on( 'statuschange', function( cur, pre ) {
me._onFileStatusChange( cur, pre );
});
}
},
_onFileStatusChange: function( curStatus, preStatus ) {
var stats = this.stats;
switch ( preStatus ) {
case STATUS.PROGRESS:
stats.numOfProgress--;
break;
case STATUS.QUEUED:
stats.numOfQueue --;
break;
case STATUS.ERROR:
stats.numOfUploadFailed--;
break;
case STATUS.INVALID:
stats.numOfInvalid--;
break;
case STATUS.INTERRUPT:
stats.numofInterrupt--;
break;
}
switch ( curStatus ) {
case STATUS.QUEUED:
stats.numOfQueue++;
break;
case STATUS.PROGRESS:
stats.numOfProgress++;
break;
case STATUS.ERROR:
stats.numOfUploadFailed++;
break;
case STATUS.COMPLETE:
stats.numOfSuccess++;
break;
case STATUS.CANCELLED:
stats.numOfCancel++;
break;
case STATUS.INVALID:
stats.numOfInvalid++;
break;
case STATUS.INTERRUPT:
stats.numofInterrupt++;
break;
}
}
});
Mediator.installTo( Queue.prototype );
return Queue;
});
/**
* @fileOverview
*/
define('widgets/queue',[
'base',
'uploader',
'queue',
'file',
'lib/file',
'runtime/client',
'widgets/widget'
], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
var $ = Base.$,
rExt = /\.\w+$/,
Status = WUFile.Status;
return Uploader.register({
name: 'queue',
init: function( opts ) {
var me = this,
deferred, len, i, item, arr, accept, runtime;
if ( $.isPlainObject( opts.accept ) ) {
opts.accept = [ opts.accept ];
}
// accept中的中生成匹配正则。
if ( opts.accept ) {
arr = [];
for ( i = 0, len = opts.accept.length; i < len; i++ ) {
item = opts.accept[ i ].extensions;
item && arr.push( item );
}
if ( arr.length ) {
accept = '\\.' + arr.join(',')
.replace( /,/g, '$|\\.' )
.replace( /\*/g, '.*' ) + '$';
}
me.accept = new RegExp( accept, 'i' );
}
me.queue = new Queue();
me.stats = me.queue.stats;
// 如果当前不是html5运行时那就算了。
// 不执行后续操作
if ( this.request('predict-runtime-type') !== 'html5' ) {
return;
}
// 创建一个 html5 运行时的 placeholder
// 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
deferred = Base.Deferred();
this.placeholder = runtime = new RuntimeClient('Placeholder');
runtime.connectRuntime({
runtimeOrder: 'html5'
}, function() {
me._ruid = runtime.getRuid();
deferred.resolve();
});
return deferred.promise();
},
// 为了支持外部直接添加一个原生File对象。
_wrapFile: function( file ) {
if ( !(file instanceof WUFile) ) {
if ( !(file instanceof File) ) {
if ( !this._ruid ) {
throw new Error('Can\'t add external files.');
}
file = new File( this._ruid, file );
}
file = new WUFile( file );
}
return file;
},
// 判断文件是否可以被加入队列
acceptFile: function( file ) {
var invalid = !file || !file.size || this.accept &&
// 如果名字中有后缀,才做后缀白名单处理。
rExt.exec( file.name ) && !this.accept.test( file.name );
return !invalid;
},
/**
* @event beforeFileQueued
* @param {File} file File
* @description handler`false`
* @for Uploader
*/
/**
* @event fileQueued
* @param {File} file File
* @description
* @for Uploader
*/
_addFile: function( file ) {
var me = this;
file = me._wrapFile( file );
// 不过类型判断允许不允许,先派送 `beforeFileQueued`
if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
return;
}
// 类型不匹配,则派送错误事件,并返回。
if ( !me.acceptFile( file ) ) {
me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
return;
}
me.queue.append( file );
me.owner.trigger( 'fileQueued', file );
return file;
},
getFile: function( fileId ) {
return this.queue.getFile( fileId );
},
/**
* @event filesQueued
* @param {File} files File(lib/File
* @description
* @for Uploader
*/
/**
* @property {Boolean} [auto=false]
* @namespace options
* @for Uploader
* @description true
*
*/
/**
* @method addFiles
* @grammar addFiles( file ) => undefined
* @grammar addFiles( [file1, file2 ...] ) => undefined
* @param {Array of File or File} [files] Files
* @description
* @for Uploader
*/
addFile: function( files ) {
var me = this;
if ( !files.length ) {
files = [ files ];
}
files = $.map( files, function( file ) {
return me._addFile( file );
});
if ( files.length ) {
me.owner.trigger( 'filesQueued', files );
if ( me.options.auto ) {
setTimeout(function() {
me.request('start-upload');
}, 20 );
}
}
},
getStats: function() {
return this.stats;
},
/**
* @event fileDequeued
* @param {File} file File
* @description
* @for Uploader
*/
/**
* @method removeFile
* @grammar removeFile( file ) => undefined
* @grammar removeFile( id ) => undefined
* @grammar removeFile( file, true ) => undefined
* @grammar removeFile( id, true ) => undefined
* @param {File|id} file FileFileid
* @description , `true` queue
* @for Uploader
* @example
*
* $li.on('click', '.remove-this', function() {
* uploader.removeFile( file );
* })
*/
removeFile: function( file, remove ) {
var me = this;
file = file.id ? file : me.queue.getFile( file );
this.request( 'cancel-file', file );
if ( remove ) {
this.queue.removeFile( file );
}
},
/**
* @method getFiles
* @grammar getFiles() => Array
* @grammar getFiles( status1, status2, status... ) => Array
* @description
* @for Uploader
* @example
* console.log( uploader.getFiles() ); // => all files
* console.log( uploader.getFiles('error') ) // => all error files.
*/
getFiles: function() {
return this.queue.getFiles.apply( this.queue, arguments );
},
fetchFile: function() {
return this.queue.fetch.apply( this.queue, arguments );
},
/**
* @method retry
* @grammar retry() => undefined
* @grammar retry( file ) => undefined
* @description
* @for Uploader
* @example
* function retry() {
* uploader.retry();
* }
*/
retry: function( file, noForceStart ) {
var me = this,
files, i, len;
if ( file ) {
file = file.id ? file : me.queue.getFile( file );
file.setStatus( Status.QUEUED );
noForceStart || me.request('start-upload');
return;
}
files = me.queue.getFiles( Status.ERROR );
i = 0;
len = files.length;
for ( ; i < len; i++ ) {
file = files[ i ];
file.setStatus( Status.QUEUED );
}
me.request('start-upload');
},
/**
* @method sort
* @grammar sort( fn ) => undefined
* @description
* @for Uploader
*/
sortFiles: function() {
return this.queue.sort.apply( this.queue, arguments );
},
/**
* @event reset
* @description uploader
* @for Uploader
*/
/**
* @method reset
* @grammar reset() => undefined
* @description uploader
* @for Uploader
* @example
* uploader.reset();
*/
reset: function() {
this.owner.trigger('reset');
this.queue = new Queue();
this.stats = this.queue.stats;
},
destroy: function() {
this.reset();
this.placeholder && this.placeholder.destroy();
}
});
});
/**
* @fileOverview Runtime
*/
define('widgets/runtime',[
'uploader',
'runtime/runtime',
'widgets/widget'
], function( Uploader, Runtime ) {
Uploader.support = function() {
return Runtime.hasRuntime.apply( Runtime, arguments );
};
/**
* @property {Object} [runtimeOrder=html5,flash]
* @namespace options
* @for Uploader
* @description html5 使 html5, 使 flash.
*
* `flash`使 flash
*/
return Uploader.register({
name: 'runtime',
init: function() {
if ( !this.predictRuntimeType() ) {
throw Error('Runtime Error');
}
},
/**
* Uploader`Runtime`
* @grammar predictRuntimeType() => String
* @method predictRuntimeType
* @for Uploader
*/
predictRuntimeType: function() {
var orders = this.options.runtimeOrder || Runtime.orders,
type = this.type,
i, len;
if ( !type ) {
orders = orders.split( /\s*,\s*/g );
for ( i = 0, len = orders.length; i < len; i++ ) {
if ( Runtime.hasRuntime( orders[ i ] ) ) {
this.type = type = orders[ i ];
break;
}
}
}
return type;
}
});
});
/**
* @fileOverview Transport
*/
define('lib/transport',[
'base',
'runtime/client',
'mediator'
], function( Base, RuntimeClient, Mediator ) {
var $ = Base.$;
function Transport( opts ) {
var me = this;
opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
RuntimeClient.call( this, 'Transport' );
this._blob = null;
this._formData = opts.formData || {};
this._headers = opts.headers || {};
this.on( 'progress', this._timeout );
this.on( 'load error', function() {
me.trigger( 'progress', 1 );
clearTimeout( me._timer );
});
}
Transport.options = {
server: '',
method: 'POST',
// 跨域时是否允许携带cookie, 只有html5 runtime才有效
withCredentials: false,
fileVal: 'file',
timeout: 2 * 60 * 1000, // 2分钟
formData: {},
headers: {},
sendAsBinary: false
};
$.extend( Transport.prototype, {
// 添加Blob, 只能添加一次,最后一次有效。
appendBlob: function( key, blob, filename ) {
var me = this,
opts = me.options;
if ( me.getRuid() ) {
me.disconnectRuntime();
}
// 连接到blob归属的同一个runtime.
me.connectRuntime( blob.ruid, function() {
me.exec('init');
});
me._blob = blob;
opts.fileVal = key || opts.fileVal;
opts.filename = filename || opts.filename;
},
// 添加其他字段
append: function( key, value ) {
if ( typeof key === 'object' ) {
$.extend( this._formData, key );
} else {
this._formData[ key ] = value;
}
},
setRequestHeader: function( key, value ) {
if ( typeof key === 'object' ) {
$.extend( this._headers, key );
} else {
this._headers[ key ] = value;
}
},
send: function( method ) {
this.exec( 'send', method );
this._timeout();
},
abort: function() {
clearTimeout( this._timer );
return this.exec('abort');
},
destroy: function() {
this.trigger('destroy');
this.off();
this.exec('destroy');
this.disconnectRuntime();
},
getResponse: function() {
return this.exec('getResponse');
},
getResponseAsJson: function() {
return this.exec('getResponseAsJson');
},
getStatus: function() {
return this.exec('getStatus');
},
_timeout: function() {
var me = this,
duration = me.options.timeout;
if ( !duration ) {
return;
}
clearTimeout( me._timer );
me._timer = setTimeout(function() {
me.abort();
me.trigger( 'error', 'timeout' );
}, duration );
}
});
// 让Transport具备事件功能。
Mediator.installTo( Transport.prototype );
return Transport;
});
/**
* @fileOverview
*/
define('widgets/upload',[
'base',
'uploader',
'file',
'lib/transport',
'widgets/widget'
], function( Base, Uploader, WUFile, Transport ) {
var $ = Base.$,
isPromise = Base.isPromise,
Status = WUFile.Status;
// 添加默认配置项
$.extend( Uploader.options, {
/**
* @property {Boolean} [prepareNextFile=false]
* @namespace options
* @for Uploader
* @description
* md5
*
*/
prepareNextFile: false,
/**
* @property {Boolean} [chunked=false]
* @namespace options
* @for Uploader
* @description
*/
chunked: false,
/**
* @property {Boolean} [chunkSize=5242880]
* @namespace options
* @for Uploader
* @description 5M.
*/
chunkSize: 5 * 1024 * 1024,
/**
* @property {Boolean} [chunkRetry=2]
* @namespace options
* @for Uploader
* @description
*/
chunkRetry: 2,
/**
* @property {Boolean} [threads=3]
* @namespace options
* @for Uploader
* @description
*/
threads: 3,
/**
* @property {Object} [formData={}]
* @namespace options
* @for Uploader
* @description
*/
formData: {}
/**
* @property {Object} [fileVal='file']
* @namespace options
* @for Uploader
* @description name
*/
/**
* @property {Object} [sendAsBinary=false]
* @namespace options
* @for Uploader
* @description `php://input`
* $_GET
*/
});
// 负责将文件切片。
function CuteFile( file, chunkSize ) {
var pending = [],
blob = file.source,
total = blob.size,
chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
start = 0,
index = 0,
len, api;
api = {
file: file,
has: function() {
return !!pending.length;
},
shift: function() {
return pending.shift();
},
unshift: function( block ) {
pending.unshift( block );
}
};
while ( index < chunks ) {
len = Math.min( chunkSize, total - start );
pending.push({
file: file,
start: start,
end: chunkSize ? (start + len) : total,
total: total,
chunks: chunks,
chunk: index++,
cuted: api
});
start += len;
}
file.blocks = pending.concat();
file.remaning = pending.length;
return api;
}
Uploader.register({
name: 'upload',
init: function() {
var owner = this.owner,
me = this;
this.runing = false;
this.progress = false;
owner
.on( 'startUpload', function() {
me.progress = true;
})
.on( 'uploadFinished', function() {
me.progress = false;
});
// 记录当前正在传的数据跟threads相关
this.pool = [];
// 缓存分好片的文件。
this.stack = [];
// 缓存即将上传的文件。
this.pending = [];
// 跟踪还有多少分片在上传中但是没有完成上传。
this.remaning = 0;
this.__tick = Base.bindFn( this._tick, this );
// 销毁上传相关的属性。
owner.on( 'uploadComplete', function( file ) {
// 把其他块取消了。
file.blocks && $.each( file.blocks, function( _, v ) {
v.transport && (v.transport.abort(), v.transport.destroy());
delete v.transport;
});
delete file.blocks;
delete file.remaning;
});
},
reset: function() {
this.request( 'stop-upload', true );
this.runing = false;
this.pool = [];
this.stack = [];
this.pending = [];
this.remaning = 0;
this._trigged = false;
this._promise = null;
},
/**
* @event startUpload
* @description
* @for Uploader
*/
/**
*
*
*
* @grammar upload() => undefined
* @grammar upload( file | fileId) => undefined
* @method upload
* @for Uploader
*/
startUpload: function(file) {
var me = this;
// 移出invalid的文件
$.each( me.request( 'get-files', Status.INVALID ), function() {
me.request( 'remove-file', this );
});
// 如果指定了开始某个文件,则只开始指定的文件。
if ( file ) {
file = file.id ? file : me.request( 'get-file', file );
if (file.getStatus() === Status.INTERRUPT) {
file.setStatus( Status.QUEUED );
$.each( me.pool, function( _, v ) {
// 之前暂停过。
if (v.file !== file) {
return;
}
v.transport && v.transport.send();
file.setStatus( Status.PROGRESS );
});
} else if (file.getStatus() !== Status.PROGRESS) {
file.setStatus( Status.QUEUED );
}
} else {
$.each( me.request( 'get-files', [ Status.INITED ] ), function() {
this.setStatus( Status.QUEUED );
});
}
if ( me.runing ) {
return Base.nextTick( me.__tick );
}
me.runing = true;
var files = [];
// 如果有暂停的,则续传
file || $.each( me.pool, function( _, v ) {
var file = v.file;
if ( file.getStatus() === Status.INTERRUPT ) {
me._trigged = false;
files.push(file);
v.transport && v.transport.send();
}
});
$.each(files, function() {
this.setStatus( Status.PROGRESS );
});
file || $.each( me.request( 'get-files',
Status.INTERRUPT ), function() {
this.setStatus( Status.PROGRESS );
});
me._trigged = false;
Base.nextTick( me.__tick );
me.owner.trigger('startUpload');
},
/**
* @event stopUpload
* @description
* @for Uploader
*/
/**
*
*
*
* @grammar stop() => undefined
* @grammar stop( true ) => undefined
* @grammar stop( file ) => undefined
* @method stop
* @for Uploader
*/
stopUpload: function( file, interrupt ) {
var me = this,
block;
if (file === true) {
interrupt = file;
file = null;
}
if ( me.runing === false ) {
return;
}
// 如果只是暂停某个文件。
if ( file ) {
file = file.id ? file : me.request( 'get-file', file );
if ( file.getStatus() !== Status.PROGRESS &&
file.getStatus() !== Status.QUEUED ) {
return;
}
file.setStatus( Status.INTERRUPT );
$.each( me.pool, function( _, v ) {
// 只 abort 指定的文件。
if (v.file === file) {
block = v;
return false;
}
});
block.transport && block.transport.abort();
if (interrupt) {
me._putback(block);
me._popBlock(block);
}
return Base.nextTick( me.__tick );
}
me.runing = false;
// 正在准备中的文件。
if (this._promise && this._promise.file) {
this._promise.file.setStatus( Status.INTERRUPT );
}
interrupt && $.each( me.pool, function( _, v ) {
v.transport && v.transport.abort();
v.file.setStatus( Status.INTERRUPT );
});
me.owner.trigger('stopUpload');
},
/**
* @method cancelFile
* @grammar cancelFile( file ) => undefined
* @grammar cancelFile( id ) => undefined
* @param {File|id} file FileFileid
* @description ,
* @for Uploader
* @example
*
* $li.on('click', '.remove-this', function() {
* uploader.cancelFile( file );
* })
*/
cancelFile: function( file ) {
file = file.id ? file : this.request( 'get-file', file );
// 如果正在上传。
file.blocks && $.each( file.blocks, function( _, v ) {
var _tr = v.transport;
if ( _tr ) {
_tr.abort();
_tr.destroy();
delete v.transport;
}
});
file.setStatus( Status.CANCELLED );
this.owner.trigger( 'fileDequeued', file );
},
/**
* `Uplaode`r
* @grammar isInProgress() => Boolean
* @method isInProgress
* @for Uploader
*/
isInProgress: function() {
return !!this.progress;
},
_getStats: function() {
return this.request('get-stats');
},
/**
*
* @grammar skipFile( file ) => undefined
* @method skipFile
* @for Uploader
*/
skipFile: function( file, status ) {
file = file.id ? file : this.request( 'get-file', file );
file.setStatus( status || Status.COMPLETE );
file.skipped = true;
// 如果正在上传。
file.blocks && $.each( file.blocks, function( _, v ) {
var _tr = v.transport;
if ( _tr ) {
_tr.abort();
_tr.destroy();
delete v.transport;
}
});
this.owner.trigger( 'uploadSkip', file );
},
/**
* @event uploadFinished
* @description
* @for Uploader
*/
_tick: function() {
var me = this,
opts = me.options,
fn, val;
// 上一个promise还没有结束则等待完成后再执行。
if ( me._promise ) {
return me._promise.always( me.__tick );
}
// 还有位置,且还有文件要处理的话。
if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
me._trigged = false;
fn = function( val ) {
me._promise = null;
// 有可能是reject过来的所以要检测val的类型。
val && val.file && me._startSend( val );
Base.nextTick( me.__tick );
};
me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
// 没有要上传的了,且没有正在传输的了。
} else if ( !me.remaning && !me._getStats().numOfQueue &&
!me._getStats().numofInterrupt ) {
me.runing = false;
me._trigged || Base.nextTick(function() {
me.owner.trigger('uploadFinished');
});
me._trigged = true;
}
},
_putback: function(block) {
var idx;
block.cuted.unshift(block);
idx = this.stack.indexOf(block.cuted);
if (!~idx) {
this.stack.unshift(block.cuted);
}
},
_getStack: function() {
var i = 0,
act;
while ( (act = this.stack[ i++ ]) ) {
if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
return act;
} else if (!act.has() ||
act.file.getStatus() !== Status.PROGRESS &&
act.file.getStatus() !== Status.INTERRUPT ) {
// 把已经处理完了的,或者,状态为非 progress上传中
// interupt暂停中 的移除。
this.stack.splice( --i, 1 );
}
}
return null;
},
_nextBlock: function() {
var me = this,
opts = me.options,
act, next, done, preparing;
// 如果当前文件还有没有需要传输的,则直接返回剩下的。
if ( (act = this._getStack()) ) {
// 是否提前准备下一个文件
if ( opts.prepareNextFile && !me.pending.length ) {
me._prepareNextFile();
}
return act.shift();
// 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
} else if ( me.runing ) {
// 如果缓存中有则直接在缓存中取没有则去queue中取。
if ( !me.pending.length && me._getStats().numOfQueue ) {
me._prepareNextFile();
}
next = me.pending.shift();
done = function( file ) {
if ( !file ) {
return null;
}
act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
me.stack.push(act);
return act.shift();
};
// 文件可能还在prepare中也有可能已经完全准备好了。
if ( isPromise( next) ) {
preparing = next.file;
next = next[ next.pipe ? 'pipe' : 'then' ]( done );
next.file = preparing;
return next;
}
return done( next );
}
},
/**
* @event uploadStart
* @param {File} file File
* @description
* @for Uploader
*/
_prepareNextFile: function() {
var me = this,
file = me.request('fetch-file'),
pending = me.pending,
promise;
if ( file ) {
promise = me.request( 'before-send-file', file, function() {
// 有可能文件被skip掉了。文件被skip掉后状态坑定不是Queued.
if ( file.getStatus() === Status.PROGRESS ||
file.getStatus() === Status.INTERRUPT ) {
return file;
}
return me._finishFile( file );
});
me.owner.trigger( 'uploadStart', file );
file.setStatus( Status.PROGRESS );
promise.file = file;
// 如果还在pending中则替换成文件本身。
promise.done(function() {
var idx = $.inArray( promise, pending );
~idx && pending.splice( idx, 1, file );
});
// befeore-send-file的钩子就有错误发生。
promise.fail(function( reason ) {
file.setStatus( Status.ERROR, reason );
me.owner.trigger( 'uploadError', file, reason );
me.owner.trigger( 'uploadComplete', file );
});
pending.push( promise );
}
},
// 让出位置了,可以让其他分片开始上传
_popBlock: function( block ) {
var idx = $.inArray( block, this.pool );
this.pool.splice( idx, 1 );
block.file.remaning--;
this.remaning--;
},
// 开始上传可以被掉过。如果promise被reject了则表示跳过此分片。
_startSend: function( block ) {
var me = this,
file = block.file,
promise;
// 有可能在 before-send-file 的 promise 期间改变了文件状态。
// 如:暂停,取消
// 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
if ( file.getStatus() !== Status.PROGRESS ) {
// 如果是中断,则还需要放回去。
if (file.getStatus() === Status.INTERRUPT) {
me._putback(block);
}
return;
}
me.pool.push( block );
me.remaning++;
// 如果没有分片,则直接使用原始的。
// 不会丢失content-type信息。
block.blob = block.chunks === 1 ? file.source :
file.source.slice( block.start, block.end );
// hook, 每个分片发送之前可能要做些异步的事情。
promise = me.request( 'before-send', block, function() {
// 有可能文件已经上传出错了,所以不需要再传输了。
if ( file.getStatus() === Status.PROGRESS ) {
me._doSend( block );
} else {
me._popBlock( block );
Base.nextTick( me.__tick );
}
});
// 如果为fail了则跳过此分片。
promise.fail(function() {
if ( file.remaning === 1 ) {
me._finishFile( file ).always(function() {
block.percentage = 1;
me._popBlock( block );
me.owner.trigger( 'uploadComplete', file );
Base.nextTick( me.__tick );
});
} else {
block.percentage = 1;
me.updateFileProgress( file );
me._popBlock( block );
Base.nextTick( me.__tick );
}
});
},
/**
* @event uploadBeforeSend
* @param {Object} object
* @param {Object} data
* @param {Object} headers
* @description
* @for Uploader
*/
/**
* @event uploadAccept
* @param {Object} object
* @param {Object} ret jsonjsonret._raw
* @description handler`false`, `server``uploadError`
* @for Uploader
*/
/**
* @event uploadProgress
* @param {File} file File
* @param {Number} percentage
* @description
* @for Uploader
*/
/**
* @event uploadError
* @param {File} file File
* @param {String} reason code
* @description
* @for Uploader
*/
/**
* @event uploadSuccess
* @param {File} file File
* @param {Object} response
* @description
* @for Uploader
*/
/**
* @event uploadComplete
* @param {File} [file] File
* @description
* @for Uploader
*/
// 做上传操作。
_doSend: function( block ) {
var me = this,
owner = me.owner,
opts = me.options,
file = block.file,
tr = new Transport( opts ),
data = $.extend({}, opts.formData ),
headers = $.extend({}, opts.headers ),
requestAccept, ret;
block.transport = tr;
tr.on( 'destroy', function() {
delete block.transport;
me._popBlock( block );
Base.nextTick( me.__tick );
});
// 广播上传进度。以文件为单位。
tr.on( 'progress', function( percentage ) {
block.percentage = percentage;
me.updateFileProgress( file );
});
// 用来询问,是否返回的结果是有错误的。
requestAccept = function( reject ) {
var fn;
ret = tr.getResponseAsJson() || {};
ret._raw = tr.getResponse();
fn = function( value ) {
reject = value;
};
// 服务端响应了,不代表成功了,询问是否响应正确。
if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
reject = reject || 'server';
}
return reject;
};
// 尝试重试,然后广播文件上传出错。
tr.on( 'error', function( type, flag ) {
block.retried = block.retried || 0;
// 自动重试
if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
block.retried < opts.chunkRetry ) {
block.retried++;
tr.send();
} else {
// http status 500 ~ 600
if ( !flag && type === 'server' ) {
type = requestAccept( type );
}
file.setStatus( Status.ERROR, type );
owner.trigger( 'uploadError', file, type );
owner.trigger( 'uploadComplete', file );
}
});
// 上传成功
tr.on( 'load', function() {
var reason;
// 如果非预期,转向上传出错。
if ( (reason = requestAccept()) ) {
tr.trigger( 'error', reason, true );
return;
}
// 全部上传完成。
if ( file.remaning === 1 ) {
me._finishFile( file, ret );
} else {
tr.destroy();
}
});
// 配置默认的上传字段。
data = $.extend( data, {
id: file.id,
name: file.name,
type: file.type,
lastModifiedDate: file.lastModifiedDate,
size: file.size
});
block.chunks > 1 && $.extend( data, {
chunks: block.chunks,
chunk: block.chunk
});
// 在发送之间可以添加字段什么的。。。
// 如果默认的字段不够使用,可以通过监听此事件来扩展
owner.trigger( 'uploadBeforeSend', block, data, headers );
// 开始发送。
tr.appendBlob( opts.fileVal, block.blob, file.name );
tr.append( data );
tr.setRequestHeader( headers );
tr.send();
},
// 完成上传。
_finishFile: function( file, ret, hds ) {
var owner = this.owner;
return owner
.request( 'after-send-file', arguments, function() {
file.setStatus( Status.COMPLETE );
owner.trigger( 'uploadSuccess', file, ret, hds );
})
.fail(function( reason ) {
// 如果外部已经标记为invalid什么的不再改状态。
if ( file.getStatus() === Status.PROGRESS ) {
file.setStatus( Status.ERROR, reason );
}
owner.trigger( 'uploadError', file, reason );
})
.always(function() {
owner.trigger( 'uploadComplete', file );
});
},
updateFileProgress: function(file) {
var totalPercent = 0,
uploaded = 0;
if (!file.blocks) {
return;
}
$.each( file.blocks, function( _, v ) {
uploaded += (v.percentage || 0) * (v.end - v.start);
});
totalPercent = uploaded / file.size;
this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
}
});
});
/**
* @fileOverview
*/
define('widgets/validator',[
'base',
'uploader',
'file',
'widgets/widget'
], function( Base, Uploader, WUFile ) {
var $ = Base.$,
validators = {},
api;
/**
* @event error
* @param {String} type
* @description validate`upload.on('error', handler)`
*
* * `Q_EXCEED_NUM_LIMIT` `fileNumLimit``uploader`
* * `Q_EXCEED_SIZE_LIMIT` `Q_EXCEED_SIZE_LIMIT``uploader`
* * `Q_TYPE_DENIED`
* @for Uploader
*/
// 暴露给外面的api
api = {
// 添加验证器
addValidator: function( type, cb ) {
validators[ type ] = cb;
},
// 移除验证器
removeValidator: function( type ) {
delete validators[ type ];
}
};
// 在Uploader初始化的时候启动Validators的初始化
Uploader.register({
name: 'validator',
init: function() {
var me = this;
Base.nextTick(function() {
$.each( validators, function() {
this.call( me.owner );
});
});
}
});
/**
* @property {int} [fileNumLimit=undefined]
* @namespace options
* @for Uploader
* @description ,
*/
api.addValidator( 'fileNumLimit', function() {
var uploader = this,
opts = uploader.options,
count = 0,
max = parseInt( opts.fileNumLimit, 10 ),
flag = true;
if ( !max ) {
return;
}
uploader.on( 'beforeFileQueued', function( file ) {
if ( count >= max && flag ) {
flag = false;
this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
setTimeout(function() {
flag = true;
}, 1 );
}
return count >= max ? false : true;
});
uploader.on( 'fileQueued', function() {
count++;
});
uploader.on( 'fileDequeued', function() {
count--;
});
uploader.on( 'reset', function() {
count = 0;
});
});
/**
* @property {int} [fileSizeLimit=undefined]
* @namespace options
* @for Uploader
* @description ,
*/
api.addValidator( 'fileSizeLimit', function() {
var uploader = this,
opts = uploader.options,
count = 0,
max = parseInt( opts.fileSizeLimit, 10 ),
flag = true;
if ( !max ) {
return;
}
uploader.on( 'beforeFileQueued', function( file ) {
var invalid = count + file.size > max;
if ( invalid && flag ) {
flag = false;
this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
setTimeout(function() {
flag = true;
}, 1 );
}
return invalid ? false : true;
});
uploader.on( 'fileQueued', function( file ) {
count += file.size;
});
uploader.on( 'fileDequeued', function( file ) {
count -= file.size;
});
uploader.on( 'reset', function() {
count = 0;
});
});
/**
* @property {int} [fileSingleSizeLimit=undefined]
* @namespace options
* @for Uploader
* @description ,
*/
api.addValidator( 'fileSingleSizeLimit', function() {
var uploader = this,
opts = uploader.options,
max = opts.fileSingleSizeLimit;
if ( !max ) {
return;
}
uploader.on( 'beforeFileQueued', function( file ) {
if ( file.size > max ) {
file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
return false;
}
});
});
/**
* @property {Boolean} [duplicate=undefined]
* @namespace options
* @for Uploader
* @description hash Key.
*/
api.addValidator( 'duplicate', function() {
var uploader = this,
opts = uploader.options,
mapping = {};
if ( opts.duplicate ) {
return;
}
function hashString( str ) {
var hash = 0,
i = 0,
len = str.length,
_char;
for ( ; i < len; i++ ) {
_char = str.charCodeAt( i );
hash = _char + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
uploader.on( 'beforeFileQueued', function( file ) {
var hash = file.__hash || (file.__hash = hashString( file.name +
file.size + file.lastModifiedDate ));
// 已经重复了
if ( mapping[ hash ] ) {
this.trigger( 'error', 'F_DUPLICATE', file );
return false;
}
});
uploader.on( 'fileQueued', function( file ) {
var hash = file.__hash;
hash && (mapping[ hash ] = true);
});
uploader.on( 'fileDequeued', function( file ) {
var hash = file.__hash;
hash && (delete mapping[ hash ]);
});
uploader.on( 'reset', function() {
mapping = {};
});
});
return api;
});
/**
* @fileOverview Md5
*/
define('lib/md5',[
'runtime/client',
'mediator'
], function( RuntimeClient, Mediator ) {
function Md5() {
RuntimeClient.call( this, 'Md5' );
}
// 让 Md5 具备事件功能。
Mediator.installTo( Md5.prototype );
Md5.prototype.loadFromBlob = function( blob ) {
var me = this;
if ( me.getRuid() ) {
me.disconnectRuntime();
}
// 连接到blob归属的同一个runtime.
me.connectRuntime( blob.ruid, function() {
me.exec('init');
me.exec( 'loadFromBlob', blob );
});
};
Md5.prototype.getResult = function() {
return this.exec('getResult');
};
return Md5;
});
/**
* @fileOverview ,
*/
define('widgets/md5',[
'base',
'uploader',
'lib/md5',
'lib/blob',
'widgets/widget'
], function( Base, Uploader, Md5, Blob ) {
return Uploader.register({
name: 'md5',
/**
* md5 promise progress
*
*
* @method md5File
* @grammar md5File( file[, start[, end]] ) => promise
* @for Uploader
* @example
*
* uploader.on( 'fileQueued', function( file ) {
* var $li = ...;
*
* uploader.md5File( file )
*
* // 及时显示进度
* .progress(function(percentage) {
* console.log('Percentage:', percentage);
* })
*
* // 完成
* .then(function(val) {
* console.log('md5 result:', val);
* });
*
* });
*/
md5File: function( file, start, end ) {
var md5 = new Md5(),
deferred = Base.Deferred(),
blob = (file instanceof Blob) ? file :
this.request( 'get-file', file ).source;
md5.on( 'progress load', function( e ) {
e = e || {};
deferred.notify( e.total ? e.loaded / e.total : 1 );
});
md5.on( 'complete', function() {
deferred.resolve( md5.getResult() );
});
md5.on( 'error', function( reason ) {
deferred.reject( reason );
});
if ( arguments.length > 1 ) {
start = start || 0;
end = end || 0;
start < 0 && (start = blob.size + start);
end < 0 && (end = blob.size + end);
end = Math.min( end, blob.size );
blob = blob.slice( start, end );
}
md5.loadFromBlob( blob );
return deferred.promise();
}
});
});
/**
* @fileOverview RuntimeRuntime,
*/
define('runtime/compbase',[],function() {
function CompBase( owner, runtime ) {
this.owner = owner;
this.options = owner.options;
this.getRuntime = function() {
return runtime;
};
this.getRuid = function() {
return runtime.uid;
};
this.trigger = function() {
return owner.trigger.apply( owner, arguments );
};
}
return CompBase;
});
/**
* @fileOverview Html5Runtime
*/
define('runtime/html5/runtime',[
'base',
'runtime/runtime',
'runtime/compbase'
], function( Base, Runtime, CompBase ) {
var type = 'html5',
components = {};
function Html5Runtime() {
var pool = {},
me = this,
destroy = this.destroy;
Runtime.apply( me, arguments );
me.type = type;
// 这个方法的调用者实际上是RuntimeClient
me.exec = function( comp, fn/*, args...*/) {
var client = this,
uid = client.uid,
args = Base.slice( arguments, 2 ),
instance;
if ( components[ comp ] ) {
instance = pool[ uid ] = pool[ uid ] ||
new components[ comp ]( client, me );
if ( instance[ fn ] ) {
return instance[ fn ].apply( instance, args );
}
}
};
me.destroy = function() {
// @todo 删除池子中的所有实例
return destroy && destroy.apply( this, arguments );
};
}
Base.inherits( Runtime, {
constructor: Html5Runtime,
// 不需要连接其他程序直接执行callback
init: function() {
var me = this;
setTimeout(function() {
me.trigger('ready');
}, 1 );
}
});
// 注册Components
Html5Runtime.register = function( name, component ) {
var klass = components[ name ] = Base.inherits( CompBase, component );
return klass;
};
// 注册html5运行时。
// 只有在支持的前提下注册。
if ( window.Blob && window.FileReader && window.DataView ) {
Runtime.addRuntime( type, Html5Runtime );
}
return Html5Runtime;
});
/**
* @fileOverview Blob Html
*/
define('runtime/html5/blob',[
'runtime/html5/runtime',
'lib/blob'
], function( Html5Runtime, Blob ) {
return Html5Runtime.register( 'Blob', {
slice: function( start, end ) {
var blob = this.owner.source,
slice = blob.slice || blob.webkitSlice || blob.mozSlice;
blob = slice.call( blob, start, end );
return new Blob( this.getRuid(), blob );
}
});
});
/**
* @fileOverview FilePaste
*/
define('runtime/html5/dnd',[
'base',
'runtime/html5/runtime',
'lib/file'
], function( Base, Html5Runtime, File ) {
var $ = Base.$,
prefix = 'webuploader-dnd-';
return Html5Runtime.register( 'DragAndDrop', {
init: function() {
var elem = this.elem = this.options.container;
this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
this.dropHandler = Base.bindFn( this._dropHandler, this );
this.dndOver = false;
elem.on( 'dragenter', this.dragEnterHandler );
elem.on( 'dragover', this.dragOverHandler );
elem.on( 'dragleave', this.dragLeaveHandler );
elem.on( 'drop', this.dropHandler );
if ( this.options.disableGlobalDnd ) {
$( document ).on( 'dragover', this.dragOverHandler );
$( document ).on( 'drop', this.dropHandler );
}
},
_dragEnterHandler: function( e ) {
var me = this,
denied = me._denied || false,
items;
e = e.originalEvent || e;
if ( !me.dndOver ) {
me.dndOver = true;
// 注意只有 chrome 支持。
items = e.dataTransfer.items;
if ( items && items.length ) {
me._denied = denied = !me.trigger( 'accept', items );
}
me.elem.addClass( prefix + 'over' );
me.elem[ denied ? 'addClass' :
'removeClass' ]( prefix + 'denied' );
}
e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
return false;
},
_dragOverHandler: function( e ) {
// 只处理框内的。
var parentElem = this.elem.parent().get( 0 );
if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
return false;
}
clearTimeout( this._leaveTimer );
this._dragEnterHandler.call( this, e );
return false;
},
_dragLeaveHandler: function() {
var me = this,
handler;
handler = function() {
me.dndOver = false;
me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
};
clearTimeout( me._leaveTimer );
me._leaveTimer = setTimeout( handler, 100 );
return false;
},
_dropHandler: function( e ) {
var me = this,
ruid = me.getRuid(),
parentElem = me.elem.parent().get( 0 ),
dataTransfer, data;
// 只处理框内的。
if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
return false;
}
e = e.originalEvent || e;
dataTransfer = e.dataTransfer;
// 如果是页面内拖拽,还不能处理,不阻止事件。
// 此处 ie11 下会报参数错误,
try {
data = dataTransfer.getData('text/html');
} catch( err ) {
}
me.dndOver = false;
me.elem.removeClass( prefix + 'over' );
if ( data ) {
return;
}
me._getTansferFiles( dataTransfer, function( results ) {
me.trigger( 'drop', $.map( results, function( file ) {
return new File( ruid, file );
}) );
});
return false;
},
// 如果传入 callback 则去查看文件夹,否则只管当前文件夹。
_getTansferFiles: function( dataTransfer, callback ) {
var results = [],
promises = [],
items, files, file, item, i, len, canAccessFolder;
items = dataTransfer.items;
files = dataTransfer.files;
canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
for ( i = 0, len = files.length; i < len; i++ ) {
file = files[ i ];
item = items && items[ i ];
if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
promises.push( this._traverseDirectoryTree(
item.webkitGetAsEntry(), results ) );
} else {
results.push( file );
}
}
Base.when.apply( Base, promises ).done(function() {
if ( !results.length ) {
return;
}
callback( results );
});
},
_traverseDirectoryTree: function( entry, results ) {
var deferred = Base.Deferred(),
me = this;
if ( entry.isFile ) {
entry.file(function( file ) {
results.push( file );
deferred.resolve();
});
} else if ( entry.isDirectory ) {
entry.createReader().readEntries(function( entries ) {
var len = entries.length,
promises = [],
arr = [], // 为了保证顺序。
i;
for ( i = 0; i < len; i++ ) {
promises.push( me._traverseDirectoryTree(
entries[ i ], arr ) );
}
Base.when.apply( Base, promises ).then(function() {
results.push.apply( results, arr );
deferred.resolve();
}, deferred.reject );
});
}
return deferred.promise();
},
destroy: function() {
var elem = this.elem;
// 还没 init 就调用 destroy
if (!elem) {
return;
}
elem.off( 'dragenter', this.dragEnterHandler );
elem.off( 'dragover', this.dragOverHandler );
elem.off( 'dragleave', this.dragLeaveHandler );
elem.off( 'drop', this.dropHandler );
if ( this.options.disableGlobalDnd ) {
$( document ).off( 'dragover', this.dragOverHandler );
$( document ).off( 'drop', this.dropHandler );
}
}
});
});
/**
* @fileOverview FilePaste
*/
define('runtime/html5/filepaste',[
'base',
'runtime/html5/runtime',
'lib/file'
], function( Base, Html5Runtime, File ) {
return Html5Runtime.register( 'FilePaste', {
init: function() {
var opts = this.options,
elem = this.elem = opts.container,
accept = '.*',
arr, i, len, item;
// accetp的mimeTypes中生成匹配正则。
if ( opts.accept ) {
arr = [];
for ( i = 0, len = opts.accept.length; i < len; i++ ) {
item = opts.accept[ i ].mimeTypes;
item && arr.push( item );
}
if ( arr.length ) {
accept = arr.join(',');
accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
}
}
this.accept = accept = new RegExp( accept, 'i' );
this.hander = Base.bindFn( this._pasteHander, this );
elem.on( 'paste', this.hander );
},
_pasteHander: function( e ) {
var allowed = [],
ruid = this.getRuid(),
items, item, blob, i, len;
e = e.originalEvent || e;
items = e.clipboardData.items;
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[ i ];
if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
continue;
}
allowed.push( new File( ruid, blob ) );
}
if ( allowed.length ) {
// 不阻止非文件粘贴(文字粘贴)的事件冒泡
e.preventDefault();
e.stopPropagation();
this.trigger( 'paste', allowed );
}
},
destroy: function() {
this.elem.off( 'paste', this.hander );
}
});
});
/**
* @fileOverview FilePicker
*/
define('runtime/html5/filepicker',[
'base',
'runtime/html5/runtime'
], function( Base, Html5Runtime ) {
var $ = Base.$;
return Html5Runtime.register( 'FilePicker', {
init: function() {
var container = this.getRuntime().getContainer(),
me = this,
owner = me.owner,
opts = me.options,
label = this.label = $( document.createElement('label') ),
input = this.input = $( document.createElement('input') ),
arr, i, len, mouseHandler;
input.attr( 'type', 'file' );
input.attr( 'capture', 'camera');
input.attr( 'name', opts.name );
input.addClass('webuploader-element-invisible');
label.on( 'click', function(e) {
input.trigger('click');
e.stopPropagation();
owner.trigger('dialogopen');
});
label.css({
opacity: 0,
width: '100%',
height: '100%',
display: 'block',
cursor: 'pointer',
background: '#ffffff'
});
if ( opts.multiple ) {
input.attr( 'multiple', 'multiple' );
}
// @todo Firefox不支持单独指定后缀
if ( opts.accept && opts.accept.length > 0 ) {
arr = [];
for ( i = 0, len = opts.accept.length; i < len; i++ ) {
arr.push( opts.accept[ i ].mimeTypes );
}
input.attr( 'accept', arr.join(',') );
}
container.append( input );
container.append( label );
mouseHandler = function( e ) {
owner.trigger( e.type );
};
input.on( 'change', function( e ) {
var fn = arguments.callee,
clone;
me.files = e.target.files;
// reset input
clone = this.cloneNode( true );
clone.value = null;
this.parentNode.replaceChild( clone, this );
input.off();
input = $( clone ).on( 'change', fn )
.on( 'mouseenter mouseleave', mouseHandler );
owner.trigger('change');
});
label.on( 'mouseenter mouseleave', mouseHandler );
},
getFiles: function() {
return this.files;
},
destroy: function() {
this.input.off();
this.label.off();
}
});
});
/**
* Terms:
*
* Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
* @fileOverview Image
*/
define('runtime/html5/util',[
'base'
], function( Base ) {
var urlAPI = window.createObjectURL && window ||
window.URL && URL.revokeObjectURL && URL ||
window.webkitURL,
createObjectURL = Base.noop,
revokeObjectURL = createObjectURL;
if ( urlAPI ) {
// 更安全的方式调用比如android里面就能把context改成其他的对象。
createObjectURL = function() {
return urlAPI.createObjectURL.apply( urlAPI, arguments );
};
revokeObjectURL = function() {
return urlAPI.revokeObjectURL.apply( urlAPI, arguments );
};
}
return {
createObjectURL: createObjectURL,
revokeObjectURL: revokeObjectURL,
dataURL2Blob: function( dataURI ) {
var byteStr, intArray, ab, i, mimetype, parts;
parts = dataURI.split(',');
if ( ~parts[ 0 ].indexOf('base64') ) {
byteStr = atob( parts[ 1 ] );
} else {
byteStr = decodeURIComponent( parts[ 1 ] );
}
ab = new ArrayBuffer( byteStr.length );
intArray = new Uint8Array( ab );
for ( i = 0; i < byteStr.length; i++ ) {
intArray[ i ] = byteStr.charCodeAt( i );
}
mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];
return this.arrayBufferToBlob( ab, mimetype );
},
dataURL2ArrayBuffer: function( dataURI ) {
var byteStr, intArray, i, parts;
parts = dataURI.split(',');
if ( ~parts[ 0 ].indexOf('base64') ) {
byteStr = atob( parts[ 1 ] );
} else {
byteStr = decodeURIComponent( parts[ 1 ] );
}
intArray = new Uint8Array( byteStr.length );
for ( i = 0; i < byteStr.length; i++ ) {
intArray[ i ] = byteStr.charCodeAt( i );
}
return intArray.buffer;
},
arrayBufferToBlob: function( buffer, type ) {
var builder = window.BlobBuilder || window.WebKitBlobBuilder,
bb;
// android不支持直接new Blob, 只能借助blobbuilder.
if ( builder ) {
bb = new builder();
bb.append( buffer );
return bb.getBlob( type );
}
return new Blob([ buffer ], type ? { type: type } : {} );
},
// 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.
// 你得到的结果是png.
canvasToDataUrl: function( canvas, type, quality ) {
return canvas.toDataURL( type, quality / 100 );
},
// imagemeat会复写这个方法如果用户选择加载那个文件了的话。
parseMeta: function( blob, callback ) {
callback( false, {});
},
// imagemeat会复写这个方法如果用户选择加载那个文件了的话。
updateImageHead: function( data ) {
return data;
}
};
});
/**
* Terms:
*
* Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
* @fileOverview Image
*/
define('runtime/html5/imagemeta',[
'runtime/html5/util'
], function( Util ) {
var api;
api = {
parsers: {
0xffe1: []
},
maxMetaDataSize: 262144,
parse: function( blob, cb ) {
var me = this,
fr = new FileReader();
fr.onload = function() {
cb( false, me._parse( this.result ) );
fr = fr.onload = fr.onerror = null;
};
fr.onerror = function( e ) {
cb( e.message );
fr = fr.onload = fr.onerror = null;
};
blob = blob.slice( 0, me.maxMetaDataSize );
fr.readAsArrayBuffer( blob.getSource() );
},
_parse: function( buffer, noParse ) {
if ( buffer.byteLength < 6 ) {
return;
}
var dataview = new DataView( buffer ),
offset = 2,
maxOffset = dataview.byteLength - 4,
headLength = offset,
ret = {},
markerBytes, markerLength, parsers, i;
if ( dataview.getUint16( 0 ) === 0xffd8 ) {
while ( offset < maxOffset ) {
markerBytes = dataview.getUint16( offset );
if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||
markerBytes === 0xfffe ) {
markerLength = dataview.getUint16( offset + 2 ) + 2;
if ( offset + markerLength > dataview.byteLength ) {
break;
}
parsers = api.parsers[ markerBytes ];
if ( !noParse && parsers ) {
for ( i = 0; i < parsers.length; i += 1 ) {
parsers[ i ].call( api, dataview, offset,
markerLength, ret );
}
}
offset += markerLength;
headLength = offset;
} else {
break;
}
}
if ( headLength > 6 ) {
if ( buffer.slice ) {
ret.imageHead = buffer.slice( 2, headLength );
} else {
// Workaround for IE10, which does not yet
// support ArrayBuffer.slice:
ret.imageHead = new Uint8Array( buffer )
.subarray( 2, headLength );
}
}
}
return ret;
},
updateImageHead: function( buffer, head ) {
var data = this._parse( buffer, true ),
buf1, buf2, bodyoffset;
bodyoffset = 2;
if ( data.imageHead ) {
bodyoffset = 2 + data.imageHead.byteLength;
}
if ( buffer.slice ) {
buf2 = buffer.slice( bodyoffset );
} else {
buf2 = new Uint8Array( buffer ).subarray( bodyoffset );
}
buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );
buf1[ 0 ] = 0xFF;
buf1[ 1 ] = 0xD8;
buf1.set( new Uint8Array( head ), 2 );
buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );
return buf1.buffer;
}
};
Util.parseMeta = function() {
return api.parse.apply( api, arguments );
};
Util.updateImageHead = function() {
return api.updateImageHead.apply( api, arguments );
};
return api;
});
/**
* https://github.com/blueimp/JavaScript-Load-Image
* orientation.
*
* Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.
* @fileOverview EXIF
*/
// Sample
// ====================================
// Make : Apple
// Model : iPhone 4S
// Orientation : 1
// XResolution : 72 [72/1]
// YResolution : 72 [72/1]
// ResolutionUnit : 2
// Software : QuickTime 7.7.1
// DateTime : 2013:09:01 22:53:55
// ExifIFDPointer : 190
// ExposureTime : 0.058823529411764705 [1/17]
// FNumber : 2.4 [12/5]
// ExposureProgram : Normal program
// ISOSpeedRatings : 800
// ExifVersion : 0220
// DateTimeOriginal : 2013:09:01 22:52:51
// DateTimeDigitized : 2013:09:01 22:52:51
// ComponentsConfiguration : YCbCr
// ShutterSpeedValue : 4.058893515764426
// ApertureValue : 2.5260688216892597 [4845/1918]
// BrightnessValue : -0.3126686601998395
// MeteringMode : Pattern
// Flash : Flash did not fire, compulsory flash mode
// FocalLength : 4.28 [107/25]
// SubjectArea : [4 values]
// FlashpixVersion : 0100
// ColorSpace : 1
// PixelXDimension : 2448
// PixelYDimension : 3264
// SensingMethod : One-chip color area sensor
// ExposureMode : 0
// WhiteBalance : Auto white balance
// FocalLengthIn35mmFilm : 35
// SceneCaptureType : Standard
define('runtime/html5/imagemeta/exif',[
'base',
'runtime/html5/imagemeta'
], function( Base, ImageMeta ) {
var EXIF = {};
EXIF.ExifMap = function() {
return this;
};
EXIF.ExifMap.prototype.map = {
'Orientation': 0x0112
};
EXIF.ExifMap.prototype.get = function( id ) {
return this[ id ] || this[ this.map[ id ] ];
};
EXIF.exifTagTypes = {
// byte, 8-bit unsigned int:
1: {
getValue: function( dataView, dataOffset ) {
return dataView.getUint8( dataOffset );
},
size: 1
},
// ascii, 8-bit byte:
2: {
getValue: function( dataView, dataOffset ) {
return String.fromCharCode( dataView.getUint8( dataOffset ) );
},
size: 1,
ascii: true
},
// short, 16 bit int:
3: {
getValue: function( dataView, dataOffset, littleEndian ) {
return dataView.getUint16( dataOffset, littleEndian );
},
size: 2
},
// long, 32 bit int:
4: {
getValue: function( dataView, dataOffset, littleEndian ) {
return dataView.getUint32( dataOffset, littleEndian );
},
size: 4
},
// rational = two long values,
// first is numerator, second is denominator:
5: {
getValue: function( dataView, dataOffset, littleEndian ) {
return dataView.getUint32( dataOffset, littleEndian ) /
dataView.getUint32( dataOffset + 4, littleEndian );
},
size: 8
},
// slong, 32 bit signed int:
9: {
getValue: function( dataView, dataOffset, littleEndian ) {
return dataView.getInt32( dataOffset, littleEndian );
},
size: 4
},
// srational, two slongs, first is numerator, second is denominator:
10: {
getValue: function( dataView, dataOffset, littleEndian ) {
return dataView.getInt32( dataOffset, littleEndian ) /
dataView.getInt32( dataOffset + 4, littleEndian );
},
size: 8
}
};
// undefined, 8-bit byte, value depending on field:
EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];
EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,
littleEndian ) {
var tagType = EXIF.exifTagTypes[ type ],
tagSize, dataOffset, values, i, str, c;
if ( !tagType ) {
Base.log('Invalid Exif data: Invalid tag type.');
return;
}
tagSize = tagType.size * length;
// Determine if the value is contained in the dataOffset bytes,
// or if the value at the dataOffset is a pointer to the actual data:
dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,
littleEndian ) : (offset + 8);
if ( dataOffset + tagSize > dataView.byteLength ) {
Base.log('Invalid Exif data: Invalid data offset.');
return;
}
if ( length === 1 ) {
return tagType.getValue( dataView, dataOffset, littleEndian );
}
values = [];
for ( i = 0; i < length; i += 1 ) {
values[ i ] = tagType.getValue( dataView,
dataOffset + i * tagType.size, littleEndian );
}
if ( tagType.ascii ) {
str = '';
// Concatenate the chars:
for ( i = 0; i < values.length; i += 1 ) {
c = values[ i ];
// Ignore the terminating NULL byte(s):
if ( c === '\u0000' ) {
break;
}
str += c;
}
return str;
}
return values;
};
EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,
data ) {
var tag = dataView.getUint16( offset, littleEndian );
data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,
dataView.getUint16( offset + 2, littleEndian ), // tag type
dataView.getUint32( offset + 4, littleEndian ), // tag length
littleEndian );
};
EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,
littleEndian, data ) {
var tagsNumber, dirEndOffset, i;
if ( dirOffset + 6 > dataView.byteLength ) {
Base.log('Invalid Exif data: Invalid directory offset.');
return;
}
tagsNumber = dataView.getUint16( dirOffset, littleEndian );
dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
if ( dirEndOffset + 4 > dataView.byteLength ) {
Base.log('Invalid Exif data: Invalid directory size.');
return;
}
for ( i = 0; i < tagsNumber; i += 1 ) {
this.parseExifTag( dataView, tiffOffset,
dirOffset + 2 + 12 * i, // tag offset
littleEndian, data );
}
// Return the offset to the next directory:
return dataView.getUint32( dirEndOffset, littleEndian );
};
// EXIF.getExifThumbnail = function(dataView, offset, length) {
// var hexData,
// i,
// b;
// if (!length || offset + length > dataView.byteLength) {
// Base.log('Invalid Exif data: Invalid thumbnail data.');
// return;
// }
// hexData = [];
// for (i = 0; i < length; i += 1) {
// b = dataView.getUint8(offset + i);
// hexData.push((b < 16 ? '0' : '') + b.toString(16));
// }
// return 'data:image/jpeg,%' + hexData.join('%');
// };
EXIF.parseExifData = function( dataView, offset, length, data ) {
var tiffOffset = offset + 10,
littleEndian, dirOffset;
// Check for the ASCII code for "Exif" (0x45786966):
if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {
// No Exif data, might be XMP data instead
return;
}
if ( tiffOffset + 8 > dataView.byteLength ) {
Base.log('Invalid Exif data: Invalid segment size.');
return;
}
// Check for the two null bytes:
if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {
Base.log('Invalid Exif data: Missing byte alignment offset.');
return;
}
// Check the byte alignment:
switch ( dataView.getUint16( tiffOffset ) ) {
case 0x4949:
littleEndian = true;
break;
case 0x4D4D:
littleEndian = false;
break;
default:
Base.log('Invalid Exif data: Invalid byte alignment marker.');
return;
}
// Check for the TIFF tag marker (0x002A):
if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {
Base.log('Invalid Exif data: Missing TIFF marker.');
return;
}
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );
// Create the exif object to store the tags:
data.exif = new EXIF.ExifMap();
// Parse the tags of the main image directory and retrieve the
// offset to the next directory, usually the thumbnail directory:
dirOffset = EXIF.parseExifTags( dataView, tiffOffset,
tiffOffset + dirOffset, littleEndian, data );
// 尝试读取缩略图
// if ( dirOffset ) {
// thumbnailData = {exif: {}};
// dirOffset = EXIF.parseExifTags(
// dataView,
// tiffOffset,
// tiffOffset + dirOffset,
// littleEndian,
// thumbnailData
// );
// // Check for JPEG Thumbnail offset:
// if (thumbnailData.exif[0x0201]) {
// data.exif.Thumbnail = EXIF.getExifThumbnail(
// dataView,
// tiffOffset + thumbnailData.exif[0x0201],
// thumbnailData.exif[0x0202] // Thumbnail data length
// );
// }
// }
};
ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );
return EXIF;
});
/**
* androidtoDataUrlbug
* androidtoDataUrl('image/jpege')png.
*
*
* @fileOverview jpeg encoder
*/
define('runtime/html5/jpegencoder',[], function( require, exports, module ) {
/*
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
Basic GUI blocking jpeg encoder
*/
function JPEGEncoder(quality) {
var self = this;
var fround = Math.round;
var ffloor = Math.floor;
var YTable = new Array(64);
var UVTable = new Array(64);
var fdtbl_Y = new Array(64);
var fdtbl_UV = new Array(64);
var YDC_HT;
var UVDC_HT;
var YAC_HT;
var UVAC_HT;
var bitcode = new Array(65535);
var category = new Array(65535);
var outputfDCTQuant = new Array(64);
var DU = new Array(64);
var byteout = [];
var bytenew = 0;
var bytepos = 7;
var YDU = new Array(64);
var UDU = new Array(64);
var VDU = new Array(64);
var clt = new Array(256);
var RGB_YUV_TABLE = new Array(2048);
var currentQuality;
var ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
var std_ac_luminance_values = [
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
var std_ac_chrominance_values = [
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
function initQuantTables(sf){
var YQT = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (var i = 0; i < 64; i++) {
var t = ffloor((YQT[i]*sf+50)/100);
if (t < 1) {
t = 1;
} else if (t > 255) {
t = 255;
}
YTable[ZigZag[i]] = t;
}
var UVQT = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for (var j = 0; j < 64; j++) {
var u = ffloor((UVQT[j]*sf+50)/100);
if (u < 1) {
u = 1;
} else if (u > 255) {
u = 255;
}
UVTable[ZigZag[j]] = u;
}
var aasf = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for (var row = 0; row < 8; row++)
{
for (var col = 0; col < 8; col++)
{
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
function computeHuffmanTbl(nrcodes, std_table){
var codevalue = 0;
var pos_in_table = 0;
var HT = new Array();
for (var k = 1; k <= 16; k++) {
for (var j = 1; j <= nrcodes[k]; j++) {
HT[std_table[pos_in_table]] = [];
HT[std_table[pos_in_table]][0] = codevalue;
HT[std_table[pos_in_table]][1] = k;
pos_in_table++;
codevalue++;
}
codevalue*=2;
}
return HT;
}
function initHuffmanTbl()
{
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
}
function initCategoryNumber()
{
var nrlower = 1;
var nrupper = 2;
for (var cat = 1; cat <= 15; cat++) {
//Positive numbers
for (var nr = nrlower; nr<nrupper; nr++) {
category[32767+nr] = cat;
bitcode[32767+nr] = [];
bitcode[32767+nr][1] = cat;
bitcode[32767+nr][0] = nr;
}
//Negative numbers
for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
category[32767+nrneg] = cat;
bitcode[32767+nrneg] = [];
bitcode[32767+nrneg][1] = cat;
bitcode[32767+nrneg][0] = nrupper-1+nrneg;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
function initRGBYUVTable() {
for(var i = 0; i < 256;i++) {
RGB_YUV_TABLE[i] = 19595 * i;
RGB_YUV_TABLE[(i+ 256)>>0] = 38470 * i;
RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000;
RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i;
RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i;
RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF;
RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i;
RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i;
}
}
// IO functions
function writeBits(bs)
{
var value = bs[0];
var posval = bs[1]-1;
while ( posval >= 0 ) {
if (value & (1 << posval) ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if (bytepos < 0) {
if (bytenew == 0xFF) {
writeByte(0xFF);
writeByte(0);
}
else {
writeByte(bytenew);
}
bytepos=7;
bytenew=0;
}
}
}
function writeByte(value)
{
byteout.push(clt[value]); // write char directly instead of converting later
}
function writeWord(value)
{
writeByte((value>>8)&0xFF);
writeByte((value )&0xFF);
}
// DCT & quantization core
function fDCTQuant(data, fdtbl)
{
var d0, d1, d2, d3, d4, d5, d6, d7;
/* Pass 1: process rows. */
var dataOff=0;
var i;
var I8 = 8;
var I64 = 64;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff+1];
d2 = data[dataOff+2];
d3 = data[dataOff+3];
d4 = data[dataOff+4];
d5 = data[dataOff+5];
d6 = data[dataOff+6];
d7 = data[dataOff+7];
var tmp0 = d0 + d7;
var tmp7 = d0 - d7;
var tmp1 = d1 + d6;
var tmp6 = d1 - d6;
var tmp2 = d2 + d5;
var tmp5 = d2 - d5;
var tmp3 = d3 + d4;
var tmp4 = d3 - d4;
/* Even part */
var tmp10 = tmp0 + tmp3; /* phase 2 */
var tmp13 = tmp0 - tmp3;
var tmp11 = tmp1 + tmp2;
var tmp12 = tmp1 - tmp2;
data[dataOff] = tmp10 + tmp11; /* phase 3 */
data[dataOff+4] = tmp10 - tmp11;
var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff+2] = tmp13 + z1; /* phase 5 */
data[dataOff+6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3 = tmp11 * 0.707106781; /* c4 */
var z11 = tmp7 + z3; /* phase 5 */
var z13 = tmp7 - z3;
data[dataOff+5] = z13 + z2; /* phase 6 */
data[dataOff+3] = z13 - z2;
data[dataOff+1] = z11 + z4;
data[dataOff+7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff + 8];
d2 = data[dataOff + 16];
d3 = data[dataOff + 24];
d4 = data[dataOff + 32];
d5 = data[dataOff + 40];
d6 = data[dataOff + 48];
d7 = data[dataOff + 56];
var tmp0p2 = d0 + d7;
var tmp7p2 = d0 - d7;
var tmp1p2 = d1 + d6;
var tmp6p2 = d1 - d6;
var tmp2p2 = d2 + d5;
var tmp5p2 = d2 - d5;
var tmp3p2 = d3 + d4;
var tmp4p2 = d3 - d4;
/* Even part */
var tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2 = tmp0p2 - tmp3p2;
var tmp11p2 = tmp1p2 + tmp2p2;
var tmp12p2 = tmp1p2 - tmp2p2;
data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2 = tmp11p2 * 0.707106781; /* c4 */
var z11p2 = tmp7p2 + z3p2; /* phase 5 */
var z13p2 = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
var fDCTQuant;
for (i=0; i<I64; ++i)
{
// Apply the quantization and scaling factor & Round to nearest integer
fDCTQuant = data[i]*fdtbl[i];
outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
//outputfDCTQuant[i] = fround(fDCTQuant);
}
return outputfDCTQuant;
}
function writeAPP0()
{
writeWord(0xFFE0); // marker
writeWord(16); // length
writeByte(0x4A); // J
writeByte(0x46); // F
writeByte(0x49); // I
writeByte(0x46); // F
writeByte(0); // = "JFIF",'\0'
writeByte(1); // versionhi
writeByte(1); // versionlo
writeByte(0); // xyunits
writeWord(1); // xdensity
writeWord(1); // ydensity
writeByte(0); // thumbnwidth
writeByte(0); // thumbnheight
}
function writeSOF0(width, height)
{
writeWord(0xFFC0); // marker
writeWord(17); // length, truecolor YUV JPG
writeByte(8); // precision
writeWord(height);
writeWord(width);
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0x11); // HVY
writeByte(0); // QTY
writeByte(2); // IdU
writeByte(0x11); // HVU
writeByte(1); // QTU
writeByte(3); // IdV
writeByte(0x11); // HVV
writeByte(1); // QTV
}
function writeDQT()
{
writeWord(0xFFDB); // marker
writeWord(132); // length
writeByte(0);
for (var i=0; i<64; i++) {
writeByte(YTable[i]);
}
writeByte(1);
for (var j=0; j<64; j++) {
writeByte(UVTable[j]);
}
}
function writeDHT()
{
writeWord(0xFFC4); // marker
writeWord(0x01A2); // length
writeByte(0); // HTYDCinfo
for (var i=0; i<16; i++) {
writeByte(std_dc_luminance_nrcodes[i+1]);
}
for (var j=0; j<=11; j++) {
writeByte(std_dc_luminance_values[j]);
}
writeByte(0x10); // HTYACinfo
for (var k=0; k<16; k++) {
writeByte(std_ac_luminance_nrcodes[k+1]);
}
for (var l=0; l<=161; l++) {
writeByte(std_ac_luminance_values[l]);
}
writeByte(1); // HTUDCinfo
for (var m=0; m<16; m++) {
writeByte(std_dc_chrominance_nrcodes[m+1]);
}
for (var n=0; n<=11; n++) {
writeByte(std_dc_chrominance_values[n]);
}
writeByte(0x11); // HTUACinfo
for (var o=0; o<16; o++) {
writeByte(std_ac_chrominance_nrcodes[o+1]);
}
for (var p=0; p<=161; p++) {
writeByte(std_ac_chrominance_values[p]);
}
}
function writeSOS()
{
writeWord(0xFFDA); // marker
writeWord(12); // length
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0); // HTY
writeByte(2); // IdU
writeByte(0x11); // HTU
writeByte(3); // IdV
writeByte(0x11); // HTV
writeByte(0); // Ss
writeByte(0x3f); // Se
writeByte(0); // Bf
}
function processDU(CDU, fdtbl, DC, HTDC, HTAC){
var EOB = HTAC[0x00];
var M16zeroes = HTAC[0xF0];
var pos;
var I16 = 16;
var I63 = 63;
var I64 = 64;
var DU_DCT = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (var j=0;j<I64;++j) {
DU[ZigZag[j]]=DU_DCT[j];
}
var Diff = DU[0] - DC; DC = DU[0];
//Encode DC
if (Diff==0) {
writeBits(HTDC[0]); // Diff might be 0
} else {
pos = 32767+Diff;
writeBits(HTDC[category[pos]]);
writeBits(bitcode[pos]);
}
//Encode ACs
var end0pos = 63; // was const... which is crazy
for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
//end0pos = first element in reverse order !=0
if ( end0pos == 0) {
writeBits(EOB);
return DC;
}
var i = 1;
var lng;
while ( i <= end0pos ) {
var startpos = i;
for (; (DU[i]==0) && (i<=end0pos); ++i) {}
var nrzeroes = i-startpos;
if ( nrzeroes >= I16 ) {
lng = nrzeroes>>4;
for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
writeBits(M16zeroes);
nrzeroes = nrzeroes&0xF;
}
pos = 32767+DU[i];
writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
writeBits(bitcode[pos]);
i++;
}
if ( end0pos != I63 ) {
writeBits(EOB);
}
return DC;
}
function initCharLookupTable(){
var sfcc = String.fromCharCode;
for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
clt[i] = sfcc(i);
}
}
this.encode = function(image,quality) // image data object
{
// var time_start = new Date().getTime();
if(quality) setQuality(quality);
// Initialize bit writer
byteout = new Array();
bytenew=0;
bytepos=7;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY=0;
var DCU=0;
var DCV=0;
bytenew=0;
bytepos=7;
this.encode.displayName = "_encode_";
var imageData = image.data;
var width = image.width;
var height = image.height;
var quadWidth = width*4;
var tripleWidth = width*3;
var x, y = 0;
var r, g, b;
var start,p, col,row,pos;
while(y < height){
x = 0;
while(x < quadWidth){
start = quadWidth * y + x;
p = start;
col = -1;
row = 0;
for(pos=0; pos < 64; pos++){
row = pos >> 3;// /8
col = ( pos & 7 ) * 4; // %8
p = start + ( row * quadWidth ) + col;
if(y+row >= height){ // padding bottom
p-= (quadWidth*(y+1+row-height));
}
if(x+col >= quadWidth){ // padding right
p-= ((x+col) - quadWidth +4)
}
r = imageData[ p++ ];
g = imageData[ p++ ];
b = imageData[ p++ ];
/* // calculate YUV values dynamically
YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
*/
// use lookup table (slightly faster)
YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128;
UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
}
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
x+=32;
}
y+=8;
}
////////////////////////////////////////////////////////////////
// Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) {
var fillbits = [];
fillbits[1] = bytepos+1;
fillbits[0] = (1<<(bytepos+1))-1;
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
byteout = [];
// benchmarking
// var duration = new Date().getTime() - time_start;
// console.log('Encoding time: '+ currentQuality + 'ms');
//
return jpegDataUri
}
function setQuality(quality){
if (quality <= 0) {
quality = 1;
}
if (quality > 100) {
quality = 100;
}
if(currentQuality == quality) return // don't recalc if unchanged
var sf = 0;
if (quality < 50) {
sf = Math.floor(5000 / quality);
} else {
sf = Math.floor(200 - quality*2);
}
initQuantTables(sf);
currentQuality = quality;
// console.log('Quality set to: '+quality +'%');
}
function init(){
// var time_start = new Date().getTime();
if(!quality) quality = 50;
// Create tables
initCharLookupTable()
initHuffmanTbl();
initCategoryNumber();
initRGBYUVTable();
setQuality(quality);
// var duration = new Date().getTime() - time_start;
// console.log('Initialization '+ duration + 'ms');
}
init();
};
JPEGEncoder.encode = function( data, quality ) {
var encoder = new JPEGEncoder( quality );
return encoder.encode( data );
}
return JPEGEncoder;
});
/**
* @fileOverview Fix android canvas.toDataUrl bug.
*/
define('runtime/html5/androidpatch',[
'runtime/html5/util',
'runtime/html5/jpegencoder',
'base'
], function( Util, encoder, Base ) {
var origin = Util.canvasToDataUrl,
supportJpeg;
Util.canvasToDataUrl = function( canvas, type, quality ) {
var ctx, w, h, fragement, parts;
// 非android手机直接跳过。
if ( !Base.os.android ) {
return origin.apply( null, arguments );
}
// 检测是否canvas支持jpeg导出根据数据格式来判断。
// JPEG 前两位分别是255, 216
if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {
fragement = origin.apply( null, arguments );
parts = fragement.split(',');
if ( ~parts[ 0 ].indexOf('base64') ) {
fragement = atob( parts[ 1 ] );
} else {
fragement = decodeURIComponent( parts[ 1 ] );
}
fragement = fragement.substring( 0, 2 );
supportJpeg = fragement.charCodeAt( 0 ) === 255 &&
fragement.charCodeAt( 1 ) === 216;
}
// 只有在android环境下才修复
if ( type === 'image/jpeg' && !supportJpeg ) {
w = canvas.width;
h = canvas.height;
ctx = canvas.getContext('2d');
return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );
}
return origin.apply( null, arguments );
};
});
/**
* @fileOverview Image
*/
define('runtime/html5/image',[
'base',
'runtime/html5/runtime',
'runtime/html5/util'
], function( Base, Html5Runtime, Util ) {
var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';
return Html5Runtime.register( 'Image', {
// flag: 标记是否被修改过。
modified: false,
init: function() {
var me = this,
img = new Image();
img.onload = function() {
me._info = {
type: me.type,
width: this.width,
height: this.height
};
//debugger;
// 读取meta信息。
if ( !me._metas && 'image/jpeg' === me.type ) {
Util.parseMeta( me._blob, function( error, ret ) {
me._metas = ret;
me.owner.trigger('load');
});
} else {
me.owner.trigger('load');
}
};
img.onerror = function() {
me.owner.trigger('error');
};
me._img = img;
},
loadFromBlob: function( blob ) {
var me = this,
img = me._img;
me._blob = blob;
me.type = blob.type;
img.src = Util.createObjectURL( blob.getSource() );
me.owner.once( 'load', function() {
Util.revokeObjectURL( img.src );
});
},
resize: function( width, height ) {
var canvas = this._canvas ||
(this._canvas = document.createElement('canvas'));
this._resize( this._img, canvas, width, height );
this._blob = null; // 没用了,可以删掉了。
this.modified = true;
this.owner.trigger( 'complete', 'resize' );
},
crop: function( x, y, w, h, s ) {
var cvs = this._canvas ||
(this._canvas = document.createElement('canvas')),
opts = this.options,
img = this._img,
iw = img.naturalWidth,
ih = img.naturalHeight,
orientation = this.getOrientation();
s = s || 1;
// todo 解决 orientation 的问题。
// values that require 90 degree rotation
// if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
// switch ( orientation ) {
// case 6:
// tmp = x;
// x = y;
// y = iw * s - tmp - w;
// console.log(ih * s, tmp, w)
// break;
// }
// (w ^= h, h ^= w, w ^= h);
// }
cvs.width = w;
cvs.height = h;
opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s );
this._blob = null; // 没用了,可以删掉了。
this.modified = true;
this.owner.trigger( 'complete', 'crop' );
},
getAsBlob: function( type ) {
var blob = this._blob,
opts = this.options,
canvas;
type = type || this.type;
// blob需要重新生成。
if ( this.modified || this.type !== type ) {
canvas = this._canvas;
if ( type === 'image/jpeg' ) {
blob = Util.canvasToDataUrl( canvas, type, opts.quality );
if ( opts.preserveHeaders && this._metas &&
this._metas.imageHead ) {
blob = Util.dataURL2ArrayBuffer( blob );
blob = Util.updateImageHead( blob,
this._metas.imageHead );
blob = Util.arrayBufferToBlob( blob, type );
return blob;
}
} else {
blob = Util.canvasToDataUrl( canvas, type );
}
blob = Util.dataURL2Blob( blob );
}
return blob;
},
getAsDataUrl: function( type ) {
var opts = this.options;
type = type || this.type;
if ( type === 'image/jpeg' ) {
return Util.canvasToDataUrl( this._canvas, type, opts.quality );
} else {
return this._canvas.toDataURL( type );
}
},
getOrientation: function() {
return this._metas && this._metas.exif &&
this._metas.exif.get('Orientation') || 1;
},
info: function( val ) {
// setter
if ( val ) {
this._info = val;
return this;
}
// getter
return this._info;
},
meta: function( val ) {
// setter
if ( val ) {
this._metas = val;
return this;
}
// getter
return this._metas;
},
destroy: function() {
var canvas = this._canvas;
this._img.onload = null;
if ( canvas ) {
canvas.getContext('2d')
.clearRect( 0, 0, canvas.width, canvas.height );
canvas.width = canvas.height = 0;
this._canvas = null;
}
// 释放内存。非常重要否则释放不了image的内存。
this._img.src = BLANK;
this._img = this._blob = null;
},
_resize: function( img, cvs, width, height ) {
var opts = this.options,
naturalWidth = img.width,
naturalHeight = img.height,
orientation = this.getOrientation(),
scale, w, h, x, y;
// values that require 90 degree rotation
if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
// 交换width, height的值。
width ^= height;
height ^= width;
width ^= height;
}
scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,
height / naturalHeight );
// 不允许放大。
opts.allowMagnify || (scale = Math.min( 1, scale ));
w = naturalWidth * scale;
h = naturalHeight * scale;
if ( opts.crop ) {
cvs.width = width;
cvs.height = height;
} else {
cvs.width = w;
cvs.height = h;
}
x = (cvs.width - w) / 2;
y = (cvs.height - h) / 2;
opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
this._renderImageToCanvas( cvs, img, x, y, w, h );
},
_rotate2Orientaion: function( canvas, orientation ) {
var width = canvas.width,
height = canvas.height,
ctx = canvas.getContext('2d');
switch ( orientation ) {
case 5:
case 6:
case 7:
case 8:
canvas.width = height;
canvas.height = width;
break;
}
switch ( orientation ) {
case 2: // horizontal flip
ctx.translate( width, 0 );
ctx.scale( -1, 1 );
break;
case 3: // 180 rotate left
ctx.translate( width, height );
ctx.rotate( Math.PI );
break;
case 4: // vertical flip
ctx.translate( 0, height );
ctx.scale( 1, -1 );
break;
case 5: // vertical flip + 90 rotate right
ctx.rotate( 0.5 * Math.PI );
ctx.scale( 1, -1 );
break;
case 6: // 90 rotate right
ctx.rotate( 0.5 * Math.PI );
ctx.translate( 0, -height );
break;
case 7: // horizontal flip + 90 rotate right
ctx.rotate( 0.5 * Math.PI );
ctx.translate( width, -height );
ctx.scale( -1, 1 );
break;
case 8: // 90 rotate left
ctx.rotate( -0.5 * Math.PI );
ctx.translate( -width, 0 );
break;
}
},
// https://github.com/stomita/ios-imagefile-megapixel/
// blob/master/src/megapix-image.js
_renderImageToCanvas: (function() {
// 如果不是ios, 不需要这么复杂!
if ( !Base.os.ios ) {
return function( canvas ) {
var args = Base.slice( arguments, 1 ),
ctx = canvas.getContext('2d');
ctx.drawImage.apply( ctx, args );
};
}
/**
* Detecting vertical squash in loaded image.
* Fixes a bug which squash image vertically while drawing into
* canvas for some images.
*/
function detectVerticalSquash( img, iw, ih ) {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
sy = 0,
ey = ih,
py = ih,
data, alpha, ratio;
canvas.width = 1;
canvas.height = ih;
ctx.drawImage( img, 0, 0 );
data = ctx.getImageData( 0, 0, 1, ih ).data;
// search image edge pixel position in case
// it is squashed vertically.
while ( py > sy ) {
alpha = data[ (py - 1) * 4 + 3 ];
if ( alpha === 0 ) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
ratio = (py / ih);
return (ratio === 0) ? 1 : ratio;
}
// fix ie7 bug
// http://stackoverflow.com/questions/11929099/
// html5-canvas-drawimage-ratio-bug-ios
if ( Base.os.ios >= 7 ) {
return function( canvas, img, x, y, w, h ) {
var iw = img.naturalWidth,
ih = img.naturalHeight,
vertSquashRatio = detectVerticalSquash( img, iw, ih );
return canvas.getContext('2d').drawImage( img, 0, 0,
iw * vertSquashRatio, ih * vertSquashRatio,
x, y, w, h );
};
}
/**
* Detect subsampling in loaded image.
* In iOS, larger images than 2M pixels may be
* subsampled in rendering.
*/
function detectSubsampling( img ) {
var iw = img.naturalWidth,
ih = img.naturalHeight,
canvas, ctx;
// subsampling may happen overmegapixel image
if ( iw * ih > 1024 * 1024 ) {
canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
ctx = canvas.getContext('2d');
ctx.drawImage( img, -iw + 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 ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;
} else {
return false;
}
}
return function( canvas, img, x, y, width, height ) {
var iw = img.naturalWidth,
ih = img.naturalHeight,
ctx = canvas.getContext('2d'),
subsampled = detectSubsampling( img ),
doSquash = this.type === 'image/jpeg',
d = 1024,
sy = 0,
dy = 0,
tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;
if ( subsampled ) {
iw /= 2;
ih /= 2;
}
ctx.save();
tmpCanvas = document.createElement('canvas');
tmpCanvas.width = tmpCanvas.height = d;
tmpCtx = tmpCanvas.getContext('2d');
vertSquashRatio = doSquash ?
detectVerticalSquash( img, iw, ih ) : 1;
dw = Math.ceil( d * width / iw );
dh = Math.ceil( d * height / ih / vertSquashRatio );
while ( sy < ih ) {
sx = 0;
dx = 0;
while ( sx < iw ) {
tmpCtx.clearRect( 0, 0, d, d );
tmpCtx.drawImage( img, -sx, -sy );
ctx.drawImage( tmpCanvas, 0, 0, d, d,
x + dx, y + dy, dw, dh );
sx += d;
dx += dw;
}
sy += d;
dy += dh;
}
ctx.restore();
tmpCanvas = tmpCtx = null;
};
})()
});
});
/**
* @fileOverview Transport
* @todo chunked
*
* chunked
*/
define('runtime/html5/transport',[
'base',
'runtime/html5/runtime'
], function( Base, Html5Runtime ) {
var noop = Base.noop,
$ = Base.$;
return Html5Runtime.register( 'Transport', {
init: function() {
this._status = 0;
this._response = null;
},
send: function() {
var owner = this.owner,
opts = this.options,
xhr = this._initAjax(),
blob = owner._blob,
server = opts.server,
formData, binary, fr;
if ( opts.sendAsBinary ) {
server += (/\?/.test( server ) ? '&' : '?') +
$.param( owner._formData );
binary = blob.getSource();
} else {
formData = new FormData();
$.each( owner._formData, function( k, v ) {
formData.append( k, v );
});
formData.append( opts.fileVal, blob.getSource(),
opts.filename || owner._formData.name || '' );
}
if ( opts.withCredentials && 'withCredentials' in xhr ) {
xhr.open( opts.method, server, true );
xhr.withCredentials = true;
} else {
xhr.open( opts.method, server );
}
this._setRequestHeader( xhr, opts.headers );
if ( binary ) {
// 强制设置成 content-type 为文件流。
xhr.overrideMimeType &&
xhr.overrideMimeType('application/octet-stream');
// android直接发送blob会导致服务端接收到的是空文件。
// bug详情。
// https://code.google.com/p/android/issues/detail?id=39882
// 所以先用fileReader读取出来再通过arraybuffer的方式发送。
if ( Base.os.android ) {
fr = new FileReader();
fr.onload = function() {
xhr.send( this.result );
fr = fr.onload = null;
};
fr.readAsArrayBuffer( binary );
} else {
xhr.send( binary );
}
} else {
xhr.send( formData );
}
},
getResponse: function() {
return this._response;
},
getResponseAsJson: function() {
return this._parseJson( this._response );
},
getStatus: function() {
return this._status;
},
abort: function() {
var xhr = this._xhr;
if ( xhr ) {
xhr.upload.onprogress = noop;
xhr.onreadystatechange = noop;
xhr.abort();
this._xhr = xhr = null;
}
},
destroy: function() {
this.abort();
},
_initAjax: function() {
var me = this,
xhr = new XMLHttpRequest(),
opts = this.options;
if ( opts.withCredentials && !('withCredentials' in xhr) &&
typeof XDomainRequest !== 'undefined' ) {
xhr = new XDomainRequest();
}
xhr.upload.onprogress = function( e ) {
var percentage = 0;
if ( e.lengthComputable ) {
percentage = e.loaded / e.total;
}
return me.trigger( 'progress', percentage );
};
xhr.onreadystatechange = function() {
if ( xhr.readyState !== 4 ) {
return;
}
xhr.upload.onprogress = noop;
xhr.onreadystatechange = noop;
me._xhr = null;
me._status = xhr.status;
if ( xhr.status >= 200 && xhr.status < 300 ) {
me._response = xhr.responseText;
return me.trigger('load');
} else if ( xhr.status >= 500 && xhr.status < 600 ) {
me._response = xhr.responseText;
return me.trigger( 'error', 'server' );
}
return me.trigger( 'error', me._status ? 'http' : 'abort' );
};
me._xhr = xhr;
return xhr;
},
_setRequestHeader: function( xhr, headers ) {
$.each( headers, function( key, val ) {
xhr.setRequestHeader( key, val );
});
},
_parseJson: function( str ) {
var json;
try {
json = JSON.parse( str );
} catch ( ex ) {
json = {};
}
return json;
}
});
});
/**
* @fileOverview Transport flash
*/
define('runtime/html5/md5',[
'runtime/html5/runtime'
], function( FlashRuntime ) {
/*
* Fastest md5 implementation around (JKM md5)
* Credits: Joseph Myers
*
* @see http://www.myersdaily.org/joseph/javascript/md5-text.html
* @see http://jsperf.com/md5-shootout/7
*/
/* this function is much faster,
so if possible we use it. Some IEs
are the only ones I know of that
need the idiotic second function,
generated by an if clause. */
var add32 = function (a, b) {
return (a + b) & 0xFFFFFFFF;
},
cmn = function (q, a, b, x, s, t) {
a = add32(add32(a, q), add32(x, t));
return add32((a << s) | (a >>> (32 - s)), b);
},
ff = function (a, b, c, d, x, s, t) {
return cmn((b & c) | ((~b) & d), a, b, x, s, t);
},
gg = function (a, b, c, d, x, s, t) {
return cmn((b & d) | (c & (~d)), a, b, x, s, t);
},
hh = function (a, b, c, d, x, s, t) {
return cmn(b ^ c ^ d, a, b, x, s, t);
},
ii = function (a, b, c, d, x, s, t) {
return cmn(c ^ (b | (~d)), a, b, x, s, t);
},
md5cycle = function (x, k) {
var a = x[0],
b = x[1],
c = x[2],
d = x[3];
a = ff(a, b, c, d, k[0], 7, -680876936);
d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819);
b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897);
d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341);
b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416);
d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063);
b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682);
d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290);
b = ff(b, c, d, a, k[15], 22, 1236535329);
a = gg(a, b, c, d, k[1], 5, -165796510);
d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713);
b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691);
d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335);
b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438);
d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961);
b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467);
d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473);
b = gg(b, c, d, a, k[12], 20, -1926607734);
a = hh(a, b, c, d, k[5], 4, -378558);
d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562);
b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060);
d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632);
b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174);
d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979);
b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487);
d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520);
b = hh(b, c, d, a, k[2], 23, -995338651);
a = ii(a, b, c, d, k[0], 6, -198630844);
d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905);
b = ii(b, c, d, a, k[5], 21, -57434055);
a = ii(a, b, c, d, k[12], 6, 1700485571);
d = ii(d, a, b, c, k[3], 10, -1894986606);
c = ii(c, d, a, b, k[10], 15, -1051523);
b = ii(b, c, d, a, k[1], 21, -2054922799);
a = ii(a, b, c, d, k[8], 6, 1873313359);
d = ii(d, a, b, c, k[15], 10, -30611744);
c = ii(c, d, a, b, k[6], 15, -1560198380);
b = ii(b, c, d, a, k[13], 21, 1309151649);
a = ii(a, b, c, d, k[4], 6, -145523070);
d = ii(d, a, b, c, k[11], 10, -1120210379);
c = ii(c, d, a, b, k[2], 15, 718787259);
b = ii(b, c, d, a, k[9], 21, -343485551);
x[0] = add32(a, x[0]);
x[1] = add32(b, x[1]);
x[2] = add32(c, x[2]);
x[3] = add32(d, x[3]);
},
/* there needs to be support for Unicode here,
* unless we pretend that we can redefine the MD-5
* algorithm for multi-byte characters (perhaps
* by adding every four 16-bit characters and
* shortening the sum to 32 bits). Otherwise
* I suggest performing MD-5 as if every character
* was two bytes--e.g., 0040 0025 = @%--but then
* how will an ordinary MD-5 sum be matched?
* There is no way to standardize text to something
* like UTF-8 before transformation; speed cost is
* utterly prohibitive. The JavaScript standard
* itself needs to look at this: it should start
* providing access to strings as preformed UTF-8
* 8-bit unsigned value arrays.
*/
md5blk = function (s) {
var md5blks = [],
i; /* Andy King said do it this way. */
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
}
return md5blks;
},
md5blk_array = function (a) {
var md5blks = [],
i; /* Andy King said do it this way. */
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
}
return md5blks;
},
md51 = function (s) {
var n = s.length,
state = [1732584193, -271733879, -1732584194, 271733878],
i,
length,
tail,
tmp,
lo,
hi;
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk(s.substring(i - 64, i)));
}
s = s.substring(i - 64);
length = s.length;
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
}
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) {
md5cycle(state, tail);
for (i = 0; i < 16; i += 1) {
tail[i] = 0;
}
}
// Beware that the final length might not fit in 32 bits so we take care of that
tmp = n * 8;
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
lo = parseInt(tmp[2], 16);
hi = parseInt(tmp[1], 16) || 0;
tail[14] = lo;
tail[15] = hi;
md5cycle(state, tail);
return state;
},
md51_array = function (a) {
var n = a.length,
state = [1732584193, -271733879, -1732584194, 271733878],
i,
length,
tail,
tmp,
lo,
hi;
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
}
// Not sure if it is a bug, however IE10 will always produce a sub array of length 1
// containing the last element of the parent array if the sub array specified starts
// beyond the length of the parent array - weird.
// https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);
length = a.length;
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= a[i] << ((i % 4) << 3);
}
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) {
md5cycle(state, tail);
for (i = 0; i < 16; i += 1) {
tail[i] = 0;
}
}
// Beware that the final length might not fit in 32 bits so we take care of that
tmp = n * 8;
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
lo = parseInt(tmp[2], 16);
hi = parseInt(tmp[1], 16) || 0;
tail[14] = lo;
tail[15] = hi;
md5cycle(state, tail);
return state;
},
hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'],
rhex = function (n) {
var s = '',
j;
for (j = 0; j < 4; j += 1) {
s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
}
return s;
},
hex = function (x) {
var i;
for (i = 0; i < x.length; i += 1) {
x[i] = rhex(x[i]);
}
return x.join('');
},
md5 = function (s) {
return hex(md51(s));
},
////////////////////////////////////////////////////////////////////////////
/**
* SparkMD5 OOP implementation.
*
* Use this class to perform an incremental md5, otherwise use the
* static methods instead.
*/
SparkMD5 = function () {
// call reset to init the instance
this.reset();
};
// In some cases the fast add32 function cannot be used..
if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') {
add32 = function (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF),
msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
};
}
/**
* Appends a string.
* A conversion will be applied if an utf8 string is detected.
*
* @param {String} str The string to be appended
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.append = function (str) {
// converts the string to utf8 bytes if necessary
if (/[\u0080-\uFFFF]/.test(str)) {
str = unescape(encodeURIComponent(str));
}
// then append as binary
this.appendBinary(str);
return this;
};
/**
* Appends a binary string.
*
* @param {String} contents The binary string to be appended
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.appendBinary = function (contents) {
this._buff += contents;
this._length += contents.length;
var length = this._buff.length,
i;
for (i = 64; i <= length; i += 64) {
md5cycle(this._state, md5blk(this._buff.substring(i - 64, i)));
}
this._buff = this._buff.substr(i - 64);
return this;
};
/**
* Finishes the incremental computation, reseting the internal state and
* returning the result.
* Use the raw parameter to obtain the raw result instead of the hex one.
*
* @param {Boolean} raw True to get the raw result, false to get the hex result
*
* @return {String|Array} The result
*/
SparkMD5.prototype.end = function (raw) {
var buff = this._buff,
length = buff.length,
i,
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
ret;
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);
}
this._finish(tail, length);
ret = !!raw ? this._state : hex(this._state);
this.reset();
return ret;
};
/**
* Finish the final calculation based on the tail.
*
* @param {Array} tail The tail (will be modified)
* @param {Number} length The length of the remaining buffer
*/
SparkMD5.prototype._finish = function (tail, length) {
var i = length,
tmp,
lo,
hi;
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) {
md5cycle(this._state, tail);
for (i = 0; i < 16; i += 1) {
tail[i] = 0;
}
}
// Do the final computation based on the tail and length
// Beware that the final length may not fit in 32 bits so we take care of that
tmp = this._length * 8;
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
lo = parseInt(tmp[2], 16);
hi = parseInt(tmp[1], 16) || 0;
tail[14] = lo;
tail[15] = hi;
md5cycle(this._state, tail);
};
/**
* Resets the internal state of the computation.
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.reset = function () {
this._buff = "";
this._length = 0;
this._state = [1732584193, -271733879, -1732584194, 271733878];
return this;
};
/**
* Releases memory used by the incremental buffer and other aditional
* resources. If you plan to use the instance again, use reset instead.
*/
SparkMD5.prototype.destroy = function () {
delete this._state;
delete this._buff;
delete this._length;
};
/**
* Performs the md5 hash on a string.
* A conversion will be applied if utf8 string is detected.
*
* @param {String} str The string
* @param {Boolean} raw True to get the raw result, false to get the hex result
*
* @return {String|Array} The result
*/
SparkMD5.hash = function (str, raw) {
// converts the string to utf8 bytes if necessary
if (/[\u0080-\uFFFF]/.test(str)) {
str = unescape(encodeURIComponent(str));
}
var hash = md51(str);
return !!raw ? hash : hex(hash);
};
/**
* Performs the md5 hash on a binary string.
*
* @param {String} content The binary string
* @param {Boolean} raw True to get the raw result, false to get the hex result
*
* @return {String|Array} The result
*/
SparkMD5.hashBinary = function (content, raw) {
var hash = md51(content);
return !!raw ? hash : hex(hash);
};
/**
* SparkMD5 OOP implementation for array buffers.
*
* Use this class to perform an incremental md5 ONLY for array buffers.
*/
SparkMD5.ArrayBuffer = function () {
// call reset to init the instance
this.reset();
};
////////////////////////////////////////////////////////////////////////////
/**
* Appends an array buffer.
*
* @param {ArrayBuffer} arr The array to be appended
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
SparkMD5.ArrayBuffer.prototype.append = function (arr) {
// TODO: we could avoid the concatenation here but the algorithm would be more complex
// if you find yourself needing extra performance, please make a PR.
var buff = this._concatArrayBuffer(this._buff, arr),
length = buff.length,
i;
this._length += arr.byteLength;
for (i = 64; i <= length; i += 64) {
md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i)));
}
// Avoids IE10 weirdness (documented above)
this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0);
return this;
};
/**
* Finishes the incremental computation, reseting the internal state and
* returning the result.
* Use the raw parameter to obtain the raw result instead of the hex one.
*
* @param {Boolean} raw True to get the raw result, false to get the hex result
*
* @return {String|Array} The result
*/
SparkMD5.ArrayBuffer.prototype.end = function (raw) {
var buff = this._buff,
length = buff.length,
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
i,
ret;
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= buff[i] << ((i % 4) << 3);
}
this._finish(tail, length);
ret = !!raw ? this._state : hex(this._state);
this.reset();
return ret;
};
SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
/**
* Resets the internal state of the computation.
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
SparkMD5.ArrayBuffer.prototype.reset = function () {
this._buff = new Uint8Array(0);
this._length = 0;
this._state = [1732584193, -271733879, -1732584194, 271733878];
return this;
};
/**
* Releases memory used by the incremental buffer and other aditional
* resources. If you plan to use the instance again, use reset instead.
*/
SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
/**
* Concats two array buffers, returning a new one.
*
* @param {ArrayBuffer} first The first array buffer
* @param {ArrayBuffer} second The second array buffer
*
* @return {ArrayBuffer} The new array buffer
*/
SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) {
var firstLength = first.length,
result = new Uint8Array(firstLength + second.byteLength);
result.set(first);
result.set(new Uint8Array(second), firstLength);
return result;
};
/**
* Performs the md5 hash on an array buffer.
*
* @param {ArrayBuffer} arr The array buffer
* @param {Boolean} raw True to get the raw result, false to get the hex result
*
* @return {String|Array} The result
*/
SparkMD5.ArrayBuffer.hash = function (arr, raw) {
var hash = md51_array(new Uint8Array(arr));
return !!raw ? hash : hex(hash);
};
return FlashRuntime.register( 'Md5', {
init: function() {
// do nothing.
},
loadFromBlob: function( file ) {
var blob = file.getSource(),
chunkSize = 2 * 1024 * 1024,
chunks = Math.ceil( blob.size / chunkSize ),
chunk = 0,
owner = this.owner,
spark = new SparkMD5.ArrayBuffer(),
me = this,
blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice,
loadNext, fr;
fr = new FileReader();
loadNext = function() {
var start, end;
start = chunk * chunkSize;
end = Math.min( start + chunkSize, blob.size );
fr.onload = function( e ) {
spark.append( e.target.result );
owner.trigger( 'progress', {
total: file.size,
loaded: end
});
};
fr.onloadend = function() {
fr.onloadend = fr.onload = null;
if ( ++chunk < chunks ) {
setTimeout( loadNext, 1 );
} else {
setTimeout(function(){
owner.trigger('load');
me.result = spark.end();
loadNext = file = blob = spark = null;
owner.trigger('complete');
}, 50 );
}
};
fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) );
};
loadNext();
},
getResult: function() {
return this.result;
}
});
});
/**
* @fileOverview FlashRuntime
*/
define('runtime/flash/runtime',[
'base',
'runtime/runtime',
'runtime/compbase'
], function( Base, Runtime, CompBase ) {
var $ = Base.$,
type = 'flash',
components = {};
function getFlashVersion() {
var version;
try {
version = navigator.plugins[ 'Shockwave Flash' ];
version = version.description;
} catch ( ex ) {
try {
version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
.GetVariable('$version');
} catch ( ex2 ) {
version = '0.0';
}
}
version = version.match( /\d+/g );
return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
}
function FlashRuntime() {
var pool = {},
clients = {},
destroy = this.destroy,
me = this,
jsreciver = Base.guid('webuploader_');
Runtime.apply( me, arguments );
me.type = type;
// 这个方法的调用者实际上是RuntimeClient
me.exec = function( comp, fn/*, args...*/ ) {
var client = this,
uid = client.uid,
args = Base.slice( arguments, 2 ),
instance;
clients[ uid ] = client;
if ( components[ comp ] ) {
if ( !pool[ uid ] ) {
pool[ uid ] = new components[ comp ]( client, me );
}
instance = pool[ uid ];
if ( instance[ fn ] ) {
return instance[ fn ].apply( instance, args );
}
}
return me.flashExec.apply( client, arguments );
};
function handler( evt, obj ) {
var type = evt.type || evt,
parts, uid;
parts = type.split('::');
uid = parts[ 0 ];
type = parts[ 1 ];
// console.log.apply( console, arguments );
if ( type === 'Ready' && uid === me.uid ) {
me.trigger('ready');
} else if ( clients[ uid ] ) {
clients[ uid ].trigger( type.toLowerCase(), evt, obj );
}
// Base.log( evt, obj );
}
// flash的接受器。
window[ jsreciver ] = function() {
var args = arguments;
// 为了能捕获得到。
setTimeout(function() {
handler.apply( null, args );
}, 1 );
};
this.jsreciver = jsreciver;
this.destroy = function() {
// @todo 删除池子中的所有实例
return destroy && destroy.apply( this, arguments );
};
this.flashExec = function( comp, fn ) {
var flash = me.getFlash(),
args = Base.slice( arguments, 2 );
return flash.exec( this.uid, comp, fn, args );
};
// @todo
}
Base.inherits( Runtime, {
constructor: FlashRuntime,
init: function() {
var container = this.getContainer(),
opts = this.options,
html;
// if not the minimal height, shims are not initialized
// in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
container.css({
position: 'absolute',
top: '-8px',
left: '-8px',
width: '9px',
height: '9px',
overflow: 'hidden'
});
// insert flash object
html = '<object id="' + this.uid + '" type="application/' +
'x-shockwave-flash" data="' + opts.swf + '" ';
if ( Base.browser.ie ) {
html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
}
html += 'width="100%" height="100%" style="outline:0">' +
'<param name="movie" value="' + opts.swf + '" />' +
'<param name="flashvars" value="uid=' + this.uid +
'&jsreciver=' + this.jsreciver + '" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowscriptaccess" value="always" />' +
'</object>';
container.html( html );
},
getFlash: function() {
if ( this._flash ) {
return this._flash;
}
this._flash = $( '#' + this.uid ).get( 0 );
return this._flash;
}
});
FlashRuntime.register = function( name, component ) {
component = components[ name ] = Base.inherits( CompBase, $.extend({
// @todo fix this later
flashExec: function() {
var owner = this.owner,
runtime = this.getRuntime();
return runtime.flashExec.apply( owner, arguments );
}
}, component ) );
return component;
};
if ( getFlashVersion() >= 11.4 ) {
Runtime.addRuntime( type, FlashRuntime );
}
return FlashRuntime;
});
/**
* @fileOverview FilePicker
*/
define('runtime/flash/filepicker',[
'base',
'runtime/flash/runtime'
], function( Base, FlashRuntime ) {
var $ = Base.$;
return FlashRuntime.register( 'FilePicker', {
init: function( opts ) {
var copy = $.extend({}, opts ),
len, i;
// 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
len = copy.accept && copy.accept.length;
for ( i = 0; i < len; i++ ) {
if ( !copy.accept[ i ].title ) {
copy.accept[ i ].title = 'Files';
}
}
delete copy.button;
delete copy.id;
delete copy.container;
this.flashExec( 'FilePicker', 'init', copy );
},
destroy: function() {
this.flashExec( 'FilePicker', 'destroy' );
}
});
});
/**
* @fileOverview
*/
define('runtime/flash/image',[
'runtime/flash/runtime'
], function( FlashRuntime ) {
return FlashRuntime.register( 'Image', {
// init: function( options ) {
// var owner = this.owner;
// this.flashExec( 'Image', 'init', options );
// owner.on( 'load', function() {
// debugger;
// });
// },
loadFromBlob: function( blob ) {
var owner = this.owner;
owner.info() && this.flashExec( 'Image', 'info', owner.info() );
owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );
this.flashExec( 'Image', 'loadFromBlob', blob.uid );
}
});
});
/**
* @fileOverview Transport flash
*/
define('runtime/flash/transport',[
'base',
'runtime/flash/runtime',
'runtime/client'
], function( Base, FlashRuntime, RuntimeClient ) {
var $ = Base.$;
return FlashRuntime.register( 'Transport', {
init: function() {
this._status = 0;
this._response = null;
this._responseJson = null;
},
send: function() {
var owner = this.owner,
opts = this.options,
xhr = this._initAjax(),
blob = owner._blob,
server = opts.server,
binary;
xhr.connectRuntime( blob.ruid );
if ( opts.sendAsBinary ) {
server += (/\?/.test( server ) ? '&' : '?') +
$.param( owner._formData );
binary = blob.uid;
} else {
$.each( owner._formData, function( k, v ) {
xhr.exec( 'append', k, v );
});
xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
opts.filename || owner._formData.name || '' );
}
this._setRequestHeader( xhr, opts.headers );
xhr.exec( 'send', {
method: opts.method,
url: server,
forceURLStream: opts.forceURLStream,
mimeType: 'application/octet-stream'
}, binary );
},
getStatus: function() {
return this._status;
},
getResponse: function() {
return this._response || '';
},
getResponseAsJson: function() {
return this._responseJson;
},
abort: function() {
var xhr = this._xhr;
if ( xhr ) {
xhr.exec('abort');
xhr.destroy();
this._xhr = xhr = null;
}
},
destroy: function() {
this.abort();
},
_initAjax: function() {
var me = this,
xhr = new RuntimeClient('XMLHttpRequest');
xhr.on( 'uploadprogress progress', function( e ) {
var percent = e.loaded / e.total;
percent = Math.min( 1, Math.max( 0, percent ) );
return me.trigger( 'progress', percent );
});
xhr.on( 'load', function() {
var status = xhr.exec('getStatus'),
readBody = false,
err = '',
p;
xhr.off();
me._xhr = null;
if ( status >= 200 && status < 300 ) {
readBody = true;
} else if ( status >= 500 && status < 600 ) {
readBody = true;
err = 'server';
} else {
err = 'http';
}
if ( readBody ) {
me._response = xhr.exec('getResponse');
me._response = decodeURIComponent( me._response );
// flash 处理可能存在 bug, 没辙只能靠 js 了
// try {
// me._responseJson = xhr.exec('getResponseAsJson');
// } catch ( error ) {
p = function( s ) {
try {
if (window.JSON && window.JSON.parse) {
return JSON.parse(s);
}
return new Function('return ' + s).call();
} catch ( err ) {
return {};
}
};
me._responseJson = me._response ? p(me._response) : {};
// }
}
xhr.destroy();
xhr = null;
return err ? me.trigger( 'error', err ) : me.trigger('load');
});
xhr.on( 'error', function() {
xhr.off();
me._xhr = null;
me.trigger( 'error', 'http' );
});
me._xhr = xhr;
return xhr;
},
_setRequestHeader: function( xhr, headers ) {
$.each( headers, function( key, val ) {
xhr.exec( 'setRequestHeader', key, val );
});
}
});
});
/**
* @fileOverview Blob Html
*/
define('runtime/flash/blob',[
'runtime/flash/runtime',
'lib/blob'
], function( FlashRuntime, Blob ) {
return FlashRuntime.register( 'Blob', {
slice: function( start, end ) {
var blob = this.flashExec( 'Blob', 'slice', start, end );
return new Blob( this.getRuid(), blob );
}
});
});
/**
* @fileOverview Md5 flash
*/
define('runtime/flash/md5',[
'runtime/flash/runtime'
], function( FlashRuntime ) {
return FlashRuntime.register( 'Md5', {
init: function() {
// do nothing.
},
loadFromBlob: function( blob ) {
return this.flashExec( 'Md5', 'loadFromBlob', blob.uid );
}
});
});
/**
* @fileOverview
*/
define('preset/all',[
'base',
// widgets
'widgets/filednd',
'widgets/filepaste',
'widgets/filepicker',
'widgets/image',
'widgets/queue',
'widgets/runtime',
'widgets/upload',
'widgets/validator',
'widgets/md5',
// runtimes
// html5
'runtime/html5/blob',
'runtime/html5/dnd',
'runtime/html5/filepaste',
'runtime/html5/filepicker',
'runtime/html5/imagemeta/exif',
'runtime/html5/androidpatch',
'runtime/html5/image',
'runtime/html5/transport',
'runtime/html5/md5',
// flash
'runtime/flash/filepicker',
'runtime/flash/image',
'runtime/flash/transport',
'runtime/flash/blob',
'runtime/flash/md5'
], function( Base ) {
return Base;
});
define('webuploader',[
'preset/all'
], function( preset ) {
return preset;
});
return require('webuploader');
});