<commit_before>

// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties
mejs.MediaPluginBridge = {

	pluginMediaElements:{},
	htmlMediaElements:{},

	registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) {
		this.pluginMediaElements[id] = pluginMediaElement;
		this.htmlMediaElements[id] = htmlMediaElement;
	},

	// when Flash/Silverlight is ready, it calls out to this method
	initPlugin: function (id) {

		var pluginMediaElement = this.pluginMediaElements[id],
			htmlMediaElement = this.htmlMediaElements[id];

		// find the javascript bridge
		switch (pluginMediaElement.pluginType) {
			case "flash":
				pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id);
				break;
			case "silverlight":
				pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
				pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.SilverlightApp;
				break;
		}

		if (pluginMediaElement.success) {
			pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
		}
	},

	// receives events from Flash/Silverlight and sends them out as HTML5 media events
	// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
	fireEvent: function (id, eventName, values) {

		var
			e,
			i,
			bufferedTime,
			pluginMediaElement = this.pluginMediaElements[id];

		pluginMediaElement.ended = false;
		pluginMediaElement.paused = true;

		// fake event object to mimic real HTML media event.
		e = {
			type: eventName,
			target: pluginMediaElement
		};

		// attach all values to element and event object
		for (i in values) {
			pluginMediaElement[i] = values[i];
			e[i] = values[i];
		}

		// fake the newer W3C buffered TimeRange (loaded and total have been removed)
		bufferedTime = values.bufferedTime || 0;

		e.target.buffered = e.buffered = {
			start: function(index) {
				return 0;
			},
			end: function (index) {
				return bufferedTime;
			},
			length: 1
		};

		pluginMediaElement.dispatchEvent(e.type, e);
	}
};

/*
Default options
*/
mejs.MediaElementDefaults = {
	// shows debug errors on screen
	enablePluginDebug: false,
	// remove or reorder to change plugin priority
	plugins: ['flash','silverlight'],
	// specify to force MediaElement into a mode
	type: '',
	// path to Flash and Silverlight plugins
	pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
	// name of flash file
	flashName: 'flashmediaelement.swf',
	// name of silverlight file
	silverlightName: 'silverlightmediaelement.xap',
	// default if the <video width> is not specified
	defaultVideoWidth: 480,
	// default if the <video height> is not specified
	defaultVideoHeight: 270,
	// overrides <video width>
	pluginWidth: -1,
	// overrides <video height>
	pluginHeight: -1,
	// rate in milliseconds for Flash and Silverlight to fire the timeupdate event
	// larger number is less accurate, but less strain on plugin->JavaScript bridge
	timerRate: 250,
	success: function () { },
	error: function () { }
};

/*
Determines if a browser supports the <video> or <audio> element
and returns either the native element or a Flash/Silverlight version that
mimics HTML5 MediaElement
*/
mejs.MediaElement = function (el, o) {
	mejs.HtmlMediaElementShim.create(el,o);
};

mejs.HtmlMediaElementShim = {

	create: function(el, o) {
		var
			options = mejs.MediaElementDefaults,
			htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
			isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'),
			supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'),
			playback = {method:'', url:''},
			poster = htmlMediaElement.getAttribute('poster'),
			autoplay =  htmlMediaElement.getAttribute('autoplay'),
			preload =  htmlMediaElement.getAttribute('preload'),
			prop;

		// extend options
		for (prop in o) {
			options[prop] = o[prop];
		}

		// check for real poster
		poster = (typeof poster == 'undefined' || poster === null) ? '' : poster;
		preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload;
		autoplay = (typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false') ? '' : autoplay;

		// test for HTML5 and plugin capabilities
		playback = this.determinePlayback(htmlMediaElement, options, isVideo, supportsMediaTag);

		if (playback.method == 'native') {
			// add methods to native HTMLMediaElement
			this.updateNative( htmlMediaElement, options);
		} else if (playback.method !== '') {
			// create plugin to mimic HTMLMediaElement
			this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url).replace('&','%26') : '', poster, autoplay, preload);
		} else {
			// boo, no HTML5, no Flash, no Silverlight.
			this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster );
		}
	},

	determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) {

		var
			mediaFiles = [],
			i,
			j,
			k,
			l,
			n,
			url,
			type,
			result = { method: '', url: ''},
			src = htmlMediaElement.getAttribute('src'),
			pluginName,
			pluginVersions,
			pluginInfo;

		// STEP 1: Get Files from <video src> or <source src>

		// supplied type overrides all HTML
		if (typeof (options.type) != 'undefined' && options.type !== '') {
			mediaFiles.push({type:options.type, url:null});

		// test for src attribute first
		} else if (src  != 'undefined' && src  !== null) {
			url = htmlMediaElement.getAttribute('src');
			type = this.checkType(url, htmlMediaElement.getAttribute('type'), isVideo);
			mediaFiles.push({type:type, url:url});

		// then test for <source> elements
		} else {
			// test <source> types to see if they are usable
			for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
				n = htmlMediaElement.childNodes[i];
				if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
					url = n.getAttribute('src');
					type = this.checkType(url, n.getAttribute('type'), isVideo);
					mediaFiles.push({type:type, url:url});
				}
			}
		}

		// STEP 2: Test for playback method

		// test for native playback first
		if (supportsMediaTag) {
			for (i=0; i<mediaFiles.length; i++) {
				if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '') {
					result.method = 'native';
					result.url = mediaFiles[i].url;
					return result;
				}
			}
		}

		// if native playback didn't work, then test plugins
		for (i=0; i<mediaFiles.length; i++) {
			type = mediaFiles[i].type;

			// test all plugins in order of preference [silverlight, flash]
			for (j=0; j<options.plugins.length; j++) {

				pluginName = options.plugins[j];

				// test version of plugin (for future features)
				pluginVersions = mejs.plugins[pluginName];
				for (k=0; k<pluginVersions.length; k++) {
					pluginInfo = pluginVersions[k];

					// test if user has the correct plugin version
					if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {

						// test for plugin playback types
						for (l=0; l<pluginInfo.types.length; l++) {
							// find plugin that can play the type
							if (type == pluginInfo.types[l]) {
								result.method = pluginName;
								result.url = mediaFiles[i].url;
								return result;
							}
						}
					}
				}
			}
		}

		// what if there's nothing to play? just grab the first available
		if (result.method === '') {
			result.url = mediaFiles[0].url;
		}

		return result;

	},

	checkType: function(url, type, isVideo) {
		var ext;

		// if no type is supplied, fake it with the extension
		if (url && !type) {
			ext = url.substring(url.lastIndexOf('.') + 1);
			return ((isVideo) ? 'video' : 'audio') + '/' + ext;
		} else {
			return type;
		}
	},

	createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) {
		var errorContainer = document.createElement('div');
		errorContainer.className = 'me-cannotplay';

		try {
			errorContainer.style.width = htmlMediaElement.width + 'px';
			errorContainer.style.height = htmlMediaElement.height + 'px';
		} catch (e) {}

		errorContainer.innerHTML = (poster !== '') ?
			'' :
			'Download File';

		htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
		htmlMediaElement.style.display = 'none';

		options.error(htmlMediaElement);
	},

	createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload) {

		var width = 1,
			height = 1,
			pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++),
			pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType),
			container = document.createElement('div'),
			node,
			initVars;

		// check for placement inside a
 tag (sometimes WYSIWYG editors do this)
		node = htmlMediaElement.parentNode;
		while (node !== null && node.tagName.toLowerCase() != 'body') {
			if (node.parentNode.tagName.toLowerCase() == 'p') {
				node.parentNode.parentNode.insertBefore(node, node.parentNode);
				break;
			}
			node = node.parentNode;
		}

		if (isVideo) {
			width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
			height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
		} else {
			if (options.enablePluginDebug) {
				width = 320;
				height = 240;
			}
		}

		// register plugin
		pluginMediaElement.success = options.success;
		mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement);

		// add container (must be added to DOM before inserting HTML for IE)
		container.className = 'me-plugin';
		htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);

		// flash/silverlight vars
		initVars = [
			'id=' + pluginid,
			'poster=' + poster,
			'isvideo=' + isVideo.toString(),
			'autoplay=' + autoplay,
			'preload=' + preload,
			'width=' + width,
			'timerrate=' + options.timerRate,
			'height=' + height];

		if (mediaUrl !== null) {
			initVars.push('file=' + mediaUrl);
		}
		if (options.enablePluginDebug) {
			initVars.push('debug=true');
		}

		switch (pluginType) {
			case 'silverlight':
				container.innerHTML =
'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' +
'<param name="initParams" value="' + initVars.join(',') + '" />' +
'<param name="windowless" value="true" />' +
'<param name="background" value="black" />' +
'<param name="minRuntimeVersion" value="3.0.0.0" />' +
'<param name="autoUpgrade" value="true" />' +
'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
'</object>';
					break;

			case 'flash':

				if (mejs.MediaFeatures.isIE) {
					container.outerHTML =
'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
'id="' + pluginid + '" width="' + width + '" height="' + height + '">' +
'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
'<param name="flashvars" value="' + initVars.join('&') + '" />' +
'<param name="quality" value="high" />' +
'<param name="bgcolor" value="#000000" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowScriptAccess" value="sameDomain" />' +
'<param name="allowFullScreen" value="true" />' +
'</object>';

				} else {

					container.innerHTML =
'<embed id="' + pluginid + '" name="' + pluginid + '" ' +
'play="true" ' +
'loop="false" ' +
'quality="high" ' +
'bgcolor="#000000" ' +
'wmode="transparent" ' +
'allowScriptAccess="sameDomain" ' +
'allowFullScreen="true" ' +
'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
'src="' + options.pluginPath + options.flashName + '?' + initVars.join('&') + '" ' +
'width="' + width + '" ' +
'height="' + height + '"></embed>';
				}
				break;
		}
		// hide original element
		htmlMediaElement.style.display = 'none';

		// FYI: options.success will be fired by the MediaPluginBridge
	},

	updateNative: function(htmlMediaElement, options) {

		// add methods to video object to bring it into parity with Flash Object
		for (var m in mejs.HtmlMediaElement) {
			htmlMediaElement[m] = mejs.HtmlMediaElement[m];
		}

		// fire success code
		options.success(htmlMediaElement, htmlMediaElement);
	}
};

window.mejs = mejs;
window.MediaElement = mejs.MediaElement;<commit_msg>- preload fix for Chrome<commit_after>

// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties
mejs.MediaPluginBridge = {

	pluginMediaElements:{},
	htmlMediaElements:{},

	registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) {
		this.pluginMediaElements[id] = pluginMediaElement;
		this.htmlMediaElements[id] = htmlMediaElement;
	},

	// when Flash/Silverlight is ready, it calls out to this method
	initPlugin: function (id) {

		var pluginMediaElement = this.pluginMediaElements[id],
			htmlMediaElement = this.htmlMediaElements[id];

		// find the javascript bridge
		switch (pluginMediaElement.pluginType) {
			case "flash":
				pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id);
				break;
			case "silverlight":
				pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
				pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.SilverlightApp;
				break;
		}

		if (pluginMediaElement.success) {
			pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
		}
	},

	// receives events from Flash/Silverlight and sends them out as HTML5 media events
	// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
	fireEvent: function (id, eventName, values) {

		var
			e,
			i,
			bufferedTime,
			pluginMediaElement = this.pluginMediaElements[id];

		pluginMediaElement.ended = false;
		pluginMediaElement.paused = true;

		// fake event object to mimic real HTML media event.
		e = {
			type: eventName,
			target: pluginMediaElement
		};

		// attach all values to element and event object
		for (i in values) {
			pluginMediaElement[i] = values[i];
			e[i] = values[i];
		}

		// fake the newer W3C buffered TimeRange (loaded and total have been removed)
		bufferedTime = values.bufferedTime || 0;

		e.target.buffered = e.buffered = {
			start: function(index) {
				return 0;
			},
			end: function (index) {
				return bufferedTime;
			},
			length: 1
		};

		pluginMediaElement.dispatchEvent(e.type, e);
	}
};

/*
Default options
*/
mejs.MediaElementDefaults = {
	// shows debug errors on screen
	enablePluginDebug: false,
	// remove or reorder to change plugin priority
	plugins: ['flash','silverlight'],
	// specify to force MediaElement into a mode
	type: '',
	// path to Flash and Silverlight plugins
	pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
	// name of flash file
	flashName: 'flashmediaelement.swf',
	// name of silverlight file
	silverlightName: 'silverlightmediaelement.xap',
	// default if the <video width> is not specified
	defaultVideoWidth: 480,
	// default if the <video height> is not specified
	defaultVideoHeight: 270,
	// overrides <video width>
	pluginWidth: -1,
	// overrides <video height>
	pluginHeight: -1,
	// rate in milliseconds for Flash and Silverlight to fire the timeupdate event
	// larger number is less accurate, but less strain on plugin->JavaScript bridge
	timerRate: 250,
	success: function () { },
	error: function () { }
};

/*
Determines if a browser supports the <video> or <audio> element
and returns either the native element or a Flash/Silverlight version that
mimics HTML5 MediaElement
*/
mejs.MediaElement = function (el, o) {
	mejs.HtmlMediaElementShim.create(el,o);
};

mejs.HtmlMediaElementShim = {

	create: function(el, o) {
		var
			options = mejs.MediaElementDefaults,
			htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
			isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'),
			supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'),
			playback = {method:'', url:''},
			poster = htmlMediaElement.getAttribute('poster'),
			autoplay =  htmlMediaElement.getAttribute('autoplay'),
			preload =  htmlMediaElement.getAttribute('preload'),
			prop;

		// extend options
		for (prop in o) {
			options[prop] = o[prop];
		}

		// check for real poster
		poster = (typeof poster == 'undefined' || poster === null) ? '' : poster;
		preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload;
		autoplay = (typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false') ? '' : autoplay;

		// test for HTML5 and plugin capabilities
		playback = this.determinePlayback(htmlMediaElement, options, isVideo, supportsMediaTag);

		if (playback.method == 'native') {
			// add methods to native HTMLMediaElement
			this.updateNative( htmlMediaElement, options, preload, playback);
		} else if (playback.method !== '') {
			// create plugin to mimic HTMLMediaElement
			this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url).replace('&','%26') : '', poster, autoplay, preload);
		} else {
			// boo, no HTML5, no Flash, no Silverlight.
			this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster );
		}
	},

	determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) {

		var
			mediaFiles = [],
			i,
			j,
			k,
			l,
			n,
			url,
			type,
			result = { method: '', url: ''},
			src = htmlMediaElement.getAttribute('src'),
			pluginName,
			pluginVersions,
			pluginInfo;

		// STEP 1: Get Files from <video src> or <source src>

		// supplied type overrides all HTML
		if (typeof (options.type) != 'undefined' && options.type !== '') {
			mediaFiles.push({type:options.type, url:null});

		// test for src attribute first
		} else if (src  != 'undefined' && src  !== null) {
			url = htmlMediaElement.getAttribute('src');
			type = this.checkType(url, htmlMediaElement.getAttribute('type'), isVideo);
			mediaFiles.push({type:type, url:url});

		// then test for <source> elements
		} else {
			// test <source> types to see if they are usable
			for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
				n = htmlMediaElement.childNodes[i];
				if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
					url = n.getAttribute('src');
					type = this.checkType(url, n.getAttribute('type'), isVideo);
					mediaFiles.push({type:type, url:url});
				}
			}
		}

		// STEP 2: Test for playback method

		// test for native playback first
		if (supportsMediaTag) {
			for (i=0; i<mediaFiles.length; i++) {
				if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '') {
					result.method = 'native';
					result.url = mediaFiles[i].url;
					return result;
				}
			}
		}

		// if native playback didn't work, then test plugins
		for (i=0; i<mediaFiles.length; i++) {
			type = mediaFiles[i].type;

			// test all plugins in order of preference [silverlight, flash]
			for (j=0; j<options.plugins.length; j++) {

				pluginName = options.plugins[j];

				// test version of plugin (for future features)
				pluginVersions = mejs.plugins[pluginName];
				for (k=0; k<pluginVersions.length; k++) {
					pluginInfo = pluginVersions[k];

					// test if user has the correct plugin version
					if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {

						// test for plugin playback types
						for (l=0; l<pluginInfo.types.length; l++) {
							// find plugin that can play the type
							if (type == pluginInfo.types[l]) {
								result.method = pluginName;
								result.url = mediaFiles[i].url;
								return result;
							}
						}
					}
				}
			}
		}

		// what if there's nothing to play? just grab the first available
		if (result.method === '') {
			result.url = mediaFiles[0].url;
		}

		return result;

	},

	checkType: function(url, type, isVideo) {
		var ext;

		// if no type is supplied, fake it with the extension
		if (url && !type) {
			ext = url.substring(url.lastIndexOf('.') + 1);
			return ((isVideo) ? 'video' : 'audio') + '/' + ext;
		} else {
			return type;
		}
	},

	createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) {
		var errorContainer = document.createElement('div');
		errorContainer.className = 'me-cannotplay';

		try {
			errorContainer.style.width = htmlMediaElement.width + 'px';
			errorContainer.style.height = htmlMediaElement.height + 'px';
		} catch (e) {}

		errorContainer.innerHTML = (poster !== '') ?
			'' :
			'Download File';

		htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
		htmlMediaElement.style.display = 'none';

		options.error(htmlMediaElement);
	},

	createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload) {

		var width = 1,
			height = 1,
			pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++),
			pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType),
			container = document.createElement('div'),
			node,
			initVars;

		// check for placement inside a
 tag (sometimes WYSIWYG editors do this)
		node = htmlMediaElement.parentNode;
		while (node !== null && node.tagName.toLowerCase() != 'body') {
			if (node.parentNode.tagName.toLowerCase() == 'p') {
				node.parentNode.parentNode.insertBefore(node, node.parentNode);
				break;
			}
			node = node.parentNode;
		}

		if (isVideo) {
			width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
			height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
		} else {
			if (options.enablePluginDebug) {
				width = 320;
				height = 240;
			}
		}

		// register plugin
		pluginMediaElement.success = options.success;
		mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement);

		// add container (must be added to DOM before inserting HTML for IE)
		container.className = 'me-plugin';
		htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);

		// flash/silverlight vars
		initVars = [
			'id=' + pluginid,
			'poster=' + poster,
			'isvideo=' + isVideo.toString(),
			'autoplay=' + autoplay,
			'preload=' + preload,
			'width=' + width,
			'timerrate=' + options.timerRate,
			'height=' + height];

		if (mediaUrl !== null) {
			initVars.push('file=' + mediaUrl);
		}
		if (options.enablePluginDebug) {
			initVars.push('debug=true');
		}

		switch (pluginType) {
			case 'silverlight':
				container.innerHTML =
'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' +
'<param name="initParams" value="' + initVars.join(',') + '" />' +
'<param name="windowless" value="true" />' +
'<param name="background" value="black" />' +
'<param name="minRuntimeVersion" value="3.0.0.0" />' +
'<param name="autoUpgrade" value="true" />' +
'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
'</object>';
					break;

			case 'flash':

				if (mejs.MediaFeatures.isIE) {
					container.outerHTML =
'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
'id="' + pluginid + '" width="' + width + '" height="' + height + '">' +
'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
'<param name="flashvars" value="' + initVars.join('&') + '" />' +
'<param name="quality" value="high" />' +
'<param name="bgcolor" value="#000000" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowScriptAccess" value="sameDomain" />' +
'<param name="allowFullScreen" value="true" />' +
'</object>';

				} else {

					container.innerHTML =
'<embed id="' + pluginid + '" name="' + pluginid + '" ' +
'play="true" ' +
'loop="false" ' +
'quality="high" ' +
'bgcolor="#000000" ' +
'wmode="transparent" ' +
'allowScriptAccess="sameDomain" ' +
'allowFullScreen="true" ' +
'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
'src="' + options.pluginPath + options.flashName + '?' + initVars.join('&') + '" ' +
'width="' + width + '" ' +
'height="' + height + '"></embed>';
				}
				break;
		}
		// hide original element
		htmlMediaElement.style.display = 'none';

		// FYI: options.success will be fired by the MediaPluginBridge
	},

	updateNative: function(htmlMediaElement, options, preload, playback) {

		// add methods to video object to bring it into parity with Flash Object
		for (var m in mejs.HtmlMediaElement) {
			htmlMediaElement[m] = mejs.HtmlMediaElement[m];
		}

		// special case to enforce preload attribute (Chrome doesn't respect this)
		if (preload == 'none' && mejs.MediaFeatures.isChrome) {
			// forces the browser to stop loading

			htmlMediaElement.src = '';
			htmlMediaElement.load();
			htmlMediaElement.canceledPreload = true;

			htmlMediaElement.addEventListener('play',function() {
				if (htmlMediaElement.canceledPreload) {
					htmlMediaElement.src = playback.url;
					htmlMediaElement.load();
					htmlMediaElement.play();
					htmlMediaElement.canceledPreload = false;
				}
			}, false);
		}

		// fire success code
		options.success(htmlMediaElement, htmlMediaElement);
	}
};

window.mejs = mejs;
window.MediaElement = mejs.MediaElement;<|endoftext|>


