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
						
					
					
				| /*! 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; }); } |