<commit_before>var initDone = false;
var peer = RulePeer.getInstance();
var wordPeer = WordPeer.getInstance();
var existingTabs = new Array();
var tabBadgeMap = new Array(); /* tabId, badgeCount */

/**
 * Initialization
 */
function onStart ()
{
	updateDbIfNeeded(createRuleTable);
}
function createRuleTable ()
{
	console.log("createRuleTable");
	peer.createTable(createWordTable);
}
function createWordTable ()
{
	wordPeer.createTable(loadLists);
}
function loadLists (callback)
{
	peer.select('', onRuleListLoaded, null);
}
function onRuleListLoaded (list)
{
	ruleList = list;
	wordPeer.select('', onWordListLoaded, null);
}
function removeFromExistingTabList (tabIdToRemove)
{
	for (var id in existingTabs)
	{
		if (tabIdToRemove==id) existingTabs[id] = null;
	}
}
function addToExistingTabList (tabIdToAdd)
{
	existingTabs[tabIdToAdd] = true;
}
function onWordListLoaded (wordList)
{
	var ruleMap = new Array();
	for (var i=0, l=ruleList.length; i<l; i++)
	{
		ruleMap[ruleList[i].rule_id] = ruleList[i];
	}
	for (var i = 0, l = wordList.length; i < l; i++)
	{
		var rule = ruleMap[wordList[i].rule_id];
		if (rule)
		{
			rule.words.push(wordList[i]);
		}
	}
	loadRulePickerSrc();
	saveUuidIfNotSet();
}

function saveUuidIfNotSet ()
{
	for (var i=0; i<ruleList.length; i++)
	{
		var rule = ruleList[i];
		var needSave = false;
		if (CustomBlockerUtil.isEmpty(rule.user_identifier))
		{
			needSave = true;
			rule.user_identifier = UUID.generate();
		}
		if (CustomBlockerUtil.isEmpty(rule.global_identifier))
		{
			needSave = true;
			rule.global_identifier = UUID.generate();
		}
		if (needSave)
		{
		peer.saveObject(rule, function ()
			{
				var bgWindow = chrome.extension.getBackgroundPage();
				bgWindow.reloadLists();
			});
		}
	}
}

function reloadLists ()
{
	loadLists();
}
function openRulePicker (selectedRule)
{
	try
	{
		chrome.tabs.getSelected(null,function(tab)
		{
			chrome.tabs.sendRequest(tab.id,
			{
				command: 'ruleEditor',
				src: rulePickerSrc,
				rule: selectedRule,
				appliedRuleList: appliedRuleMap[tab.id]
			}, getRulePickerOnCommandFunc(tab.id, false));
		});
	}
	catch (ex) {console.log(ex)}
}
var tabOnUpdate = function(tabId, changeInfo, tab)
{
	addToExistingTabList(tabId);
	// ON/OFF
	var isDisabled = ('true' == localStorage.blockDisabled);
	_setIconDisabled(isDisabled, tabId);
	if (isDisabled) {
		return;
	}
	var url = tab.url;
	if (url !== undefined && changeInfo.status == "complete")
	{
		chrome.tabs.sendRequest(tabId,
		{
			command:'init',
			rules: ruleList,
			tabId: tabId
		},
		getForegroundCallback (tabId)
		);
	}
}

function getForegroundCallback (tabId)
{
	//TODO callback.
	return function(param)
	{
		var useCallback = false;
		switch (param.command)
		{
			case 'badge':
				execCallbackBadge(tabId, param);
				break;
			case 'setApplied':
				execCallbackSetApplied(tabId, param);
				break;
			case 'db':
				useCallback = true;
				execCallbackDb(tabId, param);
				break;
			case 'reload':
				useCallback = true;
				execCallbackReload(tabId, param);
				break;
		}

		// Set callback
		if (!useCallback)
		{
			chrome.tabs.sendRequest(tabId,
			{
				command: (param.nextAction || 'badge')
			}, getForegroundCallback (tabId));
		}
	};

};
function execCallbackReload (tabId, param)
{
	console.log("execCallbackReload (stub)");
	chrome.tabs.sendRequest(tabId,
	{
		command: (param.nextAction),
		rules: ruleList
	}, getForegroundCallback (tabId));

}
function execCallbackDb (tabId, param)
{
	try
	{
		var exPeer;
		if ('save' == param.dbCommand)
		{
			var rule =param.obj;
			var saveRuleTask = new SaveRuleTask(rule, reloadLists, tabId);
			saveRuleTask.exec(param.nextAction);
		}
	}
	catch (e)
	{
		console.log(e)
	}
}
function execCallbackSetApplied (tabId, param)
{
	console.log("foregroundCallback setApplied. param.list=" + param.list);
	var list = param.list || new Array();
	chrome.browserAction.setIcon(
		{
			path:((list.length>0)?'icon.png':'icon_disabled.png'),
			tabId:tabId
		});
	appliedRuleMap[tabId] = list;

};
function execCallbackBadge (tabId, param)
{
	var count = param.count;
	try {
		var badgeText = ''+count;
		tabBadgeMap[tabId] = badgeText;
		chrome.browserAction.setBadgeText({
			text: badgeText,
			tabId: tabId
		});
		chrome.browserAction.setTitle({
			title: getBadgeTooltipString(count),
			tabId:tabId
		});
		appliedRuleMap[tabId] = param.rules;
	} catch (ex)
	{
		console.log(ex)
	}

}
function getAppliedRules (callback)
{
	chrome.tabs.getSelected(null,function(tab)
	{
		try
		{
			callback(appliedRuleMap[tab.id]);
		}
		catch (ex) {console.log(ex)}
	});

}
var rulePickerSrc = '';
var smartRuleEditorSrc = '';
function loadRulePickerSrc()
{
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function()
	{
		if (xhr.readyState==4)
		{
			if (xhr.status==0 || xhr.status==200)
			{
				rulePickerSrc = xhr.responseText;
				loadSmartRuleEditorSrc();
			}
		}
	}
	xhr.open("GET", chrome.extension.getURL('/rule_editor_'+chrome.i18n.getMessage("extLocale")+'.html'), true);
	xhr.send();
}
function loadSmartRuleEditorSrc()
{
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function()
	{
		if (xhr.readyState==4)
		{
			if (xhr.status==0 || xhr.status==200)
			{
				smartRuleEditorSrc = xhr.responseText;
			}
		}
	}
	xhr.open("GET", chrome.extension.getURL('/smart_rule_editor_'+chrome.i18n.getMessage("extLocale")+'.html'), true);
	xhr.send();
}

var SaveRuleTask = function (rule, reloadLists, tabId)
{
	var saveWords = new Array();
	var deleteWords = new Array();

	for (var i=0, l=rule.words.length; i<l; i++)
	{
		var word = rule.words[i];
		if (word.isNew)
		{
			saveWords.push(word);
		}
		else if (word.deleted)
		{
			deleteWords.push(word);
		}
	}

	this.rule = rule;
	this.tabId = tabId;

	this.saveWords = saveWords;
	this.deleteWords = deleteWords;

	this.reloadLists /* function */ = reloadLists;
};
SaveRuleTask.prototype.exec = function (nextAction)
{
	//peer, wordPeer
	peer.saveObject(this.rule, this.getNextTask(nextAction));
};
SaveRuleTask.prototype.getNextTask = function (nextAction)
{
	var self = this;
	return function ()
	{
		var nextSaveWord = self.getNextSaveWord();
		if (nextSaveWord)
		{
			wordPeer.saveObject(nextSaveWord, self.getNextTask(nextAction));
			return;
		}
		var nextDeleteWord = self.getNextDeleteWord();
		if (nextDeleteWord)
		{
			wordPeer.deleteObject(nextDeleteWord, self.getNextTask(nextAction));
			return;
		}
		chrome.tabs.sendRequest(self.tabId,
		{
			command:nextAction,
			rules: ruleList,
			tabId: self.tabId,
			rule: self.rule
		}
		, getForegroundCallback(self.tabId)
		);
		self.reloadLists();
	}
};
SaveRuleTask.prototype.getNextSaveWord = function ()
{
	for (var i=0, l=this.saveWords.length; i<l; i++)
	{
		var word = this.saveWords[i];
		if (word.dirty)
		{
			word.rule_id = this.rule.rule_id;
			return word;
		}
	}
	return null;
};
SaveRuleTask.prototype.getNextDeleteWord = function ()
{
	for (var i=0, l=this.deleteWords.length; i<l; i++)
	{
		var word = this.deleteWords[i];
		if (word.dirty)
		{
			word.rule_id = this.rule.rule_id;
			return word;
		}
	}
};

var ruleList = new Array();
var appliedRuleMap = new Array();
if (!chrome.tabs.customBlockerOnUpdateSet)
{
	chrome.tabs.onUpdated.addListener(tabOnUpdate);
	chrome.tabs.onRemoved.addListener
		(function(tabId, removeInfo)
			{
				removeFromExistingTabList(tabId);
				appliedRuleMap[tabId] = null;
			});
	chrome.tabs.onSelectionChanged.addListener
		(function(_tabId, selectInfo)
			{
				var tabId = parseInt(_tabId);
				for (var _index in existingTabs)
				{
					var tabIdToDisable = parseInt(_index);
					if (tabIdToDisable && tabIdToDisable!=tabId)
					{
						chrome.tabs.sendRequest(tabIdToDisable,
							{
								command: 'stop'
							}, getForegroundCallback (tabIdToDisable));
					}
				}
				try
				{
					if ('true' == localStorage.blockDisabled)
						_setIconDisabled(!applied, tabId);
					else
					{
						var applied = appliedRuleMap[tabId]
							&& appliedRuleMap[tabId].length>0;
						chrome.browserAction.setIcon(
						{
							path:(applied)?'icon.png':'icon_disabled.png',
							tabId:tabId
						});

					}
					chrome.tabs.sendRequest(tabId,
						{
							command: 'resume'
						}, getForegroundCallback(tabId));
					if (tabBadgeMap[tabId])
					{
						chrome.browserAction.setBadgeText({
							text: tabBadgeMap[tabId],
							tabId: tabId
						});
					}
				}
				catch (ex) {console.log(ex);}
			});
}
function setIconDisabled (isDisabled)
{
	chrome.tabs.getSelected(null,function(tab)
	{
		_setIconDisabled(isDisabled,tab.id);
	});
}
function _setIconDisabled (isDisabled, tabId)
{
	chrome.browserAction.setBadgeText({
		text:(isDisabled)?'OFF':'',
		tabId: tabId
	});
	chrome.browserAction.setIcon(
	{
		path:(isDisabled)?'icon_disabled.png':'icon.png',
		tabId:tabId
	});

}
function highlightRuleElements (rule)
{
	chrome.tabs.getSelected(null,function(tab)
		{
			chrome.tabs.sendRequest(tab.id,
				{
					command: 'highlight',
					rule: rule
				}, getForegroundCallback(tab.id));
		});
}
function getBadgeTooltipString (count)
{
	if (count > 1)
		return chrome.i18n.getMessage("tooltipCount").replace("__count__",count);
	else
		return chrome.i18n.getMessage("tooltipCountSingle");
}
chrome.tabs.customBlockerOnUpdateSet = true;
onStart();

function onRightClick(clicked, tab) {
	chrome.tabs.sendRequest(
		tab.id,
		{
			command: 'quickRuleCreation',
			src: smartRuleEditorSrc,
			appliedRuleList: appliedRuleMap[tab.id],
			selectionText: clicked.selectionText
		},
		getForegroundCallback(tab.id)
	);
}