<commit_before>/*======================================================
************   Navbars && Toolbars   ************
======================================================*/
// On Navbar Init Callback
app.navbarInitCallback = function (view, pageContainer, navbar, navbarInnerContainer, url, position) {
    var _navbar = {
        container: navbar,
        innerContainer: navbarInnerContainer
    };
    var _page = {
        url: url,
        query: $.parseUrlQuery(url || ''),
        container: pageContainer,
        name: $(pageContainer).attr('data-page'),
        view: view,
        from: position
    };
    var eventData = {
        navbar: _navbar,
        page: _page
    };

    // Plugin hook
    app.pluginHook('navbarInit', _navbar, _page);

    // Navbar Init Callback
    $(navbarInnerContainer).trigger('navbarInit', eventData);
};
app.sizeNavbars = function (viewContainer) {
    var navbarInner = viewContainer ? $(viewContainer).find('.navbar .navbar-inner:not(.cached)') : $('.navbar .navbar-inner:not(.cached)');
    navbarInner.each(function () {
        var tt = $(this),
            left = app.rtl ? tt.find('.right') : tt.find('.left'),
            right = app.rtl ? tt.find('.left') : tt.find('.right'),
            center = tt.find('.center'),
            noLeft = left.length === 0,
            noRight = right.length === 0,
            leftWidth = noLeft ? 0 : left.outerWidth(true),
            rightWidth = noRight ? 0 : right.outerWidth(true),
            centerWidth = center.outerWidth(true),
            navbarWidth = tt.width(),
            onLeft = tt.hasClass('navbar-on-left'),
            currLeft, diff;

        if (noRight) {
            currLeft = navbarWidth - centerWidth;
        }
        if (noLeft) {
            currLeft = 0;
        }
        if (!noLeft && !noRight) {
            currLeft = (navbarWidth - rightWidth - centerWidth + leftWidth) / 2;
        }
        var requiredLeft = (navbarWidth - centerWidth) / 2;
        if (navbarWidth - leftWidth - rightWidth > centerWidth) {
            if (requiredLeft < leftWidth) {
                requiredLeft = leftWidth;
            }
            if (requiredLeft + centerWidth > navbarWidth - rightWidth) {
                requiredLeft = navbarWidth - rightWidth - centerWidth;
            }
            diff = requiredLeft - currLeft;
        }
        else {
            diff = 0;
        }
        // RTL inverter
        var inverter = app.rtl ? -1 : 1;

        // Center left
        var centerLeft = diff;
        if (app.rtl && noLeft && noRight && center.length > 0) centerLeft = -centerLeft;
        center.css({left: centerLeft + 'px'});

        if (center.hasClass('sliding')) {
            center[0].f7NavbarLeftOffset = -(currLeft + diff) * inverter;
            center[0].f7NavbarRightOffset = (navbarWidth - currLeft - diff - centerWidth) * inverter;
            if (onLeft) center.transform('translate3d(' + center[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }
        if (!noLeft && left.hasClass('sliding')) {
            if (app.rtl) {
                left[0].f7NavbarLeftOffset = -(navbarWidth - left.outerWidth()) / 2 * inverter;
                left[0].f7NavbarRightOffset = leftWidth * inverter;
            }
            else {
                left[0].f7NavbarLeftOffset = -leftWidth;
                left[0].f7NavbarRightOffset = (navbarWidth - left.outerWidth()) / 2;
            }
            if (onLeft) left.transform('translate3d(' + left[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }
        if (!noRight && right.hasClass('sliding')) {
            if (app.rtl) {
                right[0].f7NavbarLeftOffset = -rightWidth * inverter;
                right[0].f7NavbarRightOffset = (navbarWidth - right.outerWidth()) / 2 * inverter;
            }
            else {
                right[0].f7NavbarLeftOffset = -(navbarWidth - right.outerWidth()) / 2;
                right[0].f7NavbarRightOffset = rightWidth;
            }
            if (onLeft) right.transform('translate3d(' + right[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }

    });
};
app.hideNavbar = function (viewContainer) {
    $(viewContainer).addClass('hidden-navbar');
    return true;
};
app.showNavbar = function (viewContainer) {
    var vc = $(viewContainer);
    vc.addClass('hiding-navbar').removeClass('hidden-navbar').find('.navbar').transitionEnd(function () {
        vc.removeClass('hiding-navbar');
    });
    return true;
};
app.hideToolbar = function (viewContainer) {
    $(viewContainer).addClass('hidden-toolbar');
    return true;
};
app.showToolbar = function (viewContainer) {
    var vc = $(viewContainer);
    vc.addClass('hiding-toolbar').removeClass('hidden-toolbar').find('.toolbar').transitionEnd(function () {
        vc.removeClass('hiding-toolbar');
    });
};
<commit_msg>Don't size cached navbar<commit_after>/*======================================================
************   Navbars && Toolbars   ************
======================================================*/
// On Navbar Init Callback
app.navbarInitCallback = function (view, pageContainer, navbar, navbarInnerContainer, url, position) {
    var _navbar = {
        container: navbar,
        innerContainer: navbarInnerContainer
    };
    var _page = {
        url: url,
        query: $.parseUrlQuery(url || ''),
        container: pageContainer,
        name: $(pageContainer).attr('data-page'),
        view: view,
        from: position
    };
    var eventData = {
        navbar: _navbar,
        page: _page
    };

    // Plugin hook
    app.pluginHook('navbarInit', _navbar, _page);

    // Navbar Init Callback
    $(navbarInnerContainer).trigger('navbarInit', eventData);
};
app.sizeNavbars = function (viewContainer) {
    var navbarInner = viewContainer ? $(viewContainer).find('.navbar .navbar-inner:not(.cached)') : $('.navbar .navbar-inner:not(.cached)');
    navbarInner.each(function () {
        var n = $(this);
        if (n.hasClass('cached')) return;
        var left = app.rtl ? n.find('.right') : n.find('.left'),
            right = app.rtl ? n.find('.left') : n.find('.right'),
            center = n.find('.center'),
            noLeft = left.length === 0,
            noRight = right.length === 0,
            leftWidth = noLeft ? 0 : left.outerWidth(true),
            rightWidth = noRight ? 0 : right.outerWidth(true),
            centerWidth = center.outerWidth(true),
            navbarWidth = n.width(),
            onLeft = n.hasClass('navbar-on-left'),
            currLeft, diff;

        if (noRight) {
            currLeft = navbarWidth - centerWidth;
        }
        if (noLeft) {
            currLeft = 0;
        }
        if (!noLeft && !noRight) {
            currLeft = (navbarWidth - rightWidth - centerWidth + leftWidth) / 2;
        }
        var requiredLeft = (navbarWidth - centerWidth) / 2;
        if (navbarWidth - leftWidth - rightWidth > centerWidth) {
            if (requiredLeft < leftWidth) {
                requiredLeft = leftWidth;
            }
            if (requiredLeft + centerWidth > navbarWidth - rightWidth) {
                requiredLeft = navbarWidth - rightWidth - centerWidth;
            }
            diff = requiredLeft - currLeft;
        }
        else {
            diff = 0;
        }
        // RTL inverter
        var inverter = app.rtl ? -1 : 1;

        // Center left
        var centerLeft = diff;
        if (app.rtl && noLeft && noRight && center.length > 0) centerLeft = -centerLeft;
        center.css({left: centerLeft + 'px'});

        if (center.hasClass('sliding')) {
            center[0].f7NavbarLeftOffset = -(currLeft + diff) * inverter;
            center[0].f7NavbarRightOffset = (navbarWidth - currLeft - diff - centerWidth) * inverter;
            if (onLeft) center.transform('translate3d(' + center[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }
        if (!noLeft && left.hasClass('sliding')) {
            if (app.rtl) {
                left[0].f7NavbarLeftOffset = -(navbarWidth - left.outerWidth()) / 2 * inverter;
                left[0].f7NavbarRightOffset = leftWidth * inverter;
            }
            else {
                left[0].f7NavbarLeftOffset = -leftWidth;
                left[0].f7NavbarRightOffset = (navbarWidth - left.outerWidth()) / 2;
            }
            if (onLeft) left.transform('translate3d(' + left[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }
        if (!noRight && right.hasClass('sliding')) {
            if (app.rtl) {
                right[0].f7NavbarLeftOffset = -rightWidth * inverter;
                right[0].f7NavbarRightOffset = (navbarWidth - right.outerWidth()) / 2 * inverter;
            }
            else {
                right[0].f7NavbarLeftOffset = -(navbarWidth - right.outerWidth()) / 2;
                right[0].f7NavbarRightOffset = rightWidth;
            }
            if (onLeft) right.transform('translate3d(' + right[0].f7NavbarLeftOffset + 'px, 0, 0)');
        }

    });
};
app.hideNavbar = function (viewContainer) {
    $(viewContainer).addClass('hidden-navbar');
    return true;
};
app.showNavbar = function (viewContainer) {
    var vc = $(viewContainer);
    vc.addClass('hiding-navbar').removeClass('hidden-navbar').find('.navbar').transitionEnd(function () {
        vc.removeClass('hiding-navbar');
    });
    return true;
};
app.hideToolbar = function (viewContainer) {
    $(viewContainer).addClass('hidden-toolbar');
    return true;
};
app.showToolbar = function (viewContainer) {
    var vc = $(viewContainer);
    vc.addClass('hiding-toolbar').removeClass('hidden-toolbar').find('.toolbar').transitionEnd(function () {
        vc.removeClass('hiding-toolbar');
    });
};
<|endoftext|>


<commit_before>'use strict';

const utils = require('./utils');

const Reporting = require('./reporting');

function playlistKey(userId, playlistLid) {
  return JSON.stringify(['playlist', userId, playlistLid]);
}

function isPlaylistKey(key) {
  return key.startsWith('["playlist');
}

function migrateToOne(items) {
  // Prep for multiple sorts by combining
  // sortBy/sortByOrder into an array of objects.

  /* eslint-disable no-param-reassign */
  for (const key in items) {
    if (isPlaylistKey(key)) {
      const sortBy = items[key].sortBy;
      const sortByOrder = items[key].sortByOrder;

      delete items[key].sortBy;
      delete items[key].sortByOrder;

      items[key].sorts = [{sortBy, sortByOrder}];
    }
  }
  /* eslint-enable no-param-reassign */

  return items;
}

function migrateToTwo(items) {
  // Enable new sync.

  /* eslint-disable no-param-reassign */
  items.newSyncEnabled = true;
  /* eslint-enable no-param-reassign */
  return items;
}

const SCHEMA_VERSION = 2;
const MIGRATIONS = [
  // Migrations receive all items and transform them.
  migrateToOne,
  migrateToTwo,
];

// Callback a bool.
// Note that this is in local storage, since it's per machine.
exports.getShouldNotWelcome = function getShouldNotWelcome(callback) {
  chrome.storage.local.get('shouldNotWelcome', utils.unlessError(items => {
    callback(Boolean(items.shouldNotWelcome));
  }));
};

// shouldNotWelcome is a bool.
exports.setShouldNotWelcome = function setShouldNotWelcome(shouldNotWelcome, callback) {
  const storageItems = {};
  storageItems.shouldNotWelcome = shouldNotWelcome;

  chrome.storage.local.set(storageItems, utils.unlessError(callback));
};

// Callback a bool.
exports.getNewSyncEnabled = function getNewSyncEnabled(callback) {
  chrome.storage.sync.get('newSyncEnabled', utils.unlessError(items => {
    callback(Boolean(items.newSyncEnabled));
  }));
};

// newSyncEnabled is a bool.
exports.setNewSyncEnabled = function setNewSyncEnabled(newSyncEnabled, callback) {
  const storageItems = {};
  storageItems.newSyncEnabled = newSyncEnabled;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

// Callback an int.
exports.getSyncMs = function getSyncMs(callback) {
  chrome.storage.sync.get('syncMs', utils.unlessError(items => {
    let syncMs = items.syncMs;

    if (!Number.isInteger(syncMs)) {
      syncMs = 60 * 1000;
      chrome.storage.sync.set({syncMs}, utils.unlessError(() => {
        callback(syncMs);
      }));
    } else {
      callback(syncMs);
    }
  }));
};

// syncMs is an int of milliseconds.
exports.setSyncMs = function setSyncMs(syncMs, callback) {
  const storageItems = {};
  storageItems.syncMs = syncMs;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.addSyncMsChangeListener = function addSyncMsChangeListener(callback) {
  chrome.storage.onChanged.addListener((changes, namespace) => {
    if (namespace === 'sync') {
      for (const key in changes) {
        if (key === 'syncMs') {
          callback(changes[key]);
        }
      }
    }
  });
};

// Callback a ms timestamp.
exports.getLastPSync = function getLastPSync(callback) {
  chrome.storage.sync.get('lastPSync', utils.unlessError(items => {
    let lastPSync = items.lastPSync;

    if (!Number.isInteger(lastPSync)) {
      lastPSync = 0;
      chrome.storage.sync.set({lastPSync}, utils.unlessError(() => {
        console.info('init lastPSync to time 0');
        callback(lastPSync);
      }));
    } else {
      callback(lastPSync);
    }
  }));
};

exports.setLastPSync = function setLastPSync(lastPSync, callback) {
  const storageItems = {};
  storageItems.lastPSync = lastPSync;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.getOrCreateReportingUUID = function getOrCreateReportingUUID(callback) {
  chrome.storage.sync.get('reportingUUID', utils.unlessError(items => {
    let reportingUUID = items.reportingUUID;

    if (!reportingUUID) {
      reportingUUID = utils.uuidV1();
      chrome.storage.sync.set({reportingUUID}, utils.unlessError(() => {
        callback(reportingUUID);
      }));
    } else {
      callback(reportingUUID);
    }
  }));
};

exports.getPlaylist = function getPlaylist(userId, playlistLid, callback) {
  const key = playlistKey(userId, playlistLid);

  chrome.storage.sync.get(key, utils.unlessError(items => {
    const playlist = items[key];
    callback(playlist);
  }));
};

exports.savePlaylist = function savePlaylist(playlist, callback) {
  const storageItems = {};
  storageItems[playlistKey(playlist.userId, playlist.localId)] = playlist;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.deletePlaylist = function deletePlaylist(userId, playlistLid, callback) {
  chrome.storage.sync.remove(playlistKey(userId, playlistLid), utils.unlessError(callback));
};

exports.addPlaylistChangeListener = function addPlaylistChangeListener(callback) {
  chrome.storage.onChanged.addListener((changes, namespace) => {
    if (namespace === 'sync') {
      for (const key in changes) {
        if (isPlaylistKey(key)) {
          callback(changes[key]);
        }
      }
    }
  });
};

exports.getPlaylistsForUser = function getPlaylistsForUser(userId, callback) {
  chrome.storage.sync.get(null, utils.unlessError(items => {
    const playlists = [];
    for (const key in items) {
      try {
        const parsedKey = JSON.parse(key);
        if (parsedKey[0] === 'playlist' && parsedKey[1] === userId) {
          playlists.push(items[key]);
        }
      } catch (SyntaxError) {
        // eslint-disable-line no-empty
      }
    }

    Reporting.GATracker.set('dimension4', playlists.length);

    callback(playlists);
  }));
};

exports.importPlaylistsForUser = function importPlaylistsForUser(userId, playlists, callback) {
  exports.getPlaylistsForUser(userId, currentPlaylists => {
    // FIXME we should wait on all callbacks before calling back.
    for (let i = 0; i < currentPlaylists.length; i++) {
      exports.deletePlaylist(userId, currentPlaylists[i].localId, () => null);
    }

    for (let i = 0; i < playlists.length; i++) {
      console.log('saving', playlists[i]);
      if (i === playlists.length - 1) {
        exports.savePlaylist(playlists[i], callback);
      } else {
        exports.savePlaylist(playlists[i], () => null);
      }
    }
  });
};

exports.handleMigrations = function handleMigrations(callback) {
  chrome.storage.sync.get(null, utils.unlessError(items => {
    /* eslint-disable no-param-reassign */
    if (!('schemaVersion' in items)) {
      items.schemaVersion = 0;
    }

    if (items.schemaVersion === SCHEMA_VERSION) {
      return callback();
    }

    for (let version = items.schemaVersion; version < MIGRATIONS.length; version++) {
      items = MIGRATIONS[version](items);
    }

    console.info('migrating from', items.schemaVersion, 'to', SCHEMA_VERSION);
    items.schemaVersion = SCHEMA_VERSION;
    /* eslint-disable no-param-reassign */

    console.info('migrated items:', items);
    chrome.storage.sync.set(items, utils.unlessError(callback));
  }));
};
<commit_msg>default sync interval to 5 minutes<commit_after>'use strict';

const utils = require('./utils');

const Reporting = require('./reporting');

function playlistKey(userId, playlistLid) {
  return JSON.stringify(['playlist', userId, playlistLid]);
}

function isPlaylistKey(key) {
  return key.startsWith('["playlist');
}

function migrateToOne(items) {
  // Prep for multiple sorts by combining
  // sortBy/sortByOrder into an array of objects.

  /* eslint-disable no-param-reassign */
  for (const key in items) {
    if (isPlaylistKey(key)) {
      const sortBy = items[key].sortBy;
      const sortByOrder = items[key].sortByOrder;

      delete items[key].sortBy;
      delete items[key].sortByOrder;

      items[key].sorts = [{sortBy, sortByOrder}];
    }
  }
  /* eslint-enable no-param-reassign */

  return items;
}

function migrateToTwo(items) {
  // Enable new sync.

  /* eslint-disable no-param-reassign */
  items.newSyncEnabled = true;
  /* eslint-enable no-param-reassign */
  return items;
}

const SCHEMA_VERSION = 2;
const MIGRATIONS = [
  // Migrations receive all items and transform them.
  migrateToOne,
  migrateToTwo,
];

// Callback a bool.
// Note that this is in local storage, since it's per machine.
exports.getShouldNotWelcome = function getShouldNotWelcome(callback) {
  chrome.storage.local.get('shouldNotWelcome', utils.unlessError(items => {
    callback(Boolean(items.shouldNotWelcome));
  }));
};

// shouldNotWelcome is a bool.
exports.setShouldNotWelcome = function setShouldNotWelcome(shouldNotWelcome, callback) {
  const storageItems = {};
  storageItems.shouldNotWelcome = shouldNotWelcome;

  chrome.storage.local.set(storageItems, utils.unlessError(callback));
};

// Callback a bool.
exports.getNewSyncEnabled = function getNewSyncEnabled(callback) {
  chrome.storage.sync.get('newSyncEnabled', utils.unlessError(items => {
    callback(Boolean(items.newSyncEnabled));
  }));
};

// newSyncEnabled is a bool.
exports.setNewSyncEnabled = function setNewSyncEnabled(newSyncEnabled, callback) {
  const storageItems = {};
  storageItems.newSyncEnabled = newSyncEnabled;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

// Callback an int.
exports.getSyncMs = function getSyncMs(callback) {
  chrome.storage.sync.get('syncMs', utils.unlessError(items => {
    let syncMs = items.syncMs;

    if (!Number.isInteger(syncMs)) {
      syncMs = 60 * 1000 * 5;
      chrome.storage.sync.set({syncMs}, utils.unlessError(() => {
        callback(syncMs);
      }));
    } else {
      callback(syncMs);
    }
  }));
};

// syncMs is an int of milliseconds.
exports.setSyncMs = function setSyncMs(syncMs, callback) {
  const storageItems = {};
  storageItems.syncMs = syncMs;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.addSyncMsChangeListener = function addSyncMsChangeListener(callback) {
  chrome.storage.onChanged.addListener((changes, namespace) => {
    if (namespace === 'sync') {
      for (const key in changes) {
        if (key === 'syncMs') {
          callback(changes[key]);
        }
      }
    }
  });
};

// Callback a ms timestamp.
exports.getLastPSync = function getLastPSync(callback) {
  chrome.storage.sync.get('lastPSync', utils.unlessError(items => {
    let lastPSync = items.lastPSync;

    if (!Number.isInteger(lastPSync)) {
      lastPSync = 0;
      chrome.storage.sync.set({lastPSync}, utils.unlessError(() => {
        console.info('init lastPSync to time 0');
        callback(lastPSync);
      }));
    } else {
      callback(lastPSync);
    }
  }));
};

exports.setLastPSync = function setLastPSync(lastPSync, callback) {
  const storageItems = {};
  storageItems.lastPSync = lastPSync;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.getOrCreateReportingUUID = function getOrCreateReportingUUID(callback) {
  chrome.storage.sync.get('reportingUUID', utils.unlessError(items => {
    let reportingUUID = items.reportingUUID;

    if (!reportingUUID) {
      reportingUUID = utils.uuidV1();
      chrome.storage.sync.set({reportingUUID}, utils.unlessError(() => {
        callback(reportingUUID);
      }));
    } else {
      callback(reportingUUID);
    }
  }));
};

exports.getPlaylist = function getPlaylist(userId, playlistLid, callback) {
  const key = playlistKey(userId, playlistLid);

  chrome.storage.sync.get(key, utils.unlessError(items => {
    const playlist = items[key];
    callback(playlist);
  }));
};

exports.savePlaylist = function savePlaylist(playlist, callback) {
  const storageItems = {};
  storageItems[playlistKey(playlist.userId, playlist.localId)] = playlist;

  chrome.storage.sync.set(storageItems, utils.unlessError(callback));
};

exports.deletePlaylist = function deletePlaylist(userId, playlistLid, callback) {
  chrome.storage.sync.remove(playlistKey(userId, playlistLid), utils.unlessError(callback));
};

exports.addPlaylistChangeListener = function addPlaylistChangeListener(callback) {
  chrome.storage.onChanged.addListener((changes, namespace) => {
    if (namespace === 'sync') {
      for (const key in changes) {
        if (isPlaylistKey(key)) {
          callback(changes[key]);
        }
      }
    }
  });
};

exports.getPlaylistsForUser = function getPlaylistsForUser(userId, callback) {
  chrome.storage.sync.get(null, utils.unlessError(items => {
    const playlists = [];
    for (const key in items) {
      try {
        const parsedKey = JSON.parse(key);
        if (parsedKey[0] === 'playlist' && parsedKey[1] === userId) {
          playlists.push(items[key]);
        }
      } catch (SyntaxError) {
        // eslint-disable-line no-empty
      }
    }

    Reporting.GATracker.set('dimension4', playlists.length);

    callback(playlists);
  }));
};

exports.importPlaylistsForUser = function importPlaylistsForUser(userId, playlists, callback) {
  exports.getPlaylistsForUser(userId, currentPlaylists => {
    // FIXME we should wait on all callbacks before calling back.
    for (let i = 0; i < currentPlaylists.length; i++) {
      exports.deletePlaylist(userId, currentPlaylists[i].localId, () => null);
    }

    for (let i = 0; i < playlists.length; i++) {
      console.log('saving', playlists[i]);
      if (i === playlists.length - 1) {
        exports.savePlaylist(playlists[i], callback);
      } else {
        exports.savePlaylist(playlists[i], () => null);
      }
    }
  });
};

exports.handleMigrations = function handleMigrations(callback) {
  chrome.storage.sync.get(null, utils.unlessError(items => {
    /* eslint-disable no-param-reassign */
    if (!('schemaVersion' in items)) {
      items.schemaVersion = 0;
    }

    if (items.schemaVersion === SCHEMA_VERSION) {
      return callback();
    }

    for (let version = items.schemaVersion; version < MIGRATIONS.length; version++) {
      items = MIGRATIONS[version](items);
    }

    console.info('migrating from', items.schemaVersion, 'to', SCHEMA_VERSION);
    items.schemaVersion = SCHEMA_VERSION;
    /* eslint-disable no-param-reassign */

    console.info('migrated items:', items);
    chrome.storage.sync.set(items, utils.unlessError(callback));
  }));
};
<|endoftext|>


<commit_before>/* ========================================================================
 * ZUI: treemap.js
 * http://zui.sexy
 * ========================================================================
 * Copyright (c) 2014-2016 cnezsoft.com; Licensed MIT
 * ======================================================================== */

// Tree map data format
// {
//      text: main text,
//      html: main text as html format
//      style: node style,
//      textColor: text color,
//      color: background color
//      border: border style,
//      cableWidth: 2,
//      cableColor: '#808080'
//      cableStyle: 'solid'
// }

(function($, window, document, Math, undefined) {
    'use strict';

    var NAME = 'zui.treemap',
        DEFAULTS = {
            data: [],
            // direction: 'bottom', // or 'top', 'left', 'right'
            cableWidth: 1,
            cableColor: '#808080',
            cableStyle: 'solid',
            rowSpace: 30,
            nodeSpace: 20,
            listenNodeResize: true,
            nodeTemplate: '

',
            foldable: true,
            clickNodeToFold: true,
            // sort: false, // Boolean or function
            // tooltip: null,
            // nodeStyle: null,
        };
        // var  DEFAULT_NODE = {
            // id: uuid(),           // uuid
            // text: '',             // main text,
            // html: '',             // main text as html format
            // style: null,          // node element style
            // textColor: '',        // text color
            // color: '',            // background color
            // border: '',           // border style,
            // tooltip: ''           // node caption
            // attrs: null           // attrs
            // title: ''             // node title
            // tooltip: ''           // node tooltip
        // };

    var getDataFromUlList = function($list) {
        return $list.children('li,.treemap-data-item').map(function() {
            var $item = $(this),
                item = $item.data(),
                $text = $item.children('.text'),
                $html = $item.children('.content'),
                $children = $item.children('ul,.treemap-data-list');
            if($text.length) item.text = $text.text();
            if($html.length) item.html = $html.html();
            if($children.length) {
                item.children = getDataFromUlList($children);
            }
            if(!item.text && !item.html) {
                var $content = $item.children(':not(ul,.treemap-data-list)');
                var $itemClone = $item.clone();
                $itemClone.find('ul,.treemap-data-list').remove();
                if(!$content.length) {
                    item.text = $itemClone.text();
                } else {
                    item.html = $itemClone.html();
                }
            }
            return item;
        }).get();
    };

    var Treemap = function(element, options) {
        var $element = $(element);
        if($.isArray(options)) {
            options = {data: options};
        }
        options = $.extend({}, DEFAULTS, $element.data(), options);

        var data = options.data || [];
        if(!data.length) {
            var $dataList = $element.children('.treemap-data');
            if($dataList.length) {
                data = getDataFromUlList($dataList.hide());
            }
        }

        var $nodes = $element.children('.treemap-nodes');
        if(!$nodes.length) {
            $nodes = $('
').appendTo($element);
        }

        var that     = this;
        that.$       = $element;
        that.$nodes  = $nodes;
        that.data    = $.isArray(data) ? data : [data];
        that.options = options;
        that.offsetX = 0;
        that.offsetY = 0;
        that.scale   = options.scale || 1;

        // Bind events

        that.render();

        $nodes.on('resize', '.treemap-node-wrapper', function() {
            that.delayDrawLines();
        });
        if(options.foldable) {
            $nodes.on('click', options.clickNodeToFold ? '.treemap-node-wrapper' : '.treemap-node-fold-icon', function() {
                that.toggle($(this).closest('.treemap-node'));
            });
        }

        $nodes.on('click', '.treemap-node-wrapper', function() {
            var $node = $(this).closest('.treemap-node');
            that.callEvent('onNodeClick', $node.data('node'));
        });
    };

    Treemap.prototype.toggle = function($node, toggle) {
        var that = this;
        if(typeof $node === 'boolean') {
            toggle = $node;
            $node = null;
        }
        if(!$node) {
            $node = that.$nodes.children('.treemap-node').first();
        }
        if(toggle === undefined) {
            toggle = !$node.hasClass('collapsed');
        }
        $node.addClass('tree-node-collapsing').toggleClass('collapsed', toggle).find('[data-toggle="tooltip"]').tooltip('hide');
        that.$nodes.find('.tooltip').remove();
        that.drawLines();
        clearTimeout(that.toggleTimeTask);
        that.toggleTimeTask = setTimeout(function() {
            $node.removeClass('tree-node-collapsing');
        }, 200);
    };

    Treemap.prototype.render = function(data) {
        var that       = this;
        that.data = data || that.data;

        if(that.data) {
            that.createNodes();
            that.drawLines();
        }

        that.callEvent('afterRender');
    };

    Treemap.prototype.createNodes = function(nodes, parent, callback) {
        var that       = this,
            options    = that.options,
            rowSpace   = options.rowSpace,
            $nodes     = that.$nodes;
        if(!parent) {
            $nodes.find('.treemap-node-wrapper').off('resize.' + NAME);
            $nodes.empty();
        }
        if(options.sort) {
            nodes.sort($.isFunction(options.sort) ? options.sort : function(nodeX, nodeY) {
                return (nodeX.order || 0) - (nodeY.order);
            });
        }
        var lastNode = null;
        nodes = nodes || that.data;
        $.each(nodes || that.data, function(idx, node) {
            if(typeof node === 'string') {
                node = {html: node};
                nodes[idx] = node;
            }

            if(!node.id) node.id = $.zui.uuid();

            // Create node element
            var isCustomNodeTemplate = $.isFunction(options.nodeTemplate);
            var $node = isCustomNodeTemplate ? options.nodeTemplate(node, that) : $(options.nodeTemplate);

            // Create node wrapper element
            var $wrapper = $node.find('.treemap-node-wrapper');
            if(!$wrapper.length) {
                $wrapper = $('
').appendTo($node);
            }

            var children = node.children;
            var hasChild = children && children.length;
            node.isOnlyOneChild = hasChild === 1;

            // Set node data attributes
            node.idx    = idx;
            var row = parent ? (parent.row + 1) : 0;
            $node.toggleClass('treemap-node-has-child', !!hasChild)
                 .toggleClass('treemap-node-has-parent', !!parent)
                 .toggleClass('treemap-node-one-child', hasChild === 1)
                 .toggleClass('collapsed', !!node.collapsed && node.collapsed !== 'false')
                 .toggleClass('treemap-node-root', !row)
                 .attr('data-id', node.id).data('node', node);
            if(node.className) {
                $node.addClass(node.className);
            }
            node.row = row;

            // Set node element attributes and sytle
            var style = $.extend({}, options.nodeStyle, node.style);
            if(node.textColor) style.color = node.textColor;
            if(node.color) style.backgroundColor = node.color;
            if(node.border) style.border = node.border;
            var attrs = $.extend({}, node.attrs, {
                title: node.caption
            });
            if(node.tooltip) {
                attrs['data-toggle'] = 'tooltip';
                attrs.title = node.tooltip;
            }
            $wrapper.attr(attrs).css(style);
            if(lastNode) {
                $node.css('padding-left', options.nodeSpace);
            }
            if(!isCustomNodeTemplate) {
                if(node.html) $wrapper.append(node.html);
                else if(node.text) $wrapper.text(node.text);
            }

            // append node element to ducument
            $node.appendTo(parent ? parent.$children : $nodes);

            // Save sizes
            node.bounds = {
                width  : $wrapper.outerWidth(),
                height : $wrapper.outerHeight()
            };

            if(lastNode) {
                lastNode.next = node;
            }
            node.prev     = lastNode;
            node.parent   = parent;
            node.$        = $node;
            node.$wrapper = $wrapper;

            // Create children
            if(hasChild) {
                var $children = $node.find('.treemap-node-children');
                if(!$children.length) {
                    $children = $('
').appendTo($node);
                }
                $children.css('margin-top', rowSpace);
                node.$children = $children;
                that.createNodes(children, node);
            }

            if(options.listenNodeResize) {
                $wrapper.on('resize.' + NAME, function() {
                    node.bounds.width = $wrapper.outerWidth();
                    node.bounds.height = $wrapper.outerHeight();
                    that.delayDrawLines();
                });
            }

            lastNode    = node;
            callback && callback($node, node);
        });

        if(!parent) {
            // Init tooltip
            $nodes.find('[data-toggle="tooltip"]').tooltip(options.tooltip);
        }
    };

    Treemap.prototype.delayDrawLines = function() {
        var that = this;
        clearTimeout(that.delayDrawLinesTask);
        that.delayDrawLinesTask = setTimeout(function() {
            that.drawLines();
        }, 10);
    };

    Treemap.prototype.drawLines = function(nodes, parent) {
        var that       = this,
            options    = that.options,
            rowSpace   = options.rowSpace;
        var cableStyle = {};
        if(options.cableWidth) cableStyle.borderWidth = options.cableWidth;
        if(options.cableStyle) cableStyle.borderStyle = options.cableStyle;
        if(options.cableColor) cableStyle.borderColor = options.cableColor;
        var rowSpaceHalf = Math.round(rowSpace/2);
        var nodesOffsetLeft = that.$nodes.offset().left;
        $.each(nodes || that.data, function(idx, node) {
            var $wrapper = node.$wrapper;
            var children = node.children;
            var nodeCableStyle = $.extend({
                height: rowSpaceHalf,
                top: -rowSpaceHalf - 1,
                left: Math.round(($wrapper.outerWidth() - cableStyle.borderWidth)/2)
            }, cableStyle);
            if(parent && !parent.isOnlyOneChild) {
                var $topLine = $wrapper.find('.treemap-line-top');
                if(!$topLine.length) {
                    $topLine = $('
').appendTo($wrapper);
                }
                $topLine.css(nodeCableStyle);
            }
            if(children && children.length) {
                nodeCableStyle.top = $wrapper.outerHeight() - 1;
                if(node.isOnlyOneChild) {
                    nodeCableStyle.height = rowSpace;
                }
                var $bottomLine = $wrapper.find('.treemap-line-bottom');
                if(!$bottomLine.length) {
                    $bottomLine = $('
').appendTo($wrapper);
                    if(options.foldable) {
                        $bottomLine.append('');
                    }
                }
                $bottomLine.css(nodeCableStyle);
                that.drawLines(children, node);
                if(children.length > 1) {
                    var firstChild = children[0],
                        lastChild = children[children.length - 1];

                    var $centerLine = node.$.children('.treemap-line');
                    if(!$centerLine.length) {
                        $centerLine = $('
').insertAfter($wrapper);
                    }
                    var lineLeft = Math.round(firstChild.$wrapper.offset().left - nodesOffsetLeft + firstChild.bounds.width/2);
                    $centerLine.css($.extend({
                        marginTop: rowSpaceHalf,
                        left: lineLeft,
                        width: lastChild.$wrapper.offset().left - nodesOffsetLeft -lineLeft + lastChild.bounds.width/2
                    }, cableStyle));
                }
            }
        });

        if(!parent) {
            that.callEvent('afterDrawLines');
        }
    };

    // Call event
    Treemap.prototype.callEvent = function(name, params) {
        var that = this;
        if(!$.isArray(params)) params = [params];
        that.$.trigger(name, params);
        if($.isFunction(that.options[name])) {
            return that.options[name].apply(that, params);
        }
    };

    Treemap.DEFAULTS = DEFAULTS;
    Treemap.NAME     = NAME;

    $.fn.treemap = function(option, param1, parma2) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data(NAME);
            var options = typeof option == 'object' && option;

            if(!data) $this.data(NAME, (data = new Treemap(this, options)));

            if(typeof option == 'string') data[option](param1, parma2);
        });
    };

    $.fn.treemap.Constructor = Treemap;
}(jQuery, window, document, Math, undefined));

<commit_msg> * fix treemap lines broken sometimes.<commit_after>/* ========================================================================
 * ZUI: treemap.js
 * http://zui.sexy
 * ========================================================================
 * Copyright (c) 2014-2016 cnezsoft.com; Licensed MIT
 * ======================================================================== */

// Tree map data format
// {
//      text: main text,
//      html: main text as html format
//      style: node style,
//      textColor: text color,
//      color: background color
//      border: border style,
//      cableWidth: 2,
//      cableColor: '#808080'
//      cableStyle: 'solid'
// }

(function($, window, document, Math, undefined) {
    'use strict';

    var NAME = 'zui.treemap',
        DEFAULTS = {
            data: [],
            // direction: 'bottom', // or 'top', 'left', 'right'
            cableWidth: 1,
            cableColor: '#808080',
            cableStyle: 'solid',
            rowSpace: 30,
            nodeSpace: 20,
            listenNodeResize: true,
            nodeTemplate: '

',
            foldable: true,
            clickNodeToFold: true,
            // sort: false, // Boolean or function
            // tooltip: null,
            // nodeStyle: null,
        };
        // var  DEFAULT_NODE = {
            // id: uuid(),           // uuid
            // text: '',             // main text,
            // html: '',             // main text as html format
            // style: null,          // node element style
            // textColor: '',        // text color
            // color: '',            // background color
            // border: '',           // border style,
            // tooltip: ''           // node caption
            // attrs: null           // attrs
            // title: ''             // node title
            // tooltip: ''           // node tooltip
        // };

    var getDataFromUlList = function($list) {
        return $list.children('li,.treemap-data-item').map(function() {
            var $item = $(this),
                item = $item.data(),
                $text = $item.children('.text'),
                $html = $item.children('.content'),
                $children = $item.children('ul,.treemap-data-list');
            if($text.length) item.text = $text.text();
            if($html.length) item.html = $html.html();
            if($children.length) {
                item.children = getDataFromUlList($children);
            }
            if(!item.text && !item.html) {
                var $content = $item.children(':not(ul,.treemap-data-list)');
                var $itemClone = $item.clone();
                $itemClone.find('ul,.treemap-data-list').remove();
                if(!$content.length) {
                    item.text = $itemClone.text();
                } else {
                    item.html = $itemClone.html();
                }
            }
            return item;
        }).get();
    };

    var Treemap = function(element, options) {
        var $element = $(element);
        if($.isArray(options)) {
            options = {data: options};
        }
        options = $.extend({}, DEFAULTS, $element.data(), options);

        var data = options.data || [];
        if(!data.length) {
            var $dataList = $element.children('.treemap-data');
            if($dataList.length) {
                data = getDataFromUlList($dataList.hide());
            }
        }

        var $nodes = $element.children('.treemap-nodes');
        if(!$nodes.length) {
            $nodes = $('
').appendTo($element);
        }

        var that     = this;
        that.$       = $element;
        that.$nodes  = $nodes;
        that.data    = $.isArray(data) ? data : [data];
        that.options = options;
        that.offsetX = 0;
        that.offsetY = 0;
        that.scale   = options.scale || 1;

        // Bind events

        that.render();

        $nodes.on('resize', '.treemap-node-wrapper', function() {
            that.delayDrawLines();
        });
        if(options.foldable) {
            $nodes.on('click', options.clickNodeToFold ? '.treemap-node-wrapper' : '.treemap-node-fold-icon', function() {
                that.toggle($(this).closest('.treemap-node'));
            });
        }

        $nodes.on('click', '.treemap-node-wrapper', function() {
            var $node = $(this).closest('.treemap-node');
            that.callEvent('onNodeClick', $node.data('node'));
        });
    };

    Treemap.prototype.toggle = function($node, toggle) {
        var that = this;
        if(typeof $node === 'boolean') {
            toggle = $node;
            $node = null;
        }
        if(!$node) {
            $node = that.$nodes.children('.treemap-node').first();
        }
        if(toggle === undefined) {
            toggle = !$node.hasClass('collapsed');
        }
        $node.addClass('tree-node-collapsing').toggleClass('collapsed', toggle).find('[data-toggle="tooltip"]').tooltip('hide');
        that.$nodes.find('.tooltip').remove();
        that.drawLines();
        clearTimeout(that.toggleTimeTask);
        that.toggleTimeTask = setTimeout(function() {
            $node.removeClass('tree-node-collapsing');
        }, 200);
    };

    Treemap.prototype.render = function(data) {
        var that       = this;
        that.data = data || that.data;

        if(that.data) {
            that.createNodes();
            that.drawLines();
            that.delayDrawLines(500);
        }

        that.callEvent('afterRender');
    };

    Treemap.prototype.createNodes = function(nodes, parent, callback) {
        var that       = this,
            options    = that.options,
            rowSpace   = options.rowSpace,
            $nodes     = that.$nodes;
        if(!parent) {
            $nodes.find('.treemap-node-wrapper').off('resize.' + NAME);
            $nodes.empty();
        }
        if(options.sort) {
            nodes.sort($.isFunction(options.sort) ? options.sort : function(nodeX, nodeY) {
                return (nodeX.order || 0) - (nodeY.order);
            });
        }
        var lastNode = null;
        nodes = nodes || that.data;
        $.each(nodes || that.data, function(idx, node) {
            if(typeof node === 'string') {
                node = {html: node};
                nodes[idx] = node;
            }

            if(!node.id) node.id = $.zui.uuid();

            // Create node element
            var isCustomNodeTemplate = $.isFunction(options.nodeTemplate);
            var $node = isCustomNodeTemplate ? options.nodeTemplate(node, that) : $(options.nodeTemplate);

            // Create node wrapper element
            var $wrapper = $node.find('.treemap-node-wrapper');
            if(!$wrapper.length) {
                $wrapper = $('
').appendTo($node);
            }

            var children = node.children;
            var hasChild = children && children.length;
            node.isOnlyOneChild = hasChild === 1;

            // Set node data attributes
            node.idx    = idx;
            var row = parent ? (parent.row + 1) : 0;
            $node.toggleClass('treemap-node-has-child', !!hasChild)
                 .toggleClass('treemap-node-has-parent', !!parent)
                 .toggleClass('treemap-node-one-child', hasChild === 1)
                 .toggleClass('collapsed', !!node.collapsed && node.collapsed !== 'false')
                 .toggleClass('treemap-node-root', !row)
                 .attr('data-id', node.id).data('node', node);
            if(node.className) {
                $node.addClass(node.className);
            }
            node.row = row;

            // Set node element attributes and sytle
            var style = $.extend({}, options.nodeStyle, node.style);
            if(node.textColor) style.color = node.textColor;
            if(node.color) style.backgroundColor = node.color;
            if(node.border) style.border = node.border;
            var attrs = $.extend({}, node.attrs, {
                title: node.caption
            });
            if(node.tooltip) {
                attrs['data-toggle'] = 'tooltip';
                attrs.title = node.tooltip;
            }
            $wrapper.attr(attrs).css(style);
            if(lastNode) {
                $node.css('padding-left', options.nodeSpace);
            }
            if(!isCustomNodeTemplate) {
                if(node.html) $wrapper.append(node.html);
                else if(node.text) $wrapper.text(node.text);
            }

            // append node element to ducument
            $node.appendTo(parent ? parent.$children : $nodes);

            // Save sizes
            // node.bounds = {
            //     width  : $wrapper.outerWidth(),
            //     height : $wrapper.outerHeight()
            // };

            if(lastNode) {
                lastNode.next = node;
            }
            node.prev     = lastNode;
            node.parent   = parent;
            node.$        = $node;
            node.$wrapper = $wrapper;

            // Create children
            if(hasChild) {
                var $children = $node.find('.treemap-node-children');
                if(!$children.length) {
                    $children = $('
').appendTo($node);
                }
                $children.css('margin-top', rowSpace);
                node.$children = $children;
                that.createNodes(children, node);
            }

            if(options.listenNodeResize) {
                $wrapper.on('resize.' + NAME, function() {
                    // node.bounds.width = $wrapper.outerWidth();
                    // node.bounds.height = $wrapper.outerHeight();
                    that.delayDrawLines();
                });
            }

            lastNode    = node;
            callback && callback($node, node);
        });

        if(!parent) {
            // Init tooltip
            $nodes.find('[data-toggle="tooltip"]').tooltip(options.tooltip);
        }
    };

    Treemap.prototype.delayDrawLines = function(delay) {
        var that = this;
        clearTimeout(that.delayDrawLinesTask);
        that.delayDrawLinesTask = setTimeout(function() {
            that.drawLines();
        }, delay || 10);
    };

    Treemap.prototype.drawLines = function(nodes, parent) {
        var that       = this,
            options    = that.options,
            rowSpace   = options.rowSpace;
        var cableStyle = {};
        if(options.cableWidth) cableStyle.borderWidth = options.cableWidth;
        if(options.cableStyle) cableStyle.borderStyle = options.cableStyle;
        if(options.cableColor) cableStyle.borderColor = options.cableColor;
        var rowSpaceHalf = Math.round(rowSpace/2);
        var nodesOffsetLeft = that.$nodes.offset().left;
        $.each(nodes || that.data, function(idx, node) {
            var $wrapper = node.$wrapper;
            var children = node.children;
            var nodeCableStyle = $.extend({
                height: rowSpaceHalf,
                top: -rowSpaceHalf - 1,
                left: Math.round(($wrapper.outerWidth() - cableStyle.borderWidth)/2),
                color: cableStyle.borderColor
            }, cableStyle);
            if(parent && !parent.isOnlyOneChild) {
                var $topLine = $wrapper.find('.treemap-line-top');
                if(!$topLine.length) {
                    $topLine = $('
').appendTo($wrapper);
                }
                $topLine.css(nodeCableStyle);
            }
            if(children && children.length) {
                nodeCableStyle.top = $wrapper.outerHeight() - 1;
                if(node.isOnlyOneChild) {
                    nodeCableStyle.height = rowSpace;
                }
                var $bottomLine = $wrapper.find('.treemap-line-bottom');
                if(!$bottomLine.length) {
                    $bottomLine = $('
').appendTo($wrapper);
                    if(options.foldable) {
                        $bottomLine.append('');
                    }
                }
                $bottomLine.css(nodeCableStyle);
                that.drawLines(children, node);
                if(children.length > 1) {
                    var firstChild = children[0],
                        lastChild = children[children.length - 1];

                    var $centerLine = node.$.children('.treemap-line');
                    if(!$centerLine.length) {
                        $centerLine = $('
').insertAfter($wrapper);
                    }
                    var lineLeft = Math.round(firstChild.$wrapper.offset().left - nodesOffsetLeft + firstChild.$wrapper.outerWidth()/2);
                    $centerLine.css($.extend({
                        marginTop: rowSpaceHalf,
                        left: lineLeft,
                        width: lastChild.$wrapper.offset().left - nodesOffsetLeft -lineLeft + lastChild.$wrapper.outerWidth()/2
                    }, cableStyle));
                }
            }
        });

        if(!parent) {
            that.callEvent('afterDrawLines');
        }
    };

    // Call event
    Treemap.prototype.callEvent = function(name, params) {
        var that = this;
        if(!$.isArray(params)) params = [params];
        that.$.trigger(name, params);
        if($.isFunction(that.options[name])) {
            return that.options[name].apply(that, params);
        }
    };

    Treemap.DEFAULTS = DEFAULTS;
    Treemap.NAME     = NAME;

    $.fn.treemap = function(option, param1, parma2) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data(NAME);
            var options = typeof option == 'object' && option;

            if(!data) $this.data(NAME, (data = new Treemap(this, options)));

            if(typeof option == 'string') data[option](param1, parma2);
        });
    };

    $.fn.treemap.Constructor = Treemap;
}(jQuery, window, document, Math, undefined));

<|endoftext|>


<commit_before>var util2 = require('util2');
var myutil = require('myutil');
var Emitter = require('emitter');
var WindowStack = require('ui/windowstack');
var Window = require('ui/window');
var simply = require('ui/simply');

var defaults = {
  backgroundColor: 'white',
    textColor: 'black',
    highlightBackgroundColor: 'black',
    highlightTextColor: 'white',
  fullscreen: false,
};

var Menu = function(menuDef) {
  Window.call(this, myutil.shadow(defaults, menuDef || {}));
  this._dynamic = false;
  this._sections = {};
  this._selection = { sectionIndex: 0, itemIndex: 0 };
  this._selections = [];
};

Menu._codeName = 'menu';

util2.inherit(Menu, Window);

util2.copy(Emitter.prototype, Menu.prototype);

Menu.prototype._show = function() {
  this._resolveMenu();
  Window.prototype._show.apply(this, arguments);
  var select = this._selection;
  simply.impl.menuSelection(select.sectionIndex, select.itemIndex);
};

Menu.prototype._numPreloadItems = 50;

Menu.prototype._prop = function(state, clear, pushing) {
  if (this === WindowStack.top()) {
    simply.impl.menu.call(this, state, clear, pushing);
    this._resolveSection(this._selection);
  }
};

Menu.prototype.action = function() {
  throw new Error("Menus don't support action bars.");
};

Menu.prototype.buttonConfig = function() {
  throw new Error("Menus don't support changing button configurations.");
};

Menu.prototype._buttonAutoConfig = function() {};

Menu.prototype._getMetaSection = function(sectionIndex) {
  return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} ));
};

Menu.prototype._getSections = function() {
  var sections = this.state.sections;
  if (sections instanceof Array) {
    return sections;
  }
  if (typeof sections === 'number') {
    sections = new Array(sections);
    return (this.state.sections = sections);
  }
  if (typeof sections === 'function') {
    this.sectionsProvider = this.state.sections;
    delete this.state.sections;
  }
  if (this.sectionsProvider) {
    sections = this.sectionsProvider.call(this);
    if (sections) {
      this.state.sections = sections;
      return this._getSections();
    }
  }
  return (this.state.sections = []);
};

Menu.prototype._getSection = function(e, create) {
  var sections = this._getSections();
  var section = sections[e.sectionIndex];
  if (section) {
    return section;
  }
  if (this.sectionProvider) {
    section = this.sectionProvider.call(this, e);
    if (section) {
      return (sections[e.sectionIndex] = section);
    }
  }
  if (!create) { return; }
  return (sections[e.sectionIndex] = {});
};

Menu.prototype._getItems = function(e, create) {
  var section = this._getSection(e, create);
  if (!section) {
    if (e.sectionIndex > 0) { return; }
    section = this.state.sections[0] = {};
  }
  if (section.items instanceof Array) {
    return section.items;
  }
  if (typeof section.items === 'number') {
    return (section.items = new Array(section.items));
  }
  if (typeof section.items === 'function') {
    this._sections[e.sectionIndex] = section.items;
    delete section.items;
  }
  var itemsProvider = this._getMetaSection(e.sectionIndex).items || this.itemsProvider;
  if (itemsProvider) {
    var items = itemsProvider.call(this, e);
    if (items) {
      section.items = items;
      return this._getItems(e, create);
    }
  }
  return (section.items = []);
};

Menu.prototype._getItem = function(e, create) {
  var items = this._getItems(e, create);
  var item = items[e.itemIndex];
  if (item) {
    return item;
  }
  var itemProvider = this._getMetaSection(e.sectionIndex).item || this.itemProvider;
  if (itemProvider) {
    item = itemProvider.call(this, e);
    if (item) {
      return (items[e.itemIndex] = item);
    }
  }
  if (!create) { return; }
  return (items[e.itemIndex] = {});
};

Menu.prototype._resolveMenu = function() {
  var sections = this._getSections(this);
  if (this === WindowStack.top()) {
    simply.impl.menu.call(this, this.state);
    return true;
  }
};

Menu.prototype._resolveSection = function(e, clear) {
  var section = this._getSection(e);
  if (!section) { return; }
  section.items = this._getItems(e);
  if (this === WindowStack.top()) {
    simply.impl.menuSection.call(this, e.sectionIndex, section, clear);
    var select = this._selection;
    if (select.sectionIndex === e.sectionIndex) {
      this._preloadItems(select);
    }
    return true;
  }
};

Menu.prototype._resolveItem = function(e) {
  var item = this._getItem(e);
  if (!item) { return; }
  if (this === WindowStack.top()) {
    simply.impl.menuItem.call(this, e.sectionIndex, e.itemIndex, item);
    return true;
  }
};

Menu.prototype._preloadItems = function(e) {
  var select = util2.copy(e);
  select.itemIndex = Math.max(0, select.itemIndex - Math.floor(this._numPreloadItems / 2));
  for (var i = 0; i < this._numPreloadItems; ++i) {
    this._resolveItem(select);
    select.itemIndex++;
  }
};

Menu.prototype._emitSelect = function(e) {
  this._selection = e;
  var item = this._getItem(e);
  switch (e.type) {
    case 'select':
      if (item && typeof item.select === 'function') {
        if (item.select(e) === false) {
          return false;
        }
      }
      break;
    case 'longSelect':
      if (item && typeof item.longSelect === 'function') {
        if (item.longSelect(e) === false) {
          return false;
        }
      }
      break;
    case 'selection':
      var handlers = this._selections;
      this._selections = [];
      if (item && typeof item.selected === 'function') {
        if (item.selected(e) === false) {
          return false;
        }
      }
      for (var i = 0, ii = handlers.length; i < ii; ++i) {
        if (handlers[i](e) === false) {
          break;
        }
      }
      break;
  }
};

Menu.prototype.sections = function(sections) {
  if (typeof sections === 'function') {
    delete this.state.sections;
    this.sectionsProvider = sections;
    this._resolveMenu();
    return this;
  }
  this.state.sections = sections;
  this._resolveMenu();
  return this;
};

Menu.prototype.section = function(sectionIndex, section) {
  if (typeof sectionIndex === 'object') {
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.sectionProvider = sectionIndex;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex };
  if (!section) {
    return this._getSection(menuIndex);
  }
  var sections = this._getSections();
  var prevLength = sections.length;
  sections[sectionIndex] = util2.copy(section, sections[sectionIndex]);
  if (sections.length !== prevLength) {
    this._resolveMenu();
  }
  this._resolveSection(menuIndex, typeof section.items !== 'undefined');
  return this;
};

Menu.prototype.items = function(sectionIndex, items) {
  if (typeof sectionIndex === 'object') {
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.itemsProvider = sectionIndex;
    return this;
  }
  if (typeof items === 'function') {
    this._getMetaSection(sectionIndex).items = items;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex };
  if (!items) {
    return this._getItems(menuIndex);
  }
  var section = this._getSection(menuIndex, true);
  section.items = items;
  this._resolveSection(menuIndex, true);
  return this;
};

Menu.prototype.item = function(sectionIndex, itemIndex, item) {
  if (typeof sectionIndex === 'object') {
    item = itemIndex || item;
    itemIndex = sectionIndex.itemIndex;
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.itemProvider = sectionIndex;
    return this;
  }
  if (typeof itemIndex === 'function') {
    item = itemIndex;
    itemIndex = null;
  }
  if (typeof item === 'function') {
    this._getMetaSection(sectionIndex).item = item;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex, itemIndex: itemIndex };
  if (!item) {
    return this._getItem(menuIndex);
  }
  var items = this._getItems(menuIndex, true);
  var prevLength = items.length;
  items[itemIndex] = util2.copy(item, items[itemIndex]);
  if (items.length !== prevLength) {
    this._resolveSection(menuIndex);
  }
  this._resolveItem(menuIndex);
  return this;
};

Menu.prototype.selection = function(callback) {
  this._selections.push(callback);
  simply.impl.menuSelection();
};

Menu.emit = Window.emit;

Menu.emitSection = function(sectionIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex
  };
  e.section = menu._getSection(e);
  if (Menu.emit('section', null, e) === false) {
    return false;
  }
  menu._resolveSection(e);
};

Menu.emitItem = function(sectionIndex, itemIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex,
    itemIndex: itemIndex,
  };
  e.section = menu._getSection(e);
  e.item = menu._getItem(e);
  if (Menu.emit('item', null, e) === false) {
    return false;
  }
  menu._resolveItem(e);
};

Menu.emitSelect = function(type, sectionIndex, itemIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex,
    itemIndex: itemIndex,
  };
  e.section = menu._getSection(e);
  e.item = menu._getItem(e);
  switch (type) {
    case 'menuSelect': type = 'select'; break;
    case 'menuLongSelect': type = 'longSelect'; break;
    case 'menuSelection': type = 'selection'; break;
  }
  if (Menu.emit(type, null, e) === false) {
    return false;
  }
  menu._emitSelect(e);
};

module.exports = Menu;
<commit_msg>Minor formatting corrections<commit_after>var util2 = require('util2');
var myutil = require('myutil');
var Emitter = require('emitter');
var WindowStack = require('ui/windowstack');
var Window = require('ui/window');
var simply = require('ui/simply');

var defaults = {
  backgroundColor: 'white',
  textColor: 'black',
  highlightBackgroundColor: 'black',
  highlightTextColor: 'white',
  fullscreen: false,
};

var Menu = function(menuDef) {
  Window.call(this, myutil.shadow(defaults, menuDef || {}));
  this._dynamic = false;
  this._sections = {};
  this._selection = { sectionIndex: 0, itemIndex: 0 };
  this._selections = [];
};

Menu._codeName = 'menu';

util2.inherit(Menu, Window);

util2.copy(Emitter.prototype, Menu.prototype);

Menu.prototype._show = function() {
  this._resolveMenu();
  Window.prototype._show.apply(this, arguments);
  var select = this._selection;
  simply.impl.menuSelection(select.sectionIndex, select.itemIndex);
};

Menu.prototype._numPreloadItems = 50;

Menu.prototype._prop = function(state, clear, pushing) {
  if (this === WindowStack.top()) {
    simply.impl.menu.call(this, state, clear, pushing);
    this._resolveSection(this._selection);
  }
};

Menu.prototype.action = function() {
  throw new Error("Menus don't support action bars.");
};

Menu.prototype.buttonConfig = function() {
  throw new Error("Menus don't support changing button configurations.");
};

Menu.prototype._buttonAutoConfig = function() {};

Menu.prototype._getMetaSection = function(sectionIndex) {
  return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} ));
};

Menu.prototype._getSections = function() {
  var sections = this.state.sections;
  if (sections instanceof Array) {
    return sections;
  }
  if (typeof sections === 'number') {
    sections = new Array(sections);
    return (this.state.sections = sections);
  }
  if (typeof sections === 'function') {
    this.sectionsProvider = this.state.sections;
    delete this.state.sections;
  }
  if (this.sectionsProvider) {
    sections = this.sectionsProvider.call(this);
    if (sections) {
      this.state.sections = sections;
      return this._getSections();
    }
  }
  return (this.state.sections = []);
};

Menu.prototype._getSection = function(e, create) {
  var sections = this._getSections();
  var section = sections[e.sectionIndex];
  if (section) {
    return section;
  }
  if (this.sectionProvider) {
    section = this.sectionProvider.call(this, e);
    if (section) {
      return (sections[e.sectionIndex] = section);
    }
  }
  if (!create) { return; }
  return (sections[e.sectionIndex] = {});
};

Menu.prototype._getItems = function(e, create) {
  var section = this._getSection(e, create);
  if (!section) {
    if (e.sectionIndex > 0) { return; }
    section = this.state.sections[0] = {};
  }
  if (section.items instanceof Array) {
    return section.items;
  }
  if (typeof section.items === 'number') {
    return (section.items = new Array(section.items));
  }
  if (typeof section.items === 'function') {
    this._sections[e.sectionIndex] = section.items;
    delete section.items;
  }
  var itemsProvider = this._getMetaSection(e.sectionIndex).items || this.itemsProvider;
  if (itemsProvider) {
    var items = itemsProvider.call(this, e);
    if (items) {
      section.items = items;
      return this._getItems(e, create);
    }
  }
  return (section.items = []);
};

Menu.prototype._getItem = function(e, create) {
  var items = this._getItems(e, create);
  var item = items[e.itemIndex];
  if (item) {
    return item;
  }
  var itemProvider = this._getMetaSection(e.sectionIndex).item || this.itemProvider;
  if (itemProvider) {
    item = itemProvider.call(this, e);
    if (item) {
      return (items[e.itemIndex] = item);
    }
  }
  if (!create) { return; }
  return (items[e.itemIndex] = {});
};

Menu.prototype._resolveMenu = function() {
  var sections = this._getSections(this);
  if (this === WindowStack.top()) {
    simply.impl.menu.call(this, this.state);
    return true;
  }
};

Menu.prototype._resolveSection = function(e, clear) {
  var section = this._getSection(e);
  if (!section) { return; }
  section.items = this._getItems(e);
  if (this === WindowStack.top()) {
    simply.impl.menuSection.call(this, e.sectionIndex, section, clear);
    var select = this._selection;
    if (select.sectionIndex === e.sectionIndex) {
      this._preloadItems(select);
    }
    return true;
  }
};

Menu.prototype._resolveItem = function(e) {
  var item = this._getItem(e);
  if (!item) { return; }
  if (this === WindowStack.top()) {
    simply.impl.menuItem.call(this, e.sectionIndex, e.itemIndex, item);
    return true;
  }
};

Menu.prototype._preloadItems = function(e) {
  var select = util2.copy(e);
  select.itemIndex = Math.max(0, select.itemIndex - Math.floor(this._numPreloadItems / 2));
  for (var i = 0; i < this._numPreloadItems; ++i) {
    this._resolveItem(select);
    select.itemIndex++;
  }
};

Menu.prototype._emitSelect = function(e) {
  this._selection = e;
  var item = this._getItem(e);
  switch (e.type) {
    case 'select':
      if (item && typeof item.select === 'function') {
        if (item.select(e) === false) {
          return false;
        }
      }
      break;
    case 'longSelect':
      if (item && typeof item.longSelect === 'function') {
        if (item.longSelect(e) === false) {
          return false;
        }
      }
      break;
    case 'selection':
      var handlers = this._selections;
      this._selections = [];
      if (item && typeof item.selected === 'function') {
        if (item.selected(e) === false) {
          return false;
        }
      }
      for (var i = 0, ii = handlers.length; i < ii; ++i) {
        if (handlers[i](e) === false) {
          break;
        }
      }
      break;
  }
};

Menu.prototype.sections = function(sections) {
  if (typeof sections === 'function') {
    delete this.state.sections;
    this.sectionsProvider = sections;
    this._resolveMenu();
    return this;
  }
  this.state.sections = sections;
  this._resolveMenu();
  return this;
};

Menu.prototype.section = function(sectionIndex, section) {
  if (typeof sectionIndex === 'object') {
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.sectionProvider = sectionIndex;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex };
  if (!section) {
    return this._getSection(menuIndex);
  }
  var sections = this._getSections();
  var prevLength = sections.length;
  sections[sectionIndex] = util2.copy(section, sections[sectionIndex]);
  if (sections.length !== prevLength) {
    this._resolveMenu();
  }
  this._resolveSection(menuIndex, typeof section.items !== 'undefined');
  return this;
};

Menu.prototype.items = function(sectionIndex, items) {
  if (typeof sectionIndex === 'object') {
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.itemsProvider = sectionIndex;
    return this;
  }
  if (typeof items === 'function') {
    this._getMetaSection(sectionIndex).items = items;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex };
  if (!items) {
    return this._getItems(menuIndex);
  }
  var section = this._getSection(menuIndex, true);
  section.items = items;
  this._resolveSection(menuIndex, true);
  return this;
};

Menu.prototype.item = function(sectionIndex, itemIndex, item) {
  if (typeof sectionIndex === 'object') {
    item = itemIndex || item;
    itemIndex = sectionIndex.itemIndex;
    sectionIndex = sectionIndex.sectionIndex || 0;
  } else if (typeof sectionIndex === 'function') {
    this.itemProvider = sectionIndex;
    return this;
  }
  if (typeof itemIndex === 'function') {
    item = itemIndex;
    itemIndex = null;
  }
  if (typeof item === 'function') {
    this._getMetaSection(sectionIndex).item = item;
    return this;
  }
  var menuIndex = { sectionIndex: sectionIndex, itemIndex: itemIndex };
  if (!item) {
    return this._getItem(menuIndex);
  }
  var items = this._getItems(menuIndex, true);
  var prevLength = items.length;
  items[itemIndex] = util2.copy(item, items[itemIndex]);
  if (items.length !== prevLength) {
    this._resolveSection(menuIndex);
  }
  this._resolveItem(menuIndex);
  return this;
};

Menu.prototype.selection = function(callback) {
  this._selections.push(callback);
  simply.impl.menuSelection();
};

Menu.emit = Window.emit;

Menu.emitSection = function(sectionIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex
  };
  e.section = menu._getSection(e);
  if (Menu.emit('section', null, e) === false) {
    return false;
  }
  menu._resolveSection(e);
};

Menu.emitItem = function(sectionIndex, itemIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex,
    itemIndex: itemIndex,
  };
  e.section = menu._getSection(e);
  e.item = menu._getItem(e);
  if (Menu.emit('item', null, e) === false) {
    return false;
  }
  menu._resolveItem(e);
};

Menu.emitSelect = function(type, sectionIndex, itemIndex) {
  var menu = WindowStack.top();
  if (!(menu instanceof Menu)) { return; }
  var e = {
    menu: menu,
    sectionIndex: sectionIndex,
    itemIndex: itemIndex,
  };
  e.section = menu._getSection(e);
  e.item = menu._getItem(e);
  switch (type) {
    case 'menuSelect': type = 'select'; break;
    case 'menuLongSelect': type = 'longSelect'; break;
    case 'menuSelection': type = 'selection'; break;
  }
  if (Menu.emit(type, null, e) === false) {
    return false;
  }
  menu._emitSelect(e);
};

module.exports = Menu;
<|endoftext|>