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