var menuId = chrome.contextMenus.create({"title": chrome.i18n.getMessage('menuCreateRule') + " (Beta)", "contexts":["selection"],
	"onclick": onRightClick});
<commit_msg>Fix bug (obsolete method was called)<commit_after>var initDone = false;
var peer = RulePeer.getInstance();
var wordPeer = WordPeer.getInstance();
var existingTabs = new Array();
var tabBadgeMap = new Array(); /* tabId, badgeCount */

/**
 * Initialization
 */
function onStart ()
{
	updateDbIfNeeded(createRuleTable);
}
function createRuleTable ()
{
	console.log("createRuleTable");
	peer.createTable(createWordTable);
}
function createWordTable ()
{
	wordPeer.createTable(loadLists);
}
function loadLists (callback)
{
	peer.select('', onRuleListLoaded, null);
}
function onRuleListLoaded (list)
{
	ruleList = list;
	wordPeer.select('', onWordListLoaded, null);
}
function removeFromExistingTabList (tabIdToRemove)
{
	for (var id in existingTabs)
	{
		if (tabIdToRemove==id) existingTabs[id] = null;
	}
}
function addToExistingTabList (tabIdToAdd)
{
	existingTabs[tabIdToAdd] = true;
}
function onWordListLoaded (wordList)
{
	var ruleMap = new Array();
	for (var i=0, l=ruleList.length; i<l; i++)
	{
		ruleMap[ruleList[i].rule_id] = ruleList[i];
	}
	for (var i = 0, l = wordList.length; i < l; i++)
	{
		var rule = ruleMap[wordList[i].rule_id];
		if (rule)
		{
			rule.words.push(wordList[i]);
		}
	}
	loadRulePickerSrc();
	saveUuidIfNotSet();
}

function saveUuidIfNotSet ()
{
	for (var i=0; i<ruleList.length; i++)
	{
		var rule = ruleList[i];
		var needSave = false;
		if (CustomBlockerUtil.isEmpty(rule.user_identifier))
		{
			needSave = true;
			rule.user_identifier = UUID.generate();
		}
		if (CustomBlockerUtil.isEmpty(rule.global_identifier))
		{
			needSave = true;
			rule.global_identifier = UUID.generate();
		}
		if (needSave)
		{
		peer.saveObject(rule, function ()
			{
				var bgWindow = chrome.extension.getBackgroundPage();
				bgWindow.reloadLists();
			});
		}
	}
}

function reloadLists ()
{
	loadLists();
}
function openRulePicker (selectedRule)
{
	try
	{
		chrome.tabs.getSelected(null,function(tab)
		{
			chrome.tabs.sendRequest(tab.id,
			{
				command: 'ruleEditor',
				src: rulePickerSrc,
				rule: selectedRule,
				appliedRuleList: appliedRuleMap[tab.id]
			}, getForegroundCallback(tab.id));
		});
	}
	catch (ex) {console.log(ex)}
}
var tabOnUpdate = function(tabId, changeInfo, tab)
{
	addToExistingTabList(tabId);
	// ON/OFF
	var isDisabled = ('true' == localStorage.blockDisabled);
	_setIconDisabled(isDisabled, tabId);
	if (isDisabled) {
		return;
	}
	var url = tab.url;
	if (url !== undefined && changeInfo.status == "complete")
	{
		chrome.tabs.sendRequest(tabId,
		{
			command:'init',
			rules: ruleList,
			tabId: tabId
		},
		getForegroundCallback (tabId)
		);
	}
}

function getForegroundCallback (tabId)
{
	//TODO callback.
	return function(param)
	{
		var useCallback = false;
		switch (param.command)
		{
			case 'badge':
				execCallbackBadge(tabId, param);
				break;
			case 'setApplied':
				execCallbackSetApplied(tabId, param);
				break;
			case 'db':
				useCallback = true;
				execCallbackDb(tabId, param);
				break;
			case 'reload':
				useCallback = true;
				execCallbackReload(tabId, param);
				break;
		}

		// Set callback
		if (!useCallback)
		{
			chrome.tabs.sendRequest(tabId,
			{
				command: (param.nextAction || 'badge')
			}, getForegroundCallback (tabId));
		}
	};

};
function execCallbackReload (tabId, param)
{
	console.log("execCallbackReload (stub)");
	chrome.tabs.sendRequest(tabId,
	{
		command: (param.nextAction),
		rules: ruleList
	}, getForegroundCallback (tabId));

}
function execCallbackDb (tabId, param)
{
	try
	{
		var exPeer;
		if ('save' == param.dbCommand)
		{
			var rule =param.obj;
			var saveRuleTask = new SaveRuleTask(rule, reloadLists, tabId);
			saveRuleTask.exec(param.nextAction);
		}
	}
	catch (e)
	{
		console.log(e)
	}
}
function execCallbackSetApplied (tabId, param)
{
	console.log("foregroundCallback setApplied. param.list=" + param.list);
	var list = param.list || new Array();
	chrome.browserAction.setIcon(
		{
			path:((list.length>0)?'icon.png':'icon_disabled.png'),
			tabId:tabId
		});
	appliedRuleMap[tabId] = list;

};
function execCallbackBadge (tabId, param)
{
	var count = param.count;
	try {
		var badgeText = ''+count;
		tabBadgeMap[tabId] = badgeText;
		chrome.browserAction.setBadgeText({
			text: badgeText,
			tabId: tabId
		});
		chrome.browserAction.setTitle({
			title: getBadgeTooltipString(count),
			tabId:tabId
		});
		appliedRuleMap[tabId] = param.rules;
	} catch (ex)
	{
		console.log(ex)
	}

}
function getAppliedRules (callback)
{
	chrome.tabs.getSelected(null,function(tab)
	{
		try
		{
			callback(appliedRuleMap[tab.id]);
		}
		catch (ex) {console.log(ex)}
	});

}
var rulePickerSrc = '';
var smartRuleEditorSrc = '';
function loadRulePickerSrc()
{
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function()
	{
		if (xhr.readyState==4)
		{
			if (xhr.status==0 || xhr.status==200)
			{
				rulePickerSrc = xhr.responseText;
				loadSmartRuleEditorSrc();
			}
		}
	}
	xhr.open("GET", chrome.extension.getURL('/rule_editor_'+chrome.i18n.getMessage("extLocale")+'.html'), true);
	xhr.send();
}
function loadSmartRuleEditorSrc()
{
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function()
	{
		if (xhr.readyState==4)
		{
			if (xhr.status==0 || xhr.status==200)
			{
				smartRuleEditorSrc = xhr.responseText;
			}
		}
	}
	xhr.open("GET", chrome.extension.getURL('/smart_rule_editor_'+chrome.i18n.getMessage("extLocale")+'.html'), true);
	xhr.send();
}

var SaveRuleTask = function (rule, reloadLists, tabId)
{
	var saveWords = new Array();
	var deleteWords = new Array();

	for (var i=0, l=rule.words.length; i<l; i++)
	{
		var word = rule.words[i];
		if (word.isNew)
		{
			saveWords.push(word);
		}
		else if (word.deleted)
		{
			deleteWords.push(word);
		}
	}

	this.rule = rule;
	this.tabId = tabId;

	this.saveWords = saveWords;
	this.deleteWords = deleteWords;

	this.reloadLists /* function */ = reloadLists;
};
SaveRuleTask.prototype.exec = function (nextAction)
{
	//peer, wordPeer
	peer.saveObject(this.rule, this.getNextTask(nextAction));
};
SaveRuleTask.prototype.getNextTask = function (nextAction)
{
	var self = this;
	return function ()
	{
		var nextSaveWord = self.getNextSaveWord();
		if (nextSaveWord)
		{
			wordPeer.saveObject(nextSaveWord, self.getNextTask(nextAction));
			return;
		}
		var nextDeleteWord = self.getNextDeleteWord();
		if (nextDeleteWord)
		{
			wordPeer.deleteObject(nextDeleteWord, self.getNextTask(nextAction));
			return;
		}
		chrome.tabs.sendRequest(self.tabId,
		{
			command:nextAction,
			rules: ruleList,
			tabId: self.tabId,
			rule: self.rule
		}
		, getForegroundCallback(self.tabId)
		);
		self.reloadLists();
	}
};
SaveRuleTask.prototype.getNextSaveWord = function ()
{
	for (var i=0, l=this.saveWords.length; i<l; i++)
	{
		var word = this.saveWords[i];
		if (word.dirty)
		{
			word.rule_id = this.rule.rule_id;
			return word;
		}
	}
	return null;
};
SaveRuleTask.prototype.getNextDeleteWord = function ()
{
	for (var i=0, l=this.deleteWords.length; i<l; i++)
	{
		var word = this.deleteWords[i];
		if (word.dirty)
		{
			word.rule_id = this.rule.rule_id;
			return word;
		}
	}
};

var ruleList = new Array();
var appliedRuleMap = new Array();
if (!chrome.tabs.customBlockerOnUpdateSet)
{
	chrome.tabs.onUpdated.addListener(tabOnUpdate);
	chrome.tabs.onRemoved.addListener
		(function(tabId, removeInfo)
			{
				removeFromExistingTabList(tabId);
				appliedRuleMap[tabId] = null;
			});
	chrome.tabs.onSelectionChanged.addListener
		(function(_tabId, selectInfo)
			{
				var tabId = parseInt(_tabId);
				for (var _index in existingTabs)
				{
					var tabIdToDisable = parseInt(_index);
					if (tabIdToDisable && tabIdToDisable!=tabId)
					{
						chrome.tabs.sendRequest(tabIdToDisable,
							{
								command: 'stop'
							}, getForegroundCallback (tabIdToDisable));
					}
				}
				try
				{
					if ('true' == localStorage.blockDisabled)
						_setIconDisabled(!applied, tabId);
					else
					{
						var applied = appliedRuleMap[tabId]
							&& appliedRuleMap[tabId].length>0;
						chrome.browserAction.setIcon(
						{
							path:(applied)?'icon.png':'icon_disabled.png',
							tabId:tabId
						});

					}
					chrome.tabs.sendRequest(tabId,
						{
							command: 'resume'
						}, getForegroundCallback(tabId));
					if (tabBadgeMap[tabId])
					{
						chrome.browserAction.setBadgeText({
							text: tabBadgeMap[tabId],
							tabId: tabId
						});
					}
				}
				catch (ex) {console.log(ex);}
			});
}
function setIconDisabled (isDisabled)
{
	chrome.tabs.getSelected(null,function(tab)
	{
		_setIconDisabled(isDisabled,tab.id);
	});
}
function _setIconDisabled (isDisabled, tabId)
{
	chrome.browserAction.setBadgeText({
		text:(isDisabled)?'OFF':'',
		tabId: tabId
	});
	chrome.browserAction.setIcon(
	{
		path:(isDisabled)?'icon_disabled.png':'icon.png',
		tabId:tabId
	});

}
function highlightRuleElements (rule)
{
	chrome.tabs.getSelected(null,function(tab)
		{
			chrome.tabs.sendRequest(tab.id,
				{
					command: 'highlight',
					rule: rule
				}, getForegroundCallback(tab.id));
		});
}
function getBadgeTooltipString (count)
{
	if (count > 1)
		return chrome.i18n.getMessage("tooltipCount").replace("__count__",count);
	else
		return chrome.i18n.getMessage("tooltipCountSingle");
}
chrome.tabs.customBlockerOnUpdateSet = true;
onStart();

function onRightClick(clicked, tab) {
	chrome.tabs.sendRequest(
		tab.id,
		{
			command: 'quickRuleCreation',
			src: smartRuleEditorSrc,
			appliedRuleList: appliedRuleMap[tab.id],
			selectionText: clicked.selectionText
		},
		getForegroundCallback(tab.id)
	);
}

var menuId = chrome.contextMenus.create({"title": chrome.i18n.getMessage('menuCreateRule') + " (Beta)", "contexts":["selection"],
	"onclick": onRightClick});
<|endoftext|>


<commit_before>function copyText(text) {
  // Egregiously stolen from
  // https://stackoverflow.com/questions/3436102/copy-to-clipboard-in-chrome-extension
  var copyFrom = document.createElement("textarea");
  copyFrom.textContent = text;
  var body = document.getElementsByTagName('body')[0];
  body.appendChild(copyFrom);
  copyFrom.select();
  document.execCommand('copy');
  body.removeChild(copyFrom);
}

var menuDef = {
  "title": "AmaJSON Export \"%s\:",
  "contexts": ["link"],
  "onclick": function(textInfo, tab) {
    chrome.tabs.executeScript(tab.id, {
      "file": "amajson.js",
    })

    chrome.tabs.sendMessage(tab.id, textInfo, function(result) {
      if (!result || typeof(result.node) === "undefined") {
        chrome.tabs.executeScript(tab.id, {
          code: "alert(\"Unable to process, please re-try\");"
        })
        return;
      }
      copyText(result.node);
    })
  }
};

chrome.contextMenus.create(menuDef);
<commit_msg>background.js: Refactor, fix async bugs, doc<commit_after>/* CONSTANTS */

const CONTEXT_MENU_CONFIG = {
  "title": "Export notes to JSON",
  "documentUrlPatterns": ["*://*.amazon.com/notebook"],
}

const REASONABLE_TIMEOUT_FOR_SCREEN_PARSE = 3000;

/*
 * void copyText(string jsonSerializationText)
 *
 * Adds the content of jsonSerializationText to the system clipboard
 *
 */
function copyText(text) {
  // Egregiously stolen from
  // https://stackoverflow.com/questions/3436102/copy-to-clipboard-in-chrome-extension

  // Create a temporary node with the converted content as a node
  let copyFrom = document.createElement("textarea");
  copyFrom.textContent = text;

  let body = document.getElementsByTagName('body')[0];
  body.appendChild(copyFrom);

  // Select the content and copy it to the clipboard
  copyFrom.select();
  document.execCommand('copy');

  // Cleanup
  body.removeChild(copyFrom);
}

/*
 *
 * Event handler for the context-click operation. The pure eventing is handled
 * in the call to `chrome.contextMenus.create` below. Given the event
 * execution, we enter this handler.
 *
 * In order to run a more "back-end" style JavaScript code payload, we need to
 * pass a message to the content scripts held in `amajson.js`. Therefore the
 * first step is to load `amajson.js`. Due to the security sandboxing we need
 * to load these contents (inject them into the page) using `executeScript`.
 *
 * This occurs asynchronously so we need to wait until the Promise
 * resolves. Upon resolution we can continue processing the event. In this
 * case, the action is to send the message to the listener defined in
 * `amajson.js`.
 *
 */

function exportToJsonClick(info, tab) {
  let processPageNodes = new Promise((resolve, reject) => {
    chrome.tabs.executeScript(tab.id, {
      "file": "amajson.js",
    }, () => resolve());
    setTimeout(() => reject(), REASONABLE_TIMEOUT_FOR_SCREEN_PARSE);
  })

  processPageNodes.then(() => {
    chrome.tabs.sendMessage(tab.id, info, function(result) {
      copyText(result.node);
    })
  })
}

// This code fires during the extension (re)load
chrome.contextMenus.create(Object.assign(CONTEXT_MENU_CONFIG, {
  "onclick": exportToJsonClick
}));
<|endoftext|>


<commit_before>import request from 'browser-request';

chrome.runtime.onMessage.addListener(function (req, sender, callback) {
  if (req.action === 'getProfessorLink') {
    console.log('hi');
    request(req.options, (error, response, body) => {
      error = true;
      if (error) {
        callback({
          error: error,
          response: response
        });
      }
      callback(body);
    });
  } else if (req.action === 'getProfessorInfo') {
    request(req.options, (error, response, body) => {
      if (error) {
        callback({
          error: error,
          response: response
        });
      }
      callback(body);
    });
  }
  return true;
});

<commit_msg>refactor and clean up background script<commit_after>import request from 'browser-request';

chrome.runtime.onMessage.addListener(function (req, sender, callback) {
  if (req.action === 'getProfessorLink' || req.action === 'getProfessorInfo') {
    request(req.options, (error, response, body) => {
      if (error) {
        callback({
          error: error,
          response: response
        });
      }
      callback({body: body});
    });
    return true;
  }
});

<|endoftext|>


<commit_before>var defaultSettings = {
  focus: {
    duration: 25,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  break: {
    duration: 5,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  version: 1
};

var sounds = [
  { name: 'Tone', file: 'tone.mp3' },
  { name: 'Electronic Chime', file: 'electronic-chime.mp3' },
  { name: 'Gong 1', file: 'gong-1.mp3' },
  { name: 'Gong 2', file: 'gong-2.mp3' },
  { name: 'Computer Magic', file: 'computer-magic.mp3' },
  { name: 'Fire Pager', file: 'fire-pager.mp3' },
  { name: 'Glass Ping', file: 'glass-ping.mp3' },
  { name: 'Music Box', file: 'music-box.mp3' },
  { name: 'Pin Dropping', file: 'pin-dropping.mp3' },
  { name: 'Robot Blip 1', file: 'robot-blip-1.mp3' },
  { name: 'Robot Blip 2', file: 'robot-blip-2.mp3' },
  { name: 'Ship Bell', file: 'ship-bell.mp3' },
  { name: 'Train Horn', file: 'train-horn.mp3' },
  { name: 'Bike Horn', file: 'bike-horn.mp3' },
  { name: 'Bell Ring', file: 'bell-ring.mp3' },
  { name: 'Reception Bell', file: 'reception-bell.mp3' },
  { name: 'Toaster Oven', file: 'toaster-oven.mp3' },
  { name: 'Battle Horn', file: 'battle-horn.mp3' },
  { name: 'Ding', file: 'ding.mp3' },
  { name: 'Dong', file: 'dong.mp3' },
  { name: 'Ding Dong', file: 'ding-dong.mp3' },
  { name: 'Din Ding', file: 'din-ding.mp3' }
];

for (var i = 0; i < sounds.length; ++i) {
  sounds[i].file = 'chrome-extension://' + chrome.runtime.id + '/audio/' + sounds[i].file;
}

function Timer(durationSec, tickSec) {
  var self = this;
  var state = 'stopped';

  this.tickInterval = null;
  this.expireTimeout = null;

  this.periodStartTime = null;
  this.remainingSec = null;

  this.start = function() {
    if (state !== 'stopped') {
      return;
    }

    this.expireTimeout = createExpireTimeout(durationSec);
    this.tickInterval = createTickInterval(tickSec);

    this.remainingSec = durationSec;

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('start', [{
      elapsed: 0,
      remaining: this.remainingSec
    }]);
  };

  this.stop = function() {
    if (state === 'stopped') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    this.tickInterval = null;
    this.expireTimeout = null;
    this.periodStartTime = null;
    this.remainingSec = null;

    state = 'stopped';
    this.emitEvent('stop', [{}]);
  };

  this.pause = function() {
    if (state !== 'running') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    var periodSec = (Date.now() - this.periodStartTime) / 1000;
    this.remainingSec -= periodSec;

    state = 'paused';
    this.periodStartTime = null;
    this.emitEvent('pause', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.resume = function() {
    if (state !== 'paused') {
      return;
    }

    this.expireTimeout = createExpireTimeout(this.remainingSec);
    this.tickInterval = createTickInterval(tickSec);

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('resume', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.reset = function() {
    this.stop();
    this.start();
  };

  this.state = function() {
    return state;
  };

  function createExpireTimeout(seconds) {
    return setTimeout(function() {
      clearInterval(self.tickInterval);
      clearTimeout(self.expireTimeout);

      self.tickInterval = null;
      self.expireTimeout = null;
      self.periodStartTime = null;
      self.remainingSec = null;

      state = 'stopped';
      self.emitEvent('expire', [{
        elapsed: durationSec,
        remaining: 0
      }]);
    }, seconds * 1000);
  }

  function createTickInterval(seconds) {
    return setInterval(function() {
      var periodSec = (Date.now() - self.periodStartTime) / 1000;
      var remainingSec = self.remainingSec - periodSec;

      self.emitEvent('tick', [{
        elapsed: durationSec - remainingSec,
        remaining: remainingSec
      }]);
    }, seconds * 1000);
  }
}

Timer.prototype = Object.create(EventEmitter.prototype);

function BadgeObserver() {
}

BadgeObserver.observe = function(timer, title, color) {
  timer.addListener('start', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('tick', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('stop', function() {
    removeBadge();
  });

  timer.addListener('pause', function() {
    updateBadge({ text: '—', title: 'Paused' });
  });

  timer.addListener('resume', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('expire', function() {
    removeBadge();
  });

  function updateBadge(options) {
    var minutes = options.minutes;
    if (minutes != null) {
      text = ((minutes == 0) ? '<1' : minutes)  + 'm';
      badgeTitle = title + ': ' + minutes + 'm remaining.';
    } else {
      text = options.text;
      badgeTitle = title + ': ' + options.title;
    }

    chrome.browserAction.setTitle({ title: badgeTitle });
    chrome.browserAction.setBadgeText({ text: text });
    chrome.browserAction.setBadgeBackgroundColor({ color: color });
  };

  function removeBadge() {
    chrome.browserAction.setTitle({ title: '' });
    chrome.browserAction.setBadgeText({ text: '' });
  }
}

function MenuEntry(title, action) {
  this.id = null;

  this.show = function() {
    this.hide();

    if (title === null) {
      this.id = chrome.contextMenus.create({
        type: 'separator',
        contexts: ['browser_action']
      });
    } else {
      this.id = chrome.contextMenus.create({
        title: title,
        contexts: ['browser_action'],
        onclick: action
      });
    }
  };

  this.hide = function() {
    if (this.id === null) {
      return;
    }

    chrome.contextMenus.remove(this.id, function() { });
    this.id = null;
  };
}

MenuEntry.create = function(title, action) {
  var menu = new MenuEntry(title, action);
  menu.show();
};

function AudioObserver() {
}

AudioObserver.observe = function(timer, sourceFile) {
  timer.addListener('expire', function() {
    var audio = new Audio();
    audio.src = sourceFile;
    audio.play();
  });
};

function ContextMenuObserver() {
}

ContextMenuObserver.observe = function(controller, timer) {
  var stop = new MenuEntry('Stop', controller.stop);
  var pause = new MenuEntry('Pause', controller.pause);
  var resume = new MenuEntry('Resume', controller.resume);

  timer.addListener('start', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('pause', function() {
    resume.show();
    stop.show();
    pause.hide();
  });

  timer.addListener('resume', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('stop', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });

  timer.addListener('expire', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });
};

function Controller() {
  var self = this;
  var focusNext = true;
  var focusTimer;
  var breakTimer;

  this.startSession = function() {
    focusTimer.stop();
    breakTimer.stop();

    if (focusNext) {
      focusTimer.start();
    } else {
      breakTimer.start();
    }
  };

  this.browserAction = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    } else if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    } else {
      this.startSession();
    }
  };

  this.pause = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    }
  };

  this.stop = function() {
    focusTimer.stop();
    breakTimer.stop();
  };

  this.resume = function() {
    if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    }
  };

  this.startBreak = function() {
    focusTimer.stop();
    breakTimer.stop();
    breakTimer.start();
  };

  this.startFocus = function() {
    focusTimer.stop();
    breakTimer.stop();
    focusTimer.start();
  };

  this.focusNext = function() {
    return focusNext;
  };

  this.getSettings = function(callback) {
    chrome.storage.sync.get(function(result) {
      if (Object.keys(result).length == 0) {
        chrome.storage.sync.set(defaultSettings, function() {
          callback(defaultSettings);
        });
      } else {
        callback(result);
      }
    });
  };

  this.setSettings = function(settings, callback) {
    chrome.storage.sync.set(settings, function() {
      createTimers();
      callback();
    });
  };

  function showExpirePage(callback) {
    if (expirePageTabId !== null) {
      chrome.tabs.update(expirePageTabId, { active: true, highlighted: true }, callback);
    } else {
      chrome.tabs.create({ url: chrome.extension.getURL('expire/expire.html') }, function(tab) {
        expirePageTabId = tab.id;
        if (callback) {
          callback(tab);
        }
      });
    }
  }

  var expirePageTabId = null;
  chrome.tabs.onRemoved.addListener(function(tabId) {
    if (tabId === expirePageTabId) {
      expirePageTabId = null;
    }
  });

  function notify(title, message, buttonTitle) {
    var options = {
      type: 'basic',
      title: title,
      message: message,
      iconUrl: 'icons/128.png',
      isClickable: true,
      buttons: [{ title: buttonTitle, iconUrl: 'icons/start.png' }]
    };

    chrome.notifications.create('', options, function() { });
  }

  chrome.notifications.onClicked.addListener(function() {
    showExpirePage(function(tab) {
      chrome.windows.update(tab.windowId, { focused: true });
    });
  });

  chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex) {
    self.focusNext() ? self.startFocus() : self.startBreak();
    chrome.notifications.clear(notificationId);
  });

  function createTimers() {
    self.getSettings(function(settings) {
      if (focusTimer) {
        focusTimer.stop();
      }

      focusTimer = createFocusTimer(settings);

      if (breakTimer) {
        breakTimer.stop();
      }

      breakTimer = createBreakTimer(settings);
    });
  }

  function createFocusTimer(settings) {
    var timer = new Timer(settings.focus.duration * 60, 60);
    BadgeObserver.observe(timer, 'Focus', '#990000');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.focus.sound);

    timer.addListener('expire', function() {
      focusNext = false;

      if (settings.focus.desktopNotification) {
        notify(
          'Take a break!',
          "Start your break when you're ready",
          'Start break now'
        );
      }

      if (settings.focus.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', closeExtensionTabs);

    return timer;
  }

  function createBreakTimer(settings) {
    var timer = new Timer(settings.break.duration * 60, 60);
    BadgeObserver.observe(timer, 'Break', '#009900');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.break.sound);

    timer.addListener('expire', function() {
      focusNext = true;

      if (settings.break.desktopNotification) {
        notify(
          'Break finished',
          "Start your focus session when you're ready",
          'Start focusing now'
        );
      }

      if (settings.break.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', closeExtensionTabs);

    return timer;
  }

  function closeExtensionTabs() {
    var id = chrome.runtime.id;
    var extensionUrl = 'chrome-extension://' + id;

    chrome.tabs.query({}, function(tabs) {
      var remove = [];
      for (var i = 0; i < tabs.length; ++i) {
        if (tabs[i].url.indexOf(extensionUrl) !== -1) {
          remove.push(tabs[i].id);
        }
      }

      chrome.tabs.remove(remove, function() { });
    });
  }

  createTimers();
}

var controller = new Controller();

chrome.contextMenus.removeAll();

MenuEntry.create('Start focusing', controller.startFocus);
MenuEntry.create('Start break', controller.startBreak);
MenuEntry.create(null);

chrome.browserAction.onClicked.addListener(function() {
  controller.browserAction();
});

chrome.runtime.onMessage.addListener(function(request, sender, respond) {
  if (request.command === 'get-session') {
    if (controller.focusNext()) {
      controller.getSettings(function(settings) {
        respond({
          focusNext: true,
          title: 'Break finished',
          subtitle: "Start your " + settings.focus.duration + " minute focus session when you're ready",
          action: 'Start Focusing'
        });
      });
    } else {
      controller.getSettings(function(settings) {
        respond({
          focusNext: false,
          title: 'Take a break!',
          subtitle: "Start your " + settings.break.duration + " minute break when you're ready",
          action: 'Start Break'
        });
      });
    }
  } else if (request.command === 'start-session') {
    controller.startSession();
    respond({});
  } else if (request.command === 'get-sounds') {
    respond(sounds);
  } else if (request.command === 'get-settings') {
    controller.getSettings(respond);
  } else if (request.command === 'set-settings') {
    var newSettings = request.settings;
    var focusDuration = newSettings.focus.duration.trim();
    var breakDuration = newSettings.break.duration.trim();

    if (!focusDuration) {
      respond({ error: 'Focus duration is required.' });
      return true;
    } else if (!breakDuration) {
      respond({ error: 'Break duration is required.' });
      return true;
    }

    var focusParsed = +focusDuration;
    var breakParsed = +breakDuration;

    if (focusParsed <= 0 || isNaN(focusParsed)) {
      respond({ error: 'Focus duration must be a positive number.' });
      return true;
    } else if (breakParsed <= 0 || isNaN(breakParsed)) {
      respond({ error: 'Break duration must be a positive number.' });
      return true;
    }

    newSettings.focus.duration = focusParsed;
    newSettings.break.duration = breakParsed;

    controller.setSettings(newSettings, function() {
      respond({});
    });
  }

  return true;
});
<commit_msg>Fix #15: Starting timer should immediately dismiss desktop notification.<commit_after>var defaultSettings = {
  focus: {
    duration: 25,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  break: {
    duration: 5,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  version: 1
};

var sounds = [
  { name: 'Tone', file: 'tone.mp3' },
  { name: 'Electronic Chime', file: 'electronic-chime.mp3' },
  { name: 'Gong 1', file: 'gong-1.mp3' },
  { name: 'Gong 2', file: 'gong-2.mp3' },
  { name: 'Computer Magic', file: 'computer-magic.mp3' },
  { name: 'Fire Pager', file: 'fire-pager.mp3' },
  { name: 'Glass Ping', file: 'glass-ping.mp3' },
  { name: 'Music Box', file: 'music-box.mp3' },
  { name: 'Pin Dropping', file: 'pin-dropping.mp3' },
  { name: 'Robot Blip 1', file: 'robot-blip-1.mp3' },
  { name: 'Robot Blip 2', file: 'robot-blip-2.mp3' },
  { name: 'Ship Bell', file: 'ship-bell.mp3' },
  { name: 'Train Horn', file: 'train-horn.mp3' },
  { name: 'Bike Horn', file: 'bike-horn.mp3' },
  { name: 'Bell Ring', file: 'bell-ring.mp3' },
  { name: 'Reception Bell', file: 'reception-bell.mp3' },
  { name: 'Toaster Oven', file: 'toaster-oven.mp3' },
  { name: 'Battle Horn', file: 'battle-horn.mp3' },
  { name: 'Ding', file: 'ding.mp3' },
  { name: 'Dong', file: 'dong.mp3' },
  { name: 'Ding Dong', file: 'ding-dong.mp3' },
  { name: 'Din Ding', file: 'din-ding.mp3' }
];

for (var i = 0; i < sounds.length; ++i) {
  sounds[i].file = 'chrome-extension://' + chrome.runtime.id + '/audio/' + sounds[i].file;
}

function Timer(durationSec, tickSec) {
  var self = this;
  var state = 'stopped';

  this.tickInterval = null;
  this.expireTimeout = null;

  this.periodStartTime = null;
  this.remainingSec = null;

  this.start = function() {
    if (state !== 'stopped') {
      return;
    }

    this.expireTimeout = createExpireTimeout(durationSec);
    this.tickInterval = createTickInterval(tickSec);

    this.remainingSec = durationSec;

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('start', [{
      elapsed: 0,
      remaining: this.remainingSec
    }]);
  };

  this.stop = function() {
    if (state === 'stopped') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    this.tickInterval = null;
    this.expireTimeout = null;
    this.periodStartTime = null;
    this.remainingSec = null;

    state = 'stopped';
    this.emitEvent('stop', [{}]);
  };

  this.pause = function() {
    if (state !== 'running') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    var periodSec = (Date.now() - this.periodStartTime) / 1000;
    this.remainingSec -= periodSec;

    state = 'paused';
    this.periodStartTime = null;
    this.emitEvent('pause', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.resume = function() {
    if (state !== 'paused') {
      return;
    }

    this.expireTimeout = createExpireTimeout(this.remainingSec);
    this.tickInterval = createTickInterval(tickSec);

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('resume', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.reset = function() {
    this.stop();
    this.start();
  };

  this.state = function() {
    return state;
  };

  function createExpireTimeout(seconds) {
    return setTimeout(function() {
      clearInterval(self.tickInterval);
      clearTimeout(self.expireTimeout);

      self.tickInterval = null;
      self.expireTimeout = null;
      self.periodStartTime = null;
      self.remainingSec = null;

      state = 'stopped';
      self.emitEvent('expire', [{
        elapsed: durationSec,
        remaining: 0
      }]);
    }, seconds * 1000);
  }

  function createTickInterval(seconds) {
    return setInterval(function() {
      var periodSec = (Date.now() - self.periodStartTime) / 1000;
      var remainingSec = self.remainingSec - periodSec;

      self.emitEvent('tick', [{
        elapsed: durationSec - remainingSec,
        remaining: remainingSec
      }]);
    }, seconds * 1000);
  }
}

Timer.prototype = Object.create(EventEmitter.prototype);

function BadgeObserver() {
}

BadgeObserver.observe = function(timer, title, color) {
  timer.addListener('start', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('tick', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('stop', function() {
    removeBadge();
  });

  timer.addListener('pause', function() {
    updateBadge({ text: '—', title: 'Paused' });
  });

  timer.addListener('resume', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('expire', function() {
    removeBadge();
  });

  function updateBadge(options) {
    var minutes = options.minutes;
    if (minutes != null) {
      text = ((minutes == 0) ? '<1' : minutes)  + 'm';
      badgeTitle = title + ': ' + minutes + 'm remaining.';
    } else {
      text = options.text;
      badgeTitle = title + ': ' + options.title;
    }

    chrome.browserAction.setTitle({ title: badgeTitle });
    chrome.browserAction.setBadgeText({ text: text });
    chrome.browserAction.setBadgeBackgroundColor({ color: color });
  };

  function removeBadge() {
    chrome.browserAction.setTitle({ title: '' });
    chrome.browserAction.setBadgeText({ text: '' });
  }
}

function MenuEntry(title, action) {
  this.id = null;

  this.show = function() {
    this.hide();

    if (title === null) {
      this.id = chrome.contextMenus.create({
        type: 'separator',
        contexts: ['browser_action']
      });
    } else {
      this.id = chrome.contextMenus.create({
        title: title,
        contexts: ['browser_action'],
        onclick: action
      });
    }
  };

  this.hide = function() {
    if (this.id === null) {
      return;
    }

    chrome.contextMenus.remove(this.id, function() { });
    this.id = null;
  };
}

MenuEntry.create = function(title, action) {
  var menu = new MenuEntry(title, action);
  menu.show();
};

function AudioObserver() {
}

AudioObserver.observe = function(timer, sourceFile) {
  timer.addListener('expire', function() {
    var audio = new Audio();
    audio.src = sourceFile;
    audio.play();
  });
};

function ContextMenuObserver() {
}

ContextMenuObserver.observe = function(controller, timer) {
  var stop = new MenuEntry('Stop', controller.stop);
  var pause = new MenuEntry('Pause', controller.pause);
  var resume = new MenuEntry('Resume', controller.resume);

  timer.addListener('start', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('pause', function() {
    resume.show();
    stop.show();
    pause.hide();
  });

  timer.addListener('resume', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('stop', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });

  timer.addListener('expire', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });
};

function Controller() {
  var self = this;
  var focusNext = true;
  var focusTimer;
  var breakTimer;
  var notificationId = null;

  this.startSession = function() {
    focusTimer.stop();
    breakTimer.stop();

    if (focusNext) {
      focusTimer.start();
    } else {
      breakTimer.start();
    }
  };

  this.browserAction = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    } else if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    } else {
      this.startSession();
    }
  };

  this.pause = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    }
  };

  this.stop = function() {
    focusTimer.stop();
    breakTimer.stop();
  };

  this.resume = function() {
    if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    }
  };

  this.startBreak = function() {
    focusTimer.stop();
    breakTimer.stop();
    breakTimer.start();
  };

  this.startFocus = function() {
    focusTimer.stop();
    breakTimer.stop();
    focusTimer.start();
  };

  this.focusNext = function() {
    return focusNext;
  };

  this.getSettings = function(callback) {
    chrome.storage.sync.get(function(result) {
      if (Object.keys(result).length == 0) {
        chrome.storage.sync.set(defaultSettings, function() {
          callback(defaultSettings);
        });
      } else {
        callback(result);
      }
    });
  };

  this.setSettings = function(settings, callback) {
    chrome.storage.sync.set(settings, function() {
      createTimers();
      callback();
    });
  };

  function showExpirePage(callback) {
    if (expirePageTabId !== null) {
      chrome.tabs.update(expirePageTabId, { active: true, highlighted: true }, callback);
    } else {
      chrome.tabs.create({ url: chrome.extension.getURL('expire/expire.html') }, function(tab) {
        expirePageTabId = tab.id;
        if (callback) {
          callback(tab);
        }
      });
    }
  }

  var expirePageTabId = null;
  chrome.tabs.onRemoved.addListener(function(tabId) {
    if (tabId === expirePageTabId) {
      expirePageTabId = null;
    }
  });

  function notify(title, message, buttonTitle) {
    var options = {
      type: 'basic',
      title: title,
      message: message,
      iconUrl: 'icons/128.png',
      isClickable: true,
      buttons: [{ title: buttonTitle, iconUrl: 'icons/start.png' }]
    };

    chrome.notifications.create('', options, function (id) {
      notificationId = id;
    });
  }

  chrome.notifications.onClicked.addListener(function() {
    showExpirePage(function(tab) {
      chrome.windows.update(tab.windowId, { focused: true });
    });
  });

  chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex) {
    self.focusNext() ? self.startFocus() : self.startBreak();
    chrome.notifications.clear(notificationId);
  });

  function createTimers() {
    self.getSettings(function(settings) {
      if (focusTimer) {
        focusTimer.stop();
      }

      focusTimer = createFocusTimer(settings);

      if (breakTimer) {
        breakTimer.stop();
      }

      breakTimer = createBreakTimer(settings);
    });
  }

  function createFocusTimer(settings) {
    var timer = new Timer(settings.focus.duration * 60, 60);
    BadgeObserver.observe(timer, 'Focus', '#990000');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.focus.sound);

    timer.addListener('expire', function() {
      focusNext = false;

      if (settings.focus.desktopNotification) {
        notify(
          'Take a break!',
          "Start your break when you're ready",
          'Start break now'
        );
      }

      if (settings.focus.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function createBreakTimer(settings) {
    var timer = new Timer(settings.break.duration * 60, 60);
    BadgeObserver.observe(timer, 'Break', '#009900');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.break.sound);

    timer.addListener('expire', function() {
      focusNext = true;

      if (settings.break.desktopNotification) {
        notify(
          'Break finished',
          "Start your focus session when you're ready",
          'Start focusing now'
        );
      }

      if (settings.break.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function closeExtensionTabs() {
    var id = chrome.runtime.id;
    var extensionUrl = 'chrome-extension://' + id;

    chrome.tabs.query({}, function(tabs) {
      var remove = [];
      for (var i = 0; i < tabs.length; ++i) {
        if (tabs[i].url.indexOf(extensionUrl) !== -1) {
          remove.push(tabs[i].id);
        }
      }

      chrome.tabs.remove(remove, function() { });
    });
  }

  function closeNotifications() {
    if (notificationId !== null) {
      chrome.notifications.clear(notificationId);
      notificationId = null;
    }
  }

  createTimers();
}

var controller = new Controller();

chrome.contextMenus.removeAll();

MenuEntry.create('Start focusing', controller.startFocus);
MenuEntry.create('Start break', controller.startBreak);
MenuEntry.create(null);

chrome.browserAction.onClicked.addListener(function() {
  controller.browserAction();
});

chrome.runtime.onMessage.addListener(function(request, sender, respond) {
  if (request.command === 'get-session') {
    if (controller.focusNext()) {
      controller.getSettings(function(settings) {
        respond({
          focusNext: true,
          title: 'Break finished',
          subtitle: "Start your " + settings.focus.duration + " minute focus session when you're ready",
          action: 'Start Focusing'
        });
      });
    } else {
      controller.getSettings(function(settings) {
        respond({
          focusNext: false,
          title: 'Take a break!',
          subtitle: "Start your " + settings.break.duration + " minute break when you're ready",
          action: 'Start Break'
        });
      });
    }
  } else if (request.command === 'start-session') {
    controller.startSession();
    respond({});
  } else if (request.command === 'get-sounds') {
    respond(sounds);
  } else if (request.command === 'get-settings') {
    controller.getSettings(respond);
  } else if (request.command === 'set-settings') {
    var newSettings = request.settings;
    var focusDuration = newSettings.focus.duration.trim();
    var breakDuration = newSettings.break.duration.trim();

    if (!focusDuration) {
      respond({ error: 'Focus duration is required.' });
      return true;
    } else if (!breakDuration) {
      respond({ error: 'Break duration is required.' });
      return true;
    }

    var focusParsed = +focusDuration;
    var breakParsed = +breakDuration;

    if (focusParsed <= 0 || isNaN(focusParsed)) {
      respond({ error: 'Focus duration must be a positive number.' });
      return true;
    } else if (breakParsed <= 0 || isNaN(breakParsed)) {
      respond({ error: 'Break duration must be a positive number.' });
      return true;
    }

    newSettings.focus.duration = focusParsed;
    newSettings.break.duration = breakParsed;

    controller.setSettings(newSettings, function() {
      respond({});
    });
  }

  return true;
});
<|endoftext|>


<commit_before>function FBBaseObject() {
    this.baseURL = "";
    this.fetched = false;
}

FBBaseObject.prototype.success = function(callback) {
    if (this.fetched) {
        callback();
    } else {
        // Call this thingie when fetch is complete
    }

    return this;
}
FBBaseObject.prototype.fail = function(callback) {
    // Call when failed
    return this;
}
FBBaseObject.prototype.finally = function(callback) {
    if (this.fetched) {
        callback();
    } else {
        // Call this thingie when fetch is done/undone
    }
    return this;
}
FBBaseObject.prototype.__fetch = function(setterCallback) {
    // Construct an FB API object to fetch data for this motherficker
    return this;
}

function FBImage(baseURL) {
    this.baseURL = baseURL;
}
FBImage.prototype = new FBBaseObject();
FBImage.prototype.__fetch = function() {
    var self = this;
    this.__proto__.__fetch(function(data) {
        // TODO: Make this work like it's done in the FB API
        self.url = data.url;
    });
    return this;
}

function FBUser(user) {
    // User can be one of the following:
    // - User ID :      123456789
    // - Facebook URL : https://facebook.com/user.name
    // - Empty :        uses the current, logged in user

    var fbuser_url_regexp = /^https?:\/\/(?:www\.)?facebook.com\/([\w]+)$/;
    if (typeof user === 'number') {
        this.baseURL = '/'+user.toString();

    } else if (fbuser_url_regexp.test(user) === true) {
        var username = fbuser_url_regexp.exec(user);
        // TODO: Resolve UserID from the username

    } else if (typeof user === 'undefined') {
        this.baseURL = '/me';

    }
}

FBUser.prototype = new FBBaseObject();
FBUser.prototype.__defineGetter__("image", function() {
    return new FBImage(this.baseURL + "/image");
});
FBUser.prototype.__defineGetter__("feed", function() {
    return new FBImage(this.baseURL + "/image");
});

// Test code to draft how the new API should work
var jesse = new FBUser(100000475343767);
jesse.image.success(function(image) {
    new Image(image.url);
});

<commit_msg>Added: More drafing<commit_after>function OOFB(API_KEY) {
    var FB = this;

    FB.BaseObject = function() {
        this.graphURL = '';
        this.fetched = false;
    }

    FB.BaseObject.prototype.success = function(callback) {
        if (this.fetched) {
            callback();
        } else {
            // Call this thingie when fetch is complete
        }

        return this;
    }
    FB.BaseObject.prototype.fail = function(callback) {
        // Call when failed
        return this;
    }
    FB.BaseObject.prototype.finally = function(callback) {
        if (this.fetched) {
            callback();
        } else {
            // Call this thingie when fetch is done/undone
        }
        return this;
    }
    FB.BaseObject.prototype.__fetch = function(setterCallback) {
        // Construct an FB API object to fetch data for this motherficker
        return this;
    }

    FB.Image = function(graphURL) {
        this.graphURL = graphURL;
    }
    FB.Image.prototype = new FB.BaseObject();
    FB.Image.prototype.__fetch = function() {
        var self = this;
        this.__proto__.__fetch(function(data) {
            // TODO: Make this work like it's done in the FB API
            self.url = data.url;
        });
        return this;
    }

    FB.User = function(user) {
        // User can be one of the following:
        // - User ID :      123456789
        // - Facebook URL : https://facebook.com/user.name
        // - Empty :        uses the current, logged in user

        var fbuser_url_regexp = /^https?:\/\/(?:www\.)?facebook.com\/([\w]+)$/;
        if (typeof user === 'number') {
            this.graphURL = '/'+user.toString();

        } else if (fbuser_url_regexp.test(user) === true) {
            var username = fbuser_url_regexp.exec(user);
            // TODO: Resolve UserID from the username

        } else if (typeof user === 'undefined') {
            this.graphURL = '/me';

        }
    }

    FB.User.prototype = new FB.BaseObject();
    FB.User.prototype.__defineGetter__('image', function() {
        return new FB.Image(this.graphURL + '/image');
    });
    FB.User.prototype.__defineGetter__('feed', function() {
        return new FB.Image(this.graphURL + '/image');
    });

    FB.login = function(callback) {
        var login = new FB.BaseObject();
        login.success(callback);
        callback(FB); // For debuggin'
        return login;
    }
    FB.logout = function(callback) {
        var logout = new FB.BaseObject();
        logout.success(callback);
        callback(FB); // For debuggin'
        return logout;
    }
}

var FB = new OOFB('API_KEY_HERE');

// Test code to draft how the new API should work
var jesse = new FB.User(100000475343767);
jesse.image.success(function(image) {
    new Image(image.url);
});

var FB = new OOFB('API_KEY_HERE');
FB.login(function(FB) {
    var me = new FB.User();
    console.log("Logged in:", me);
    FB.logout(function(FB) {
        console.log("Logged out");
    });
});<|endoftext|>


<commit_before>//--------------------------------------------------------------------------------------------------

var spawn = require('child_process').spawn;
var colors = require('colors/safe');
var async = require('async');

//--------------------------------------------------------------------------------------------------

var streem = function(message, color) {
    process.stdout.write(colors[color](message.toString()));
};

//--------------------------------------------------------------------------------------------------

var exec = function(args, callback) {
    var command = args.shift();
    var ch = spawn(command, args);

    streem(command, 'red');

    ch.stdout.on('data', function (data) {
        streem(data, 'green');
    });
    ch.stderr.on('data', function (data) {
        streem(data, 'red');
    });
    ch.on('close', function (code) {
        callback(null, command);
        console.log('close = '+ command + args);
    });
    ch.on('exit', function (code) {
        console.log('exit = '+ command + args);
    });
};

//--------------------------------------------------------------------------------------------------

var commands = [
    ['git', 'add', '.'],
    ['git', 'commit', '-m', '"update"'],
    ['git', 'push']
];
async.map([['clear']], exec, function(err, r) {
    async.map(commands, exec, function(err, r) {
    });
});

/*
async.series([
    function(callback) {
        exec('git', ['status'], callback);
    },
    function(callback) {
        exec('git', ['status'], callback);
    },
    function(callback) {
        exec('git', ['status'], callback);
    }
]);
*/
//--------------------------------------------------------------------------------------------------
/*
var colors = require('colors/safe');
const exec = require('child_process').exec;
*/
/*--------------------------------------------------------------------------------------------------
|
| -> Загрузка на GitHub
|
|-------------------------------------------------------------------------------------------------*/
/*
//exec('clear && git add . && git commit -m "update" && git push', function(error, stdout, stderr) {
exec('git', function(error, stdout, stderr) {
    if (error) {
        process.stdout.write(colors.red(error));
        return;
    }
    process.stdout.write(colors.green(stdout.toString()));
    process.stdout.write(colors.red(stderr.toString()));
});
*/
//--------------------------------------------------------------------------------------------------<commit_msg>"update"<commit_after>//--------------------------------------------------------------------------------------------------

var spawn = require('child_process').spawn;
var colors = require('colors/safe');
var async = require('async');

//--------------------------------------------------------------------------------------------------

var streem = function(message, color) {
    process.stdout.write(colors[color](message.toString()));
};

//--------------------------------------------------------------------------------------------------

var exec = function(args, callback) {
    var command = args.shift();
    var ch = spawn(command, args);

    streem(command, 'red');

    ch.stdout.on('data', function (data) {
        streem(data, 'green');
    });
    ch.stderr.on('data', function (data) {
        streem(data, 'red');
    });
    ch.on('close', function (code) {
        callback(null, command);
        console.log('close = '+ command + args);
    });
};

//--------------------------------------------------------------------------------------------------

var commands = [
    ['git', 'add', '.'],
    ['git', 'commit', '-m', '"update"'],
    ['git', 'push']
];
async.map([['clear']], exec, function(err, r) {
    async.map(commands, exec, function(err, r) {
    });
});

/*
async.series([
    function(callback) {
        exec('git', ['status'], callback);
    },
    function(callback) {
        exec('git', ['status'], callback);
    },
    function(callback) {
        exec('git', ['status'], callback);
    }
]);
*/
//--------------------------------------------------------------------------------------------------
/*
var colors = require('colors/safe');
const exec = require('child_process').exec;
*/
/*--------------------------------------------------------------------------------------------------
|
| -> Загрузка на GitHub
|
|-------------------------------------------------------------------------------------------------*/
/*
//exec('clear && git add . && git commit -m "update" && git push', function(error, stdout, stderr) {
exec('git', function(error, stdout, stderr) {
    if (error) {
        process.stdout.write(colors.red(error));
        return;
    }
    process.stdout.write(colors.green(stdout.toString()));
    process.stdout.write(colors.red(stderr.toString()));
});
*/
//--------------------------------------------------------------------------------------------------<|endoftext|>


<commit_before>var GENEMAP = GENEMAP || {};

//Produce layout for gene annotations
//GENEMAP.GeneClusterer is used to cluster genes if necessary
//Labella is used to generate layout of nodes

GENEMAP.GeneAnnotationLayout = function (userConfig) {

  var defaultConfig = {
    longestChromosome: 100,
    layout: {
      width: 10, //not used
      height: 100,
      x: 0, //not used
      y: 0, //not used
    },
    autoLabels : true,
    manualLabels : true,
    annotationMarkerSize: 5,
    annotationLabelSize: 5,
    doCluster : true,
    nClusters: 6,
    nGenesToDisplay: 1000,
    maxAnnotationLayers: 3,
    scale: 1,
  };

  var config = _.merge({}, defaultConfig, userConfig);

  var buildYScale = function () {
    return d3.scale.linear()
      .range([0, config.layout.height])
      .domain([0, config.longestChromosome]);
  };

  var shouldRecluster = function(nodes) {
    if (!config.doCluster) { return false;}
    var layers = nodes.map(function (d) {  return d.getLayerIndex(); } );
    var maxLayer = Math.max.apply(null, layers);
    return ( maxLayer > config.maxAnnotationLayers );
  }

  //Use labella to generate layout nodes for each gene
  //or cluster of genes
  var generateChromosomeLayout = function(chromosome){

    //How much space do we have?
    var availableWidth = config.layout.width;
    var availableHeight = config.layout.height;
    var labelLength = 20;
    var minDisplayedFontSize = 12;
    var maxDisplayedFontSize = 14;
    var fontCoordRatio = 3.5;

    //How many rows of labels do we show?

    //Can we show 1 row?
    var par1 =  {};
    par1.layerGap = availableWidth * ( 0.1 + 0.1 / config.scale )
    par1.spaceForLabel = availableWidth - par1.layerGap;
    par1.setFontSize = par1.spaceForLabel / labelLength * fontCoordRatio;
    par1.possible = par1.setFontSize * config.scale  > minDisplayedFontSize;
    par1.nodeSpacing = par1.setFontSize;
    par1.density = 1.0;

    //Can we show 2 rows?
    var par2 = {};
    par2.spaceForLabel = availableWidth / 3;
    par2.setFontSize = par2.spaceForLabel / labelLength * fontCoordRatio;
    par2.possible = par2.setFontSize *config.scale  > minDisplayedFontSize;

    par2.setFontSize = Math.min( par2.setFontSize, maxDisplayedFontSize / config.scale  ) ;
    par2.nodeSpacing = par2.setFontSize;
    par2.spaceForLabel = 1.3 * labelLength * par2.setFontSize / fontCoordRatio;
    par2.layerGap = Math.min(5*par2.setFontSize, availableWidth / 3);
    par2.density = 0.9;

    //var nRows = par1.possible + par2.possible;
    var nRows = par1.possible + par2.possible;
    var par = (nRows == 2) ? _.clone(par2) : _.clone(par1);
    par.setFontSize = Math.min( par.setFontSize, maxDisplayedFontSize / config.scale  ) ;
    par.nodeSpacing =  Math.max( 2, par.setFontSize );
    par.lineSpacing =  1;
    par.scale = config.scale;
    par.availableHeight = availableHeight;
    if ( nRows == 0 ) {
      par.nLabels = 0;
    } else if (nRows == 1 ){
      par.nLabels = 0.4  * availableHeight / (par.nodeSpacing + par.lineSpacing);
    } else if (nRows == 2 ){
    par.nLabels =  0.6 * availableHeight / (par.nodeSpacing + par.lineSpacing);
  }

    if ( false &&  chromosome.number == "2B")
    {
      log.info( "par1",  par1);
      log.info( "par2", par2);
      log.info( "par", par);
     };

    var y = buildYScale();

    forceConfig = {
      nodeSpacing: par.nodeSpacing,
      lineSpacing: par.lineSpacing,
      algorithm: 'overlap',
      minPos: 0,
      maxPos: par.availableHeight,
      density: par.density,
    };

    var force = new labella.Force( forceConfig );

    //Decide which labels to display

    //Start with no labels displayed
    allGenes = chromosome.annotations.allGenes.filter( function(gene){
     return (gene.globalIndex < config.nGenesToDisplay);
    });
    allGenes.forEach( function(gene){ gene.displayed = false}) ;

    //Include all genes set to visible
    var nodeSource = config.manualLabels
      ? new Set(allGenes.filter( function(gene){ return gene.visible}))
      : new Set();

    //Automatically show some additional labels
    if ( config.autoLabels){
    allGenes.slice(0, par.nLabels)
      .filter( function(gene){return !gene.hidden;})
      .forEach( function(gene){ nodeSource.add(gene)});
      }

    nodeSource = Array.from(nodeSource);
    nodeSource.forEach( function(gene){
      gene.displayed = true
      gene.fontSize = par.setFontSize;
    });

    var nodes = nodeSource.map(function (d) {
      return new labella.Node(y(d.midpoint), par.setFontSize, d);
    } );

    try {
      force.nodes(nodes).compute();
    } catch (e){
      if ( e instanceof RangeError){
        log.error(e.message);
        log.info(chromosome.number);
        log.info(par);
        return null;
      }
      throw e;
    }

    //If clustering is enabled we might want to redo the layout using clusters of genes
    if( false && shouldRecluster(force.nodes()) ){
      nodeSource = chromosome.layout.annotationDisplayClusters;

      nodes = nodeSource.map(function (d) {
        return new labella.Node(y(d.midpoint), setFontSize, d);
      } );
      force.nodes(nodes).compute();
    }

    //Compute paths
    renderConfig  = {
      direction: 'right',
      layerGap: par.layerGap,
      nodeHeight: par.spaceForLabel,
    };

    var renderer = new labella.Renderer(renderConfig);
    renderer.layout(nodes);

    nodes.forEach( function(node){
     node.data.path = renderer.generatePath(node);
    });

    if ( false &&  chromosome.number == "2B") {
      log.info( nodes );
    }

    return nodes;

  }

  //Produce list of clusters (which could be single genes)
  //for a given chromosome
  var generateChromosomeClusters = function(chromosome){
    var geneClusterer = GENEMAP.GeneClusterer();
    //Run clustering algorithm so we can use the clusters later when drawing
    genes = chromosome.annotations.genes;
    geneClusters = geneClusterer.createClustersFromGenes(genes);
    return geneClusters;
  }

  my = {};

  my.layoutChromosome = function(chromosome){
    chromosome.layout.annotationNodes = (
    generateChromosomeLayout(chromosome) || chromosome.layout.annotationNodes);
  }

  my.computeChromosomeClusters = function(chromosome){
    chromosome.layout.annotationClusters = generateChromosomeClusters(chromosome);
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();
  };

  my.expandAllChromosomeClusters = function(chromosome) {
    chromosome.layout.annotationDisplayClusters = chromosome.annotations.genes;
  };

  my.collapseAllChromosomeClusters = function(chromosome) {
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();
  };

  my.expandAChromosomeCluster= function( chromosome, cluster) {
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();

    //add each gene as it's own cluster
    cluster.genesList.forEach( function(gene){
      chromosome.layout.annotationDisplayClusters.push(gene) ;
    } );

    //delete the original cluster
    var clusterIndex = chromosome.layout.annotationDisplayClusters.indexOf(cluster);
    chromosome.layout.annotationDisplayClusters.splice(clusterIndex, 1);
  };

  return my;
}
<commit_msg>Restrict labels not to display too far beyond the end of chromsomes<commit_after>var GENEMAP = GENEMAP || {};

//Produce layout for gene annotations
//GENEMAP.GeneClusterer is used to cluster genes if necessary
//Labella is used to generate layout of nodes

GENEMAP.GeneAnnotationLayout = function (userConfig) {

  var defaultConfig = {
    longestChromosome: 100,
    layout: {
      width: 10, //not used
      height: 100,
      x: 0, //not used
      y: 0, //not used
    },
    autoLabels : true,
    manualLabels : true,
    annotationMarkerSize: 5,
    annotationLabelSize: 5,
    doCluster : true,
    nClusters: 6,
    nGenesToDisplay: 1000,
    maxAnnotationLayers: 3,
    scale: 1,
  };

  var config = _.merge({}, defaultConfig, userConfig);

  var buildYScale = function () {
    return d3.scale.linear()
      .range([0, config.layout.height])
      .domain([0, config.longestChromosome]);
  };

  var shouldRecluster = function(nodes) {
    if (!config.doCluster) { return false;}
    var layers = nodes.map(function (d) {  return d.getLayerIndex(); } );
    var maxLayer = Math.max.apply(null, layers);
    return ( maxLayer > config.maxAnnotationLayers );
  }

  //Use labella to generate layout nodes for each gene
  //or cluster of genes
  var generateChromosomeLayout = function(chromosome){

    allGenes = chromosome.annotations.allGenes.filter( function(gene){
      return (gene.globalIndex < config.nGenesToDisplay);
    });

    //How much space do we have?
    var availableWidth = config.layout.width;

    //For short chromosomes, allow labels to use up to 20% past the end of the chromosome
    var availableHeight = config.layout.height * ( Math.min( 1, 0.2 + chromosome.length / config.longestChromosome ) ) ;

    //The longest label determines when we can start displaying them without overlaps
    var labelLength = allGenes.reduce( function(cur, gene){return Math.max(cur, gene.label.length)}, 0);

    var minDisplayedFontSize = 12;
    var maxDisplayedFontSize = 14;
    var fontCoordRatio = 3.5;

    //How many rows of labels do we show?

    //Can we show 1 row?
    var par1 =  {};
    par1.layerGap = availableWidth * ( 0.1 + 0.1 / config.scale )
    par1.spaceForLabel = availableWidth - par1.layerGap;
    par1.setFontSize = par1.spaceForLabel / labelLength * fontCoordRatio;
    par1.possible = par1.setFontSize * config.scale  > minDisplayedFontSize;
    par1.nodeSpacing = par1.setFontSize;
    par1.density = 1.0;

    //Can we show 2 rows?
    var par2 = {};
    par2.spaceForLabel = availableWidth / 3;
    par2.setFontSize = par2.spaceForLabel / labelLength * fontCoordRatio;
    par2.possible = par2.setFontSize *config.scale  > minDisplayedFontSize;

    par2.setFontSize = Math.min( par2.setFontSize, maxDisplayedFontSize / config.scale  ) ;
    par2.nodeSpacing = par2.setFontSize;
    par2.spaceForLabel = 1.3 * labelLength * par2.setFontSize / fontCoordRatio;
    par2.layerGap = Math.min(5*par2.setFontSize, availableWidth / 3);
    par2.density = 0.9;

    //var nRows = par1.possible + par2.possible;
    var nRows = par1.possible + par2.possible;
    var par = (nRows == 2) ? _.clone(par2) : _.clone(par1);
    par.setFontSize = Math.min( par.setFontSize, maxDisplayedFontSize / config.scale  ) ;
    par.nodeSpacing =  Math.max( 2, par.setFontSize );
    par.lineSpacing =  1;
    par.scale = config.scale;
    par.availableHeight = availableHeight;
    if ( nRows == 0 ) {
      par.nLabels = 0;
    } else if (nRows == 1 ){
      par.nLabels = 0.4  * availableHeight / (par.nodeSpacing + par.lineSpacing);
    } else if (nRows == 2 ){
    par.nLabels =  0.6 * availableHeight / (par.nodeSpacing + par.lineSpacing);
  }

    if ( false &&  chromosome.number == "2B")
    {
      log.info( "par1",  par1);
      log.info( "par2", par2);
      log.info( "par", par);
     };

    var y = buildYScale();

    forceConfig = {
      nodeSpacing: par.nodeSpacing,
      lineSpacing: par.lineSpacing,
      algorithm: 'overlap',
      minPos: 0,
      maxPos: par.availableHeight,
      density: par.density,
    };

    var force = new labella.Force( forceConfig );

    //Decide which labels to display

    //Start with no labels displayed
    allGenes.forEach( function(gene){ gene.displayed = false}) ;

    //Include all genes set to visible
    var nodeSource = config.manualLabels
      ? new Set(allGenes.filter( function(gene){ return gene.visible}))
      : new Set();

    //Automatically show some additional labels
    if ( config.autoLabels){
    allGenes.slice(0, par.nLabels)
      .filter( function(gene){return !gene.hidden;})
      .forEach( function(gene){ nodeSource.add(gene)});
      }

    nodeSource = Array.from(nodeSource);
    nodeSource.forEach( function(gene){
      gene.displayed = true
      gene.fontSize = par.setFontSize;
    });

    var nodes = nodeSource.map(function (d) {
      return new labella.Node(y(d.midpoint), par.setFontSize, d);
    } );

    try {
      force.nodes(nodes).compute();
    } catch (e){
      if ( e instanceof RangeError){
        log.error(e.message);
        log.info(chromosome.number);
        log.info(par);
        return null;
      }
      throw e;
    }

    //If clustering is enabled we might want to redo the layout using clusters of genes
    if( false && shouldRecluster(force.nodes()) ){
      nodeSource = chromosome.layout.annotationDisplayClusters;

      nodes = nodeSource.map(function (d) {
        return new labella.Node(y(d.midpoint), setFontSize, d);
      } );
      force.nodes(nodes).compute();
    }

    //Compute paths
    renderConfig  = {
      direction: 'right',
      layerGap: par.layerGap,
      nodeHeight: par.spaceForLabel,
    };

    var renderer = new labella.Renderer(renderConfig);
    renderer.layout(nodes);

    nodes.forEach( function(node){
     node.data.path = renderer.generatePath(node);
    });

    if ( false &&  chromosome.number == "2B") {
      log.info( nodes );
    }

    return nodes;

  }

  //Produce list of clusters (which could be single genes)
  //for a given chromosome
  var generateChromosomeClusters = function(chromosome){
    var geneClusterer = GENEMAP.GeneClusterer();
    //Run clustering algorithm so we can use the clusters later when drawing
    genes = chromosome.annotations.genes;
    geneClusters = geneClusterer.createClustersFromGenes(genes);
    return geneClusters;
  }

  my = {};

  my.layoutChromosome = function(chromosome){
    chromosome.layout.annotationNodes = (
    generateChromosomeLayout(chromosome) || chromosome.layout.annotationNodes);
  }

  my.computeChromosomeClusters = function(chromosome){
    chromosome.layout.annotationClusters = generateChromosomeClusters(chromosome);
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();
  };

  my.expandAllChromosomeClusters = function(chromosome) {
    chromosome.layout.annotationDisplayClusters = chromosome.annotations.genes;
  };

  my.collapseAllChromosomeClusters = function(chromosome) {
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();
  };

  my.expandAChromosomeCluster= function( chromosome, cluster) {
    chromosome.layout.annotationDisplayClusters = chromosome.layout.annotationClusters.slice();

    //add each gene as it's own cluster
    cluster.genesList.forEach( function(gene){
      chromosome.layout.annotationDisplayClusters.push(gene) ;
    } );

    //delete the original cluster
    var clusterIndex = chromosome.layout.annotationDisplayClusters.indexOf(cluster);
    chromosome.layout.annotationDisplayClusters.splice(clusterIndex, 1);
  };

  return my;
}
<|endoftext|>


<commit_before>/**
 * Copyright 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see distribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.Log',
    dependencies: ['og.common.gadgets.manager'],
    obj: function () {
        var loading_template;
        return function (config) {
            var gadget = this, alive = og.common.id('gadget_log'), $selector = $(config.selector),
                logger, instantiated, template,
                cell_options = {source: config.source, col: config.col, row: config.row, format: 'EXPANDED', log: true},
                css_position = {position: 'absolute', top: '0', left: 0, right: 0, bottom: 0};
            gadget.alive = function () {return $selector.length ? true : (gadget.die(), false);};
            gadget.load = function () {
                $selector.addClass(alive).css(css_position).html(loading_template({text: 'loading...'}));
                gadget.dataman = new og.analytics.Cell(cell_options, 'data').on('data', function (cell) {
                    gadget.data = cell.logOutput;
                    if (gadget.data) {
                        if (!instantiated) $.when(og.api.text({module: 'og.analytics.logger'})).then(function (tmpl) {
                            template = Handlebars.compile(tmpl);
                            instantiated = true;
                            gadget.update();
                        });
                        else gadget.update();
                    } else {
                        instantiated = gadget.die() && null;
                        $selector.html('No log information available');
                    }
                });
            };
            gadget.die = function () {try {gadget.dataman.kill();} catch (error) {}};
            gadget.update = function () {if (template) $selector.html(template({obj: gadget.data}));};
            gadget.resize = function () {gadget.update();};
            if (loading_template) gadget.load(); else og.api.text({module: 'og.analytics.loading_tash'})
                .pipe(function (template) {loading_template = Handlebars.compile(template); gadget.load();});
            if (!config.child) og.common.gadgets.manager.register(gadget);
        };
    }
});<commit_msg>bugfix in log gadget (prematurely killing connection)<commit_after>/**
 * Copyright 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see distribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.Log',
    dependencies: ['og.common.gadgets.manager'],
    obj: function () {
        var loading_template;
        return function (config) {
            var gadget = this, alive = og.common.id('gadget_log'), $selector = $(config.selector),
                logger, instantiated, template,
                cell_options = {source: config.source, col: config.col, row: config.row, format: 'EXPANDED', log: true},
                css_position = {position: 'absolute', top: '0', left: 0, right: 0, bottom: 0};
            gadget.alive = function () {return $(config.selector).length ? true : (gadget.die(), false);};
            gadget.load = function () {
                $selector.addClass(alive).css(css_position).html(loading_template({text: 'loading...'}));
                gadget.dataman = new og.analytics.Cell(cell_options, 'log')
                .on('data', function (cell) {
                    gadget.data = cell.logOutput;
                    if (gadget.data) {
                        if (!instantiated) $.when(og.api.text({module: 'og.analytics.logger'})).then(function (tmpl) {
                            template = Handlebars.compile(tmpl);
                            instantiated = true;
                            gadget.update();
                        });
                        else gadget.update();
                    } else {
                        instantiated = null;
                        $selector.html('No log information available');
                    }
                });
            };
            gadget.die = function () {try {gadget.dataman.kill();} catch (error) {}};
            gadget.update = function () {if (template) $selector.html(template({obj: gadget.data}));};
            gadget.resize = function () {gadget.update();};
            if (loading_template) gadget.load(); else og.api.text({module: 'og.analytics.loading_tash'})
                .pipe(function (template) {loading_template = Handlebars.compile(template); gadget.load();});
            if (!config.child) og.common.gadgets.manager.register(gadget);
        };
    }
});<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Ember from 'ember';

export default Ember.Route.extend({

  model: function() {
    return Ember.Object.create({
      hdfsTest: null,
      hdfsTestDone: null,
      hiveserverTest: null,
      hiveserverTestDone: null,
      atsTest: null,
      atsTestDone: null,
      userhomeTest: null,
      userhomeTestDone: null,
      percent: 0,
      numberOfChecks: null,
      serviceCheckPolicy: null,
    });
  },

  setupController: function(controller, model) {

    if (!model) {
      return;
    }

    controller.set('model', model);
    var self = this;

    function loadView(){
      if (model.get("hiveserverTest")
        && model.get("hdfsTest")
        && model.get("atsTest")
        && model.get("userhomeTest")) {
        Ember.run.later(this, function() {
          self.send('transition');
        }, 2000);
      }

    }

    function checkHive() {
      controller.checkConnection().then(function () {
        var percent = model.get('percent');
        model.set("hiveserverTest", true);
        model.set("hiveserver" + 'TestDone', true);
        model.set('percent', percent + (100/model.get("numberOfChecks")));
        loadView();
      }, function () {
        if (model.get('ldapFailure')) {
          var percent = model.get('percent');
          controller.requestLdapPassword(function () {
            // check the connection again
            controller.checkConnection().then(function () {
              model.set("hiveserverTest", true);
              model.set("hiveserver" + 'TestDone', true);
              model.set('percent', percent + (100/model.get("numberOfChecks")));
              loadView();
            }, function () {
              var percent = model.get('percent');
              var checkFailedMessage = "Hive authentication failed";
              var errors = controller.get("errors");
              errors += checkFailedMessage;
              errors += '
';
              controller.set("errors", errors);
              model.get("hiveserverTest", false);
              model.set("hiveserver" + 'TestDone', true);
              model.set('percent', percent + (100/model.get("numberOfChecks")));
              loadView();
            });
          });
        } else {
          model.get("hiveserverTest", false);
          model.set("hiveserver" + 'TestDone', true);
          model.set('percent', model.get('percent') + (100/model.get("numberOfChecks")));
          loadView();
        }
      });
    }

    this.fetchServiceCheckPolicy()
      .then((data) => {
        var numberOfChecks = 0;
        var serviceCheckPolicy = data.serviceCheckPolicy;
        for (var serviceCheck in serviceCheckPolicy) {
          if (serviceCheckPolicy[serviceCheck] === true) {
            numberOfChecks++;
          }
        }
        model.set("numberOfChecks", numberOfChecks);
        model.set("serviceCheckPolicy", serviceCheckPolicy);
        controller.startTests().then(function () {
          if(serviceCheckPolicy.checkHive === true){
            checkHive();
          }else{
            model.set("hiveserver" + 'TestDone', true);
            model.set("hiveserver" + 'Test', true);
            loadView();
          }
        });
      });
  },

  fetchServiceCheckPolicy: function(){
    var adapter = this.container.lookup('adapter:service-check');
    return adapter.fetchServiceCheckPolicy();
  },
  actions: {
    transition: function() {
      this.transitionTo('index');
    }
  }

});
<commit_msg>AMBARI-21001: Hive 1.5.0 view does not load in Internet Explorer 11 (sangeetar)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Ember from 'ember';

export default Ember.Route.extend({

  model: function() {
    return Ember.Object.create({
      hdfsTest: null,
      hdfsTestDone: null,
      hiveserverTest: null,
      hiveserverTestDone: null,
      atsTest: null,
      atsTestDone: null,
      userhomeTest: null,
      userhomeTestDone: null,
      percent: 0,
      numberOfChecks: null,
      serviceCheckPolicy: null,
    });
  },

  setupController: function(controller, model) {

    if (!model) {
      return;
    }

    controller.set('model', model);
    var self = this;

    function loadView(){
      if (model.get("hiveserverTest")
        && model.get("hdfsTest")
        && model.get("atsTest")
        && model.get("userhomeTest")) {
        Ember.run.later(this, function() {
          self.send('transition');
        }, 2000);
      }

    }

    function checkHive() {
      controller.checkConnection().then(function () {
        var percent = model.get('percent');
        model.set("hiveserverTest", true);
        model.set("hiveserver" + 'TestDone', true);
        model.set('percent', percent + (100/model.get("numberOfChecks")));
        loadView();
      }, function () {
        if (model.get('ldapFailure')) {
          var percent = model.get('percent');
          controller.requestLdapPassword(function () {
            // check the connection again
            controller.checkConnection().then(function () {
              model.set("hiveserverTest", true);
              model.set("hiveserver" + 'TestDone', true);
              model.set('percent', percent + (100/model.get("numberOfChecks")));
              loadView();
            }, function () {
              var percent = model.get('percent');
              var checkFailedMessage = "Hive authentication failed";
              var errors = controller.get("errors");
              errors += checkFailedMessage;
              errors += '
';
              controller.set("errors", errors);
              model.get("hiveserverTest", false);
              model.set("hiveserver" + 'TestDone', true);
              model.set('percent', percent + (100/model.get("numberOfChecks")));
              loadView();
            });
          });
        } else {
          model.get("hiveserverTest", false);
          model.set("hiveserver" + 'TestDone', true);
          model.set('percent', model.get('percent') + (100/model.get("numberOfChecks")));
          loadView();
        }
      });
    }

    this.fetchServiceCheckPolicy()
      .then (function(data) {
        var numberOfChecks = 0;
        var serviceCheckPolicy = data.serviceCheckPolicy;
        for (var serviceCheck in serviceCheckPolicy) {
          if (serviceCheckPolicy[serviceCheck] === true) {
            numberOfChecks++;
          }
        }
        model.set("numberOfChecks", numberOfChecks);
        model.set("serviceCheckPolicy", serviceCheckPolicy);
        controller.startTests().then(function () {
          if(serviceCheckPolicy.checkHive === true){
            checkHive();
          }else{
            model.set("hiveserver" + 'TestDone', true);
            model.set("hiveserver" + 'Test', true);
            loadView();
          }
        });
      });
  },

  fetchServiceCheckPolicy: function(){
    var adapter = this.container.lookup('adapter:service-check');
    return adapter.fetchServiceCheckPolicy();
  },
  actions: {
    transition: function() {
      this.transitionTo('index');
    }
  }

});
<|endoftext|>


<commit_before>// copied and modified from https://gist.github.com/dsully/1938283
$(function () {

    function loadPage(url) {
        window.location.href = url;
    }

    // Handle tabs, page reloads & browser forward/back history.
    var History = window.History;

    if (!History.enabled) {
        return false;
    }

    var statechange = function () {
        var State = History.getState();
        var hash  = History.getHash();

        // Our default tab.
        if (!State.data || !State.data.tab) {
            if (hash) {
                State.data.tab = hash;
                window.location.hash = '';
            } else {
                State.data.tab = '';
            }
        }

        var link;
        if (State.data.tab) {
            link = $('a[data-toggle="tab"][href$="#' + State.data.tab + '"]');
        } else {
            // if you can't find it by hash, try matching the url
            // this is for first loading a page,
            // where State.data.tab won't be available
            link = $('a[data-toggle="tab"][href^="' + window.location.pathname + '"]');
            if (link.length !== 0) {
                if (link.length > 1) {
                    var defaultLink = link.filter("[data-default='1']");
                    if (defaultLink.length) {
                        link = defaultLink;
                    }
                }
                link = link.first();
                History.replaceState({
                    tab: link.attr('href').split('#')[1]
                }, null, State.url);
            }
        }
        link.tab('show');
    };
    $(window).bind('load', statechange);
    History.Adapter.bind(window, 'statechange', statechange);
    History.Adapter.bind(window, 'statechange', function () {
        var State = History.getState();
        if (!State.data || !State.data.tab) {
            // just go to the url specified with a reload
            loadPage(window.location.href);
        }
    });

    $('a[data-toggle="tab"]').on('show show.bs.tab', function (event) {

        // Set the selected tab to be the current state. But don't update the URL.
        var url = event.target.href.split("#")[0];
        var tab = event.target.href.split("#")[1];

        var State = History.getState();

        if (url === window.location.href) {
            // for tabs that we don't want to change the url on
            return;
        }

        if ($('#' + tab).length === 0) {
            loadPage(url);
        }

        // Don't set the state if we haven't changed tabs.
        if (State.data.tab != tab) {
            var message = COMMCAREHQ.beforeUnloadCallback();
            var ask = message !== undefined && message !== undefined;
            if (!ask) {
                History.pushState({'tab': tab}, null, url);
            } else {
                // instead of using tabs, reload page
                // so as to trigger warning
                event.preventDefault();
                loadPage(url);
            }
        }
    });
});
<commit_msg>add comment<commit_after>// copied and modified from https://gist.github.com/dsully/1938283
$(function () {

    function loadPage(url) {
        window.location.href = url;
    }

    // Handle tabs, page reloads & browser forward/back history.
    var History = window.History;

    if (!History.enabled) {
        return false;
    }

    var statechange = function () {
        var State = History.getState();
        var hash  = History.getHash();

        // Our default tab.
        if (!State.data || !State.data.tab) {
            if (hash) {
                State.data.tab = hash;
                window.location.hash = '';
            } else {
                State.data.tab = '';
            }
        }

        var link;
        if (State.data.tab) {
            link = $('a[data-toggle="tab"][href$="#' + State.data.tab + '"]');
        } else {
            // if you can't find it by hash, try matching the url
            // this is for first loading a page,
            // where State.data.tab won't be available
            link = $('a[data-toggle="tab"][href^="' + window.location.pathname + '"]');
            if (link.length !== 0) {
                // If multiple links match the current path - likely because this is the inital page load,
                // so the pathname, foo/bar/, will match all links (since they're of the form foo/bar/#baz,
                // foo/bar/#zap, etc.) - look for any link that has been marked as the default landing page.
                // If there's no default set, ultimately we'll fall back to whatever the first link is.
                if (link.length > 1) {
                    var defaultLink = link.filter("[data-default='1']");
                    if (defaultLink.length) {
                        link = defaultLink;
                    }
                }
                link = link.first();
                History.replaceState({
                    tab: link.attr('href').split('#')[1]
                }, null, State.url);
            }
        }
        link.tab('show');
    };
    $(window).bind('load', statechange);
    History.Adapter.bind(window, 'statechange', statechange);
    History.Adapter.bind(window, 'statechange', function () {
        var State = History.getState();
        if (!State.data || !State.data.tab) {
            // just go to the url specified with a reload
            loadPage(window.location.href);
        }
    });

    $('a[data-toggle="tab"]').on('show show.bs.tab', function (event) {

        // Set the selected tab to be the current state. But don't update the URL.
        var url = event.target.href.split("#")[0];
        var tab = event.target.href.split("#")[1];

        var State = History.getState();

        if (url === window.location.href) {
            // for tabs that we don't want to change the url on
            return;
        }

        if ($('#' + tab).length === 0) {
            loadPage(url);
        }

        // Don't set the state if we haven't changed tabs.
        if (State.data.tab != tab) {
            var message = COMMCAREHQ.beforeUnloadCallback();
            var ask = message !== undefined && message !== undefined;
            if (!ask) {
                History.pushState({'tab': tab}, null, url);
            } else {
                // instead of using tabs, reload page
                // so as to trigger warning
                event.preventDefault();
                loadPage(url);
            }
        }
    });
});
<|endoftext|>