<commit_before>/* eslint-env browser */
'use strict'

let d3 = require('d3'),
	apijs = require('tnt.api'),
	popforms = require('./popforms.js'),
	treeOperations = require('./treeOperations.js')

let tooltip = function () {
	let drag = d3.drag()
	let tooltip_div

	let conf = {
		container: undefined,
		position : 'right',
		allow_drag : true,
		show_closer : true,
		fill : function () { throw 'fill is not defined in the base object' },
		width : 180,
		id : 1
	}

	let t = function (data, event) {
		drag
			.subject(function() {
				return {
					x : parseInt(d3.select(this).style('left')),
					y : parseInt(d3.select(this).style('top'))
				}
			})
			.on('drag', function() {
				if (conf.allow_drag) {
					d3.select(this)
						.style('left', d3.event.x + 'px')
						.style('top', d3.event.y + 'px')
				}
			})

		// TODO: Why do we need the div element?
		// It looks like if we anchor the tooltip in the 'body'
		// The tooltip is not located in the right place (appears at the bottom)
		// See clients/tooltips_test.html for an example
		let containerElem = conf.container
		if (!containerElem) {
			containerElem = selectAncestor(this, 'div')
			if (containerElem === undefined) {
				// We require a div element at some point to anchor the tooltip
				return
			}
		}

		tooltip_div = d3.select(containerElem)
			.append('div')
			.attr('class', 'tnt_tooltip')
			.classed('tnt_tooltip_active', true)  // TODO: Is this needed/used???
			.call(drag)

		// prev tooltips with the same header
		d3.select('#tnt_tooltip_' + conf.id).remove()

		if ((d3.event === null) && (event)) {
			d3.event = event
		}
		let d3mouse = d3.mouse(containerElem)
		d3.event = null

		let xoffset = -10
		if (conf.position === 'left') {
			xoffset = conf.width
		}

		tooltip_div.attr('id', 'tnt_tooltip_' + conf.id)

		// We place the tooltip
		tooltip_div
			.style('left', (d3mouse[0] - xoffset) + 'px')
			.style('top', (d3mouse[1]) + 'px')

		// Close
		if (conf.show_closer) {
			tooltip_div
				.append('div')
				.attr('class', 'tnt_tooltip_closer')
				.on ('click', function () {
					t.close()
				})
		}

		conf.fill.call(tooltip_div.node(), data)

		// return this here?
		return t
	}

	// gets the first ancestor of elem having tagname 'type'
	// example : let mydiv = selectAncestor(myelem, 'div')
	function selectAncestor (elem, type) {
		type = type.toLowerCase()
		if (elem.parentNode === null) {
			console.log('No more parents')
			return undefined
		}
		let tagName = elem.parentNode.tagName

		if ((tagName !== undefined) && (tagName.toLowerCase() === type)) {
			return elem.parentNode
		} else {
			return selectAncestor (elem.parentNode, type)
		}
	}

	let api = apijs(t)
		.getset(conf)

	api.check('position', function (val) {
		return (val === 'left') || (val === 'right')
	}, 'Only \'left\' or \'right\' values are allowed for position')

	api.method('close', function () {
		if (tooltip_div) {
			tooltip_div.remove()
		}
	})

	return t
}

tooltip.list = function () {
	// list tooltip is based on general tooltips
	let t = tooltip()
	let width = 180

	t.fill (function (obj) {
		let tooltip_div = d3.select(this)
		let obj_info_list = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			//.style('width', t.width() + 'px')

		// Tooltip header
		if (obj.header) {
			obj_info_list
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.text(obj.header)
		}

		// Tooltip rows
		let table_rows = obj_info_list.selectAll('.tnt_zmenu_row')
			.data(obj.rows)
			.enter()
			.append('tr')
			.attr('class', 'tnt_zmenu_row')

		table_rows
			.append('td')
			.style('text-align', 'center')
			.html(function(d,i) {
				return obj.rows[i].value
			})
			.each(function (d) {
				if (d.link === undefined) {
					return
				}
				d3.select(this)
					.classed('link', 1)
					.on('click', function (d) {
						d.link(d.obj)
						t.close.call(this)
					})
			})
	})
	return t
}

tooltip.table = function(fullTree, selectedNode) {
	// fills the selected node -- KEEP WORKING HERE
	let id = '#tnt_tree_node_treeBox_' + selectedNode.id()
	let collapsedText = ''
	d3.select(id)
		.select('.tnt_node_display_elem')
		.attr('fill', '#FF6A13')

	// table tooltips are based on general tooltips
	let t = tooltip()

	let width = 180

	t.fill (function (obj) {
		let tooltip_div = d3.select(this)

		let obj_info_table = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			//.style('width', t.width() + 'px')

		// Tooltip header
		if (obj.header) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.attr('colspan', 2)
				.text(obj.header)
				.style('text-align', 'center')
		}

		/* let table_rows = obj_info_table.selectAll('.tnt_zmenu_row')
			.data(obj.rows)
			.enter()
			.append('tr')
			.attr('class', 'tnt_zmenu_row')

		table_rows
			.append('th')
			.style('text-align', 'center')
			.attr('colspan', function (d, i) {
				if (d.value === '') {
					return 2
				}
				return 1
			})
			.attr('class', function (d) {
				if (d.value === '') {
					return 'tnt_zmenu_inner_header'
				}
				return 'tnt_zmenu_cell'
			})
			.html(function(d,i) {
				return obj.rows[i].label
			})

		table_rows
			.append('td')
			.style('text-align', 'center')
			.html(function(d, i) {
				if (typeof obj.rows[i].value === 'function')
					obj.rows[i].value.call(this, d)
				return obj.rows[i].value
			})
			.each(function (d) {
				if (d.value === '')
					d3.select(this).remove()
			})
			.each(function (d) {
				if (d.link === undefined) {
					return
				}
				d3.select(this)
					.classed('link', 1)
					.on('click', function (f) {
						f.link(f.obj)
						t.close.call(this)
					})
			}) */

		// THIS IS WHERE TOOLTIP ADDITIONS ARE MADE
		let changeBranchColorClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Change Branch Color')
			.on('click', function() {
				let nodeID = selectedNode.id()
				let numChildren = selectedNode.get_all_nodes().length - 1
				if (document.getElementById('colorPicker')) {
					if (document.getElementById('colorPicker').style.display === 'none')
						popforms.popColorPicker(selectedNode)
					else
						document.getElementById('colorPicker').style.display = 'none'
				}
				else {
					popforms.popColorPicker(selectedNode)
				}
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		changeBranchColorClickable.on('mouseover', function() {changeBranchColorClickable.style('color', '#3287d7')})
		changeBranchColorClickable.on('mouseout', function() {changeBranchColorClickable.style('color', 'black')})

		let changeBranchWidthClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Change Branch Width')
			.on('click', function() {
				if (document.getElementById('branchWidthInput')) {
					if (document.getElementById('branchWidthInput').style.display === 'none')
						popforms.popFormBranchWidth(selectedNode)
					else
						document.getElementById('branchWidthInput').style.display = 'none'
				}
				else {
					popforms.popFormBranchWidth(selectedNode)
				}
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		changeBranchWidthClickable.on('mouseover', function() {changeBranchWidthClickable.style('color', '#3287d7')})
		changeBranchWidthClickable.on('mouseout', function() {changeBranchWidthClickable.style('color', 'black')})

		if (selectedNode.is_collapsed())
			collapsedText = 'Uncollapse Node'
		else
			collapsedText = 'Collapse Node'

		let toggleClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text(collapsedText)
			.on('click', function() {
				treeOperations.toggleNode(selectedNode) // Toggles the collapsed property in the treeObj
				treeOperations.updateUserChanges(fullTree) // Updates the collapsing/uncollapsing in the SVG visualization.
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		toggleClickable.on('mouseover', function() {toggleClickable.style('color', '#3287d7')})
		toggleClickable.on('mouseout', function() {toggleClickable.style('color', 'black')})

		let toggleCertainty = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Toggle Certainty')
			.on('click', function() {
				treeOperations.changeCertaintyProperty(selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		toggleCertainty.on('mouseover', function() {toggleCertainty.style('color', '#3287d7')})
		toggleCertainty.on('mouseout', function() {toggleCertainty.style('color', 'black')})

		let ladderizeSubtree = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Ladderize Subtree')
			.on('click', function() {
				treeOperations.ladderizeSubtree(selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		ladderizeSubtree.on('mouseover', function() {ladderizeSubtree.style('color', '#3287d7')})
		ladderizeSubtree.on('mouseout', function() {ladderizeSubtree.style('color', 'black')})

		let reRootClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Set as Root')
			.style('text-align', 'center')
			.on('click', function() {
				treeOperations.rerootTree(fullTree, selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
		reRootClickable.on('mouseover', function() {reRootClickable.style('color', '#3287d7')})
		reRootClickable.on('mouseout', function() {reRootClickable.style('color', 'black')})

		let closeClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Close')
			.on('click', function() {
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		closeClickable.on('mouseover', function() {closeClickable.style('color', '#3287d7')})
		closeClickable.on('mouseout', function() {closeClickable.style('color', 'black')})
	})

	return t
}

tooltip.plain = function () {
	// plain tooltips are based on general tooltips
	let t = tooltip()

	t.fill (function (obj) {
		let tooltip_div = d3.select(this)

		let obj_info_table = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			//DEFAULT BY TNT.style('width', t.width() + 'px')

		if (obj.header) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.text(obj.header)
		}

		if (obj.body) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_row')
				.append('td')
				.style('text-align', 'center')
				.html(obj.body)
		}
	})

	return t
}

module.exports = exports = tooltip
<commit_msg>linting<commit_after>/* eslint-env browser */
'use strict'

let d3 = require('d3'),
	apijs = require('tnt.api'),
	popforms = require('./popforms.js'),
	treeOperations = require('./treeOperations.js')

let tooltip = function() {
	let drag = d3.drag()
	let tooltip_div

	let conf = {
		container: undefined,
		position: 'right',
		allow_drag: true,
		show_closer: true,
		fill: function() { throw 'fill is not defined in the base object' },
		width: 180,
		id: 1
	}

	let t = function(data, event) {
		drag
			.subject(function() {
				return {
					x: parseInt(d3.select(this).style('left')),
					y: parseInt(d3.select(this).style('top'))
				}
			})
			.on('drag', function() {
				if (conf.allow_drag) {
					d3.select(this)
						.style('left', d3.event.x + 'px')
						.style('top', d3.event.y + 'px')
				}
			})

		// TODO: Why do we need the div element?
		// It looks like if we anchor the tooltip in the 'body'
		// The tooltip is not located in the right place (appears at the bottom)
		// See clients/tooltips_test.html for an example
		let containerElem = conf.container
		if (!containerElem) {
			containerElem = selectAncestor(this, 'div')
			if (containerElem === undefined) {
				// We require a div element at some point to anchor the tooltip
				return
			}
		}

		tooltip_div = d3.select(containerElem)
			.append('div')
			.attr('class', 'tnt_tooltip')
			.classed('tnt_tooltip_active', true)  // TODO: Is this needed/used???
			.call(drag)

		// prev tooltips with the same header
		d3.select('#tnt_tooltip_' + conf.id).remove()

		if ((d3.event === null) && (event))
			d3.event = event
		let d3mouse = d3.mouse(containerElem)
		d3.event = null

		let xoffset = -10
		if (conf.position === 'left')
			xoffset = conf.width

		tooltip_div.attr('id', 'tnt_tooltip_' + conf.id)

		// We place the tooltip
		tooltip_div
			.style('left', (d3mouse[0] - xoffset) + 'px')
			.style('top', (d3mouse[1]) + 'px')

		// Close
		if (conf.show_closer) {
			tooltip_div
				.append('div')
				.attr('class', 'tnt_tooltip_closer')
				.on('click', function () {
					t.close()
				})
		}

		conf.fill.call(tooltip_div.node(), data)

		// return this here?
		return t
	}

	// gets the first ancestor of elem having tagname 'type'
	// example : let mydiv = selectAncestor(myelem, 'div')
	function selectAncestor(elem, type) {
		type = type.toLowerCase()
		if (elem.parentNode === null) {
			console.log('No more parents')
			return undefined
		}
		let tagName = elem.parentNode.tagName

		if ((tagName !== undefined) && (tagName.toLowerCase() === type)) {
			return elem.parentNode
		} else {
			return selectAncestor(elem.parentNode, type)
		}
	}

	let api = apijs(t)
		.getset(conf)

	api.check('position', function(val) {
		return (val === 'left') || (val === 'right')
	}, 'Only \'left\' or \'right\' values are allowed for position')

	api.method('close', function() {
		if (tooltip_div) {
			tooltip_div.remove()
		}
	})

	return t
}

tooltip.list = function() {
	// list tooltip is based on general tooltips
	let t = tooltip()
	let width = 180

	t.fill (function (obj) {
		let tooltip_div = d3.select(this)
		let obj_info_list = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			// .style('width', t.width() + 'px')

		// Tooltip header
		if (obj.header) {
			obj_info_list
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.text(obj.header)
		}

		// Tooltip rows
		let table_rows = obj_info_list.selectAll('.tnt_zmenu_row')
			.data(obj.rows)
			.enter()
			.append('tr')
			.attr('class', 'tnt_zmenu_row')

		table_rows
			.append('td')
			.style('text-align', 'center')
			.html(function(d, i) {
				return obj.rows[i].value
			})
			.each(function(d) {
				if (d.link === undefined) {
					return
				}
				d3.select(this)
					.classed('link', 1)
					.on('click', function (d) {
						d.link(d.obj)
						t.close.call(this)
					})
			})
	})
	return t
}

tooltip.table = function(fullTree, selectedNode) {
	// fills the selected node -- KEEP WORKING HERE
	let id = '#tnt_tree_node_treeBox_' + selectedNode.id()
	let collapsedText = ''
	d3.select(id)
		.select('.tnt_node_display_elem')
		.attr('fill', '#FF6A13')

	// table tooltips are based on general tooltips
	let t = tooltip()

	let width = 180

	t.fill (function (obj) {
		let tooltip_div = d3.select(this)

		let obj_info_table = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			//.style('width', t.width() + 'px')

		// Tooltip header
		if (obj.header) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.attr('colspan', 2)
				.text(obj.header)
				.style('text-align', 'center')
		}

		/* let table_rows = obj_info_table.selectAll('.tnt_zmenu_row')
			.data(obj.rows)
			.enter()
			.append('tr')
			.attr('class', 'tnt_zmenu_row')

		table_rows
			.append('th')
			.style('text-align', 'center')
			.attr('colspan', function (d, i) {
				if (d.value === '') {
					return 2
				}
				return 1
			})
			.attr('class', function (d) {
				if (d.value === '') {
					return 'tnt_zmenu_inner_header'
				}
				return 'tnt_zmenu_cell'
			})
			.html(function(d,i) {
				return obj.rows[i].label
			})

		table_rows
			.append('td')
			.style('text-align', 'center')
			.html(function(d, i) {
				if (typeof obj.rows[i].value === 'function')
					obj.rows[i].value.call(this, d)
				return obj.rows[i].value
			})
			.each(function (d) {
				if (d.value === '')
					d3.select(this).remove()
			})
			.each(function (d) {
				if (d.link === undefined) {
					return
				}
				d3.select(this)
					.classed('link', 1)
					.on('click', function (f) {
						f.link(f.obj)
						t.close.call(this)
					})
			}) */

		// THIS IS WHERE TOOLTIP ADDITIONS ARE MADE
		let changeBranchColorClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Change Branch Color')
			.on('click', function() {
				let nodeID = selectedNode.id()
				let numChildren = selectedNode.get_all_nodes().length - 1
				if (document.getElementById('colorPicker')) {
					if (document.getElementById('colorPicker').style.display === 'none')
						popforms.popColorPicker(selectedNode)
					else
						document.getElementById('colorPicker').style.display = 'none'
				}
				else {
					popforms.popColorPicker(selectedNode)
				}
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		changeBranchColorClickable.on('mouseover', function() {changeBranchColorClickable.style('color', '#3287d7')})
		changeBranchColorClickable.on('mouseout', function() {changeBranchColorClickable.style('color', 'black')})

		let changeBranchWidthClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Change Branch Width')
			.on('click', function() {
				if (document.getElementById('branchWidthInput')) {
					if (document.getElementById('branchWidthInput').style.display === 'none')
						popforms.popFormBranchWidth(selectedNode)
					else
						document.getElementById('branchWidthInput').style.display = 'none'
				}
				else {
					popforms.popFormBranchWidth(selectedNode)
				}
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		changeBranchWidthClickable.on('mouseover', function() {changeBranchWidthClickable.style('color', '#3287d7')})
		changeBranchWidthClickable.on('mouseout', function() {changeBranchWidthClickable.style('color', 'black')})

		if (selectedNode.is_collapsed())
			collapsedText = 'Uncollapse Node'
		else
			collapsedText = 'Collapse Node'

		let toggleClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text(collapsedText)
			.on('click', function() {
				treeOperations.toggleNode(selectedNode) // Toggles the collapsed property in the treeObj
				treeOperations.updateUserChanges(fullTree) // Updates the collapsing/uncollapsing in the SVG visualization.
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		toggleClickable.on('mouseover', function() {toggleClickable.style('color', '#3287d7')})
		toggleClickable.on('mouseout', function() {toggleClickable.style('color', 'black')})

		let toggleCertainty = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Toggle Certainty')
			.on('click', function() {
				treeOperations.changeCertaintyProperty(selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		toggleCertainty.on('mouseover', function() {toggleCertainty.style('color', '#3287d7')})
		toggleCertainty.on('mouseout', function() {toggleCertainty.style('color', 'black')})

		let ladderizeSubtree = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Ladderize Subtree')
			.on('click', function() {
				treeOperations.ladderizeSubtree(selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		ladderizeSubtree.on('mouseover', function() {ladderizeSubtree.style('color', '#3287d7')})
		ladderizeSubtree.on('mouseout', function() {ladderizeSubtree.style('color', 'black')})

		let reRootClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Set as Root')
			.style('text-align', 'center')
			.on('click', function() {
				treeOperations.rerootTree(fullTree, selectedNode)
				treeOperations.updateUserChanges(fullTree)
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
		reRootClickable.on('mouseover', function() {reRootClickable.style('color', '#3287d7')})
		reRootClickable.on('mouseout', function() {reRootClickable.style('color', 'black')})

		let closeClickable = obj_info_table
			.append('tr')
			.attr('class', 'tnt_zmenu_clickable')
			.append('td')
			.attr('colspan', 2)
			.text('Close')
			.on('click', function() {
				d3.select(id)
					.select('.tnt_node_display_elem')
					.attr('fill', 'black')
				t.close()
			})
			.style('text-align', 'center')
		closeClickable.on('mouseover', function() {closeClickable.style('color', '#3287d7')})
		closeClickable.on('mouseout', function() {closeClickable.style('color', 'black')})
	})

	return t
}

tooltip.plain = function() {
	// plain tooltips are based on general tooltips
	let t = tooltip()

	t.fill(function(obj) {
		let tooltip_div = d3.select(this)

		let obj_info_table = tooltip_div
			.append('table')
			.attr('class', 'tnt_zmenu')
			.attr('border', 'solid')
			.style('width', '200px')
			// DEFAULT BY TNT.style('width', t.width() + 'px')

		if (obj.header) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_header')
				.append('th')
				.text(obj.header)
		}

		if (obj.body) {
			obj_info_table
				.append('tr')
				.attr('class', 'tnt_zmenu_row')
				.append('td')
				.style('text-align', 'center')
				.html(obj.body)
		}
	})

	return t
}

module.exports = exports = tooltip
<|endoftext|>


<commit_before>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Creates correctly formatted tooltip for Apps
//-------------------------------------------------------------------
d3plus.tooltip.app = function(params) {

  var vars = params.vars,
      d = params.data,
      ex = params.ex,
      mouse = params.mouseevents ? params.mouseevents : false,
      arrow = "arrow" in params ? params.arrow : true,
      id = d3plus.variable.value(vars,d,vars.id.key),
      tooltip_id = params.id || vars.type.value

  if ((d3.event && d3.event.type == "click") && (vars.html.value || vars.tooltip.value.long) && !("fullscreen" in params)) {
    var fullscreen = true,
        arrow = false,
        mouse = true,
        length = "long",
        footer = vars.footer.value

    vars.covered = true
  }
  else {
    var fullscreen = false,
        align = params.anchor || vars.style.tooltip.anchor,
        length = params.length || "short",
        footer = vars.footer_text()
  }

  if ("x" in params) {
    var x = params.x
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var x = d3.mouse(vars.parent.node())[0]
  }
  else {
    var x = d.d3plus.x
    if (vars.zoom.translate && vars.zoom.scale) {
      x = vars.zoom.translate[0]+x*vars.zoom.scale
    }
    x += vars.margin.left
  }

  if ("y" in params) {
    var y = params.y
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var y = d3.mouse(vars.parent.node())[1]
  }
  else {
    var y = d.d3plus.y
    if (vars.zoom.translate && vars.zoom.scale) {
      y = vars.zoom.translate[1]+y*vars.zoom.scale
    }
    y += vars.margin.top
  }

  if ("offset" in params) {
    var offset = params.offset
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var offset = 3
  }
  else {
    var offset = d.d3plus.r ? d.d3plus.r : d.d3plus.height/2
    // console.log(offset)
    if (vars.zoom.scale) {
      offset = offset * vars.zoom.scale
    }
    // console.log(offset)
  }

  function make_tooltip(html) {

    if (d.d3plus) {

      if (d.d3plus.merged) {
        if (!ex) ex = {}
        ex.items = d.d3plus.merged.length
      }

      var active = vars.active.key ? d3plus.variable.value(vars,d,vars.active.key) : d.d3plus.active,
          temp = vars.temp.key ? d3plus.variable.value(vars,d,vars.temp.key) : d.d3plus.temp,
          total = vars.total.key ? d3plus.variable.value(vars,d,vars.total.key) : d.d3plus.total

      if (typeof active == "number" && active > 0 && total) {
        if (!ex) ex = {}
        var label = vars.active.key || "active"
        ex[label] = active+"/"+total+" ("+vars.format((active/total)*100,"share")+"%)"
      }

      if (typeof temp == "number" && temp > 0 && total) {
        if (!ex) ex = {}
        var label = vars.temp.key || "temp"
        ex[label] = temp+"/"+total+" ("+vars.format((temp/total)*100,"share")+"%)"
      }

      if (d.d3plus.share) {
        if (!ex) ex = {}
        ex.share = vars.format(d.d3plus.share*100,"share")+"%"
      }

    }

    var depth = "depth" in params ? params.depth : vars.depth.value,
        title = d3plus.variable.value(vars,d,vars.text.key,vars.id.nesting[depth]),
        icon = d3plus.variable.value(vars,d,vars.icon.key,vars.id.nesting[depth]),
        tooltip_data = d3plus.tooltip.data(vars,d,length,ex,depth)

    if ((tooltip_data.length > 0 || footer) || (!d.d3plus_label || (d.d3plus_label && "visible" in d.d3plus_label && !d.d3plus_label.visible))) {

      if (!title) {
        title = id
      }

      var depth = d.d3plus && "depth" in d.d3plus ? vars.id.nesting[d.d3plus.depth] : vars.id.key

      if (typeof vars.icon.style.value == "string") {
        var icon_style = vars.icon.style.value
      }
      else if (typeof vars.icon.style.value == "object" && vars.icon.style.value[depth]) {
        var icon_style = vars.icon.style.value[depth]
      }
      else {
        var icon_style = "default"
      }

      if (params.width) {
        var width = params.width
      }
      else if (!fullscreen && tooltip_data.length == 0) {
        var width = "auto"
      }
      else {
        var width = vars.style.tooltip.small
      }

      d3plus.tooltip.create({
        "align": align,
        "arrow": arrow,
        "background": vars.style.tooltip.background,
        "curtain": vars.style.tooltip.curtain.color,
        "curtainopacity": vars.style.tooltip.curtain.opacity,
        "fontcolor": vars.style.tooltip.font.color,
        "fontfamily": vars.style.tooltip.font.family,
        "fontsize": vars.style.tooltip.font.size,
        "fontweight": vars.style.tooltip.font.weight,
        "data": tooltip_data,
        "color": d3plus.variable.color(vars,d),
        "footer": footer,
        "fullscreen": fullscreen,
        "html": html,
        "icon": icon,
        "id": tooltip_id,
        "max_width": vars.style.tooltip.small,
        "mouseevents": mouse,
        "offset": offset,
        "parent": vars.parent,
        "style": icon_style,
        "title": title,
        "width": width,
        "x": x,
        "y": y
      })

    }

  }

  if (fullscreen) {

    if (typeof vars.html.value == "string") {
      make_tooltip(vars.html.value)
    }
    else if (typeof vars.html.value == "function") {
      make_tooltip(vars.html.value(id))
    }
    else if (vars.html.value && typeof vars.html.value == "object" && vars.html.value.url) {
      d3.json(vars.html.value.url,function(data){
        var html = vars.html.value.callback ? vars.html.value.callback(data) : data
        make_tooltip(html)
      })
    }
    else {
      make_tooltip("")
    }

  }
  else {
    make_tooltip("")
  }

}
<commit_msg>removed console.log statements<commit_after>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Creates correctly formatted tooltip for Apps
//-------------------------------------------------------------------
d3plus.tooltip.app = function(params) {

  var vars = params.vars,
      d = params.data,
      ex = params.ex,
      mouse = params.mouseevents ? params.mouseevents : false,
      arrow = "arrow" in params ? params.arrow : true,
      id = d3plus.variable.value(vars,d,vars.id.key),
      tooltip_id = params.id || vars.type.value

  if ((d3.event && d3.event.type == "click") && (vars.html.value || vars.tooltip.value.long) && !("fullscreen" in params)) {
    var fullscreen = true,
        arrow = false,
        mouse = true,
        length = "long",
        footer = vars.footer.value

    vars.covered = true
  }
  else {
    var fullscreen = false,
        align = params.anchor || vars.style.tooltip.anchor,
        length = params.length || "short",
        footer = vars.footer_text()
  }

  if ("x" in params) {
    var x = params.x
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var x = d3.mouse(vars.parent.node())[0]
  }
  else {
    var x = d.d3plus.x
    if (vars.zoom.translate && vars.zoom.scale) {
      x = vars.zoom.translate[0]+x*vars.zoom.scale
    }
    x += vars.margin.left
  }

  if ("y" in params) {
    var y = params.y
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var y = d3.mouse(vars.parent.node())[1]
  }
  else {
    var y = d.d3plus.y
    if (vars.zoom.translate && vars.zoom.scale) {
      y = vars.zoom.translate[1]+y*vars.zoom.scale
    }
    y += vars.margin.top
  }

  if ("offset" in params) {
    var offset = params.offset
  }
  else if (d3plus.apps[vars.type.value].tooltip == "follow") {
    var offset = 3
  }
  else {
    var offset = d.d3plus.r ? d.d3plus.r : d.d3plus.height/2
    if (vars.zoom.scale) {
      offset = offset * vars.zoom.scale
    }
  }

  function make_tooltip(html) {

    if (d.d3plus) {

      if (d.d3plus.merged) {
        if (!ex) ex = {}
        ex.items = d.d3plus.merged.length
      }

      var active = vars.active.key ? d3plus.variable.value(vars,d,vars.active.key) : d.d3plus.active,
          temp = vars.temp.key ? d3plus.variable.value(vars,d,vars.temp.key) : d.d3plus.temp,
          total = vars.total.key ? d3plus.variable.value(vars,d,vars.total.key) : d.d3plus.total

      if (typeof active == "number" && active > 0 && total) {
        if (!ex) ex = {}
        var label = vars.active.key || "active"
        ex[label] = active+"/"+total+" ("+vars.format((active/total)*100,"share")+"%)"
      }

      if (typeof temp == "number" && temp > 0 && total) {
        if (!ex) ex = {}
        var label = vars.temp.key || "temp"
        ex[label] = temp+"/"+total+" ("+vars.format((temp/total)*100,"share")+"%)"
      }

      if (d.d3plus.share) {
        if (!ex) ex = {}
        ex.share = vars.format(d.d3plus.share*100,"share")+"%"
      }

    }

    var depth = "depth" in params ? params.depth : vars.depth.value,
        title = d3plus.variable.value(vars,d,vars.text.key,vars.id.nesting[depth]),
        icon = d3plus.variable.value(vars,d,vars.icon.key,vars.id.nesting[depth]),
        tooltip_data = d3plus.tooltip.data(vars,d,length,ex,depth)

    if ((tooltip_data.length > 0 || footer) || (!d.d3plus_label || (d.d3plus_label && "visible" in d.d3plus_label && !d.d3plus_label.visible))) {

      if (!title) {
        title = id
      }

      var depth = d.d3plus && "depth" in d.d3plus ? vars.id.nesting[d.d3plus.depth] : vars.id.key

      if (typeof vars.icon.style.value == "string") {
        var icon_style = vars.icon.style.value
      }
      else if (typeof vars.icon.style.value == "object" && vars.icon.style.value[depth]) {
        var icon_style = vars.icon.style.value[depth]
      }
      else {
        var icon_style = "default"
      }

      if (params.width) {
        var width = params.width
      }
      else if (!fullscreen && tooltip_data.length == 0) {
        var width = "auto"
      }
      else {
        var width = vars.style.tooltip.small
      }

      d3plus.tooltip.create({
        "align": align,
        "arrow": arrow,
        "background": vars.style.tooltip.background,
        "curtain": vars.style.tooltip.curtain.color,
        "curtainopacity": vars.style.tooltip.curtain.opacity,
        "fontcolor": vars.style.tooltip.font.color,
        "fontfamily": vars.style.tooltip.font.family,
        "fontsize": vars.style.tooltip.font.size,
        "fontweight": vars.style.tooltip.font.weight,
        "data": tooltip_data,
        "color": d3plus.variable.color(vars,d),
        "footer": footer,
        "fullscreen": fullscreen,
        "html": html,
        "icon": icon,
        "id": tooltip_id,
        "max_width": vars.style.tooltip.small,
        "mouseevents": mouse,
        "offset": offset,
        "parent": vars.parent,
        "style": icon_style,
        "title": title,
        "width": width,
        "x": x,
        "y": y
      })

    }

  }

  if (fullscreen) {

    if (typeof vars.html.value == "string") {
      make_tooltip(vars.html.value)
    }
    else if (typeof vars.html.value == "function") {
      make_tooltip(vars.html.value(id))
    }
    else if (vars.html.value && typeof vars.html.value == "object" && vars.html.value.url) {
      d3.json(vars.html.value.url,function(data){
        var html = vars.html.value.callback ? vars.html.value.callback(data) : data
        make_tooltip(html)
      })
    }
    else {
      make_tooltip("")
    }

  }
  else {
    make_tooltip("")
  }

}
<|endoftext|>


<commit_before>import ReactGA from 'react-ga';
import { setAppName, setVersion, APP_TOPIC_MAPPER } from './config';
import { setAppColors } from './styles/colors';
import routes from './routes/topicRoutes';
import initializeApp from './index';

/**
 * This serves as the primary entry point to the Media Cloud Topic Mapper app.
 */

ReactGA.initialize('UA-60744513-7');

setVersion('3.1.2');

setAppName(APP_TOPIC_MAPPER);

setAppColors({
  light: '#58DBCC',
  dark: '#38BBAC',  // primary
  darker: '#189B8C',
});

initializeApp(routes);
<commit_msg>version bump for release testing<commit_after>import ReactGA from 'react-ga';
import { setAppName, setVersion, APP_TOPIC_MAPPER } from './config';
import { setAppColors } from './styles/colors';
import routes from './routes/topicRoutes';
import initializeApp from './index';

/**
 * This serves as the primary entry point to the Media Cloud Topic Mapper app.
 */

ReactGA.initialize('UA-60744513-7');

setVersion('3.1.3');

setAppName(APP_TOPIC_MAPPER);

setAppColors({
  light: '#58DBCC',
  dark: '#38BBAC',  // primary
  darker: '#189B8C',
});

initializeApp(routes);
<|endoftext|>


<commit_before>'use strict';
//to change these packed files, edit ./data then run `node scripts/pack.js`
const unpack = require('./efrt-unpack');
let path = './_packed/_';
const tags = {
  // Adjective: require(path + 'adjectives'),
  Adverb: require(path + 'adverbs'),
  Place: require(path + 'airports'),
  City: require(path + 'cities'),
  Country: require(path + 'countries'),
  Demonym: require(path + 'demonyms'),
  Expression: require(path + 'expressions'),
  FemaleName: require(path + 'female'),
  FirstName: require(path + 'firstnames'),
  Holiday: require(path + 'holidays'),
  LastName: require(path + 'lastnames'),
  MaleName: require(path + 'male'),
  Organization: require(path + 'organizations'),
  SportsTeam: require(path + 'sportsTeams'),
  Actor: require(path + 'professions'),
  Preposition: require(path + 'prepositions'),
};

// const utils = {
//   orgWords: require(path + 'orgWords'),
//   uncountable: require(path + 'uncountables'),
//   PhrasalVerb: require(path + 'phrasals'),
// };

// console.time('trie-unpack');
//turn these compressed strings into queryable tries (using `nlp-compromise/efrt` library)
const keys = Object.keys(tags);
keys.forEach((tag) => {
  tags[tag] = unpack(tags[tag]);
});
// console.timeEnd('trie-unpack');

const lookup = function(str) {
  for(let i = 0; i < keys.length; i++) {
    if (tags[keys[i]].has(str)) {
      return keys[i];
    }
  }
  return null;
};

module.exports = {
  lookup: lookup
};
// console.time('trie-query');
// console.log(lookup('aloof'));
// console.timeEnd('trie-query');
// console.log(lookup('taipei'));
// console.log(lookup('gerald'));
// console.log(lookup('mexico'));
<commit_msg>add cache to trie lookups<commit_after>'use strict';
//to change these packed files, edit ./data then run `node scripts/pack.js`
const unpack = require('./efrt-unpack');
let path = './_packed/_';
const tags = {
  // Adjective: require(path + 'adjectives'),
  Adverb: require(path + 'adverbs'),
  Place: require(path + 'airports'),
  City: require(path + 'cities'),
  Country: require(path + 'countries'),
  Demonym: require(path + 'demonyms'),
  Expression: require(path + 'expressions'),
  FemaleName: require(path + 'female'),
  FirstName: require(path + 'firstnames'),
  Holiday: require(path + 'holidays'),
  LastName: require(path + 'lastnames'),
  MaleName: require(path + 'male'),
  Organization: require(path + 'organizations'),
  SportsTeam: require(path + 'sportsTeams'),
  Actor: require(path + 'professions'),
  Preposition: require(path + 'prepositions'),
};

// const utils = {
//   orgWords: require(path + 'orgWords'),
//   uncountable: require(path + 'uncountables'),
//   PhrasalVerb: require(path + 'phrasals'),
// };

// console.time('trie-unpack');
//turn these compressed strings into queryable tries (using `nlp-compromise/efrt` library)
const keys = Object.keys(tags);
keys.forEach((tag) => {
  tags[tag] = unpack(tags[tag]);
  tags[tag].cache();
});
// console.timeEnd('trie-unpack');

const lookup = function(str) {
  for(let i = 0; i < keys.length; i++) {
    if (tags[keys[i]].has(str)) {
      return keys[i];
    }
  }
  return null;
};

module.exports = {
  lookup: lookup
};
// console.time('trie-query');
// console.log(lookup('aloof'));
// console.timeEnd('trie-query');
// console.log(lookup('taipei'));
// console.log(lookup('gerald'));
// console.log(lookup('mexico'));
<|endoftext|>


<commit_before>/**
 * Twitter JS v1.12
 * http://code.google.com/p/twitterjs/
 * Copyright (c) 2009 Remy Sharp / MIT License
 * $Date$
 */
if(typeof renderTwitters!='function')(function(){var browser=(function(){var a=navigator.userAgent.toLowerCase();return{safari:/webkit/.test(a),opera:/opera/.test(a),msie:/msie/.test(a)&&!(/opera/).test(a),mozilla:/mozilla/.test(a)&&!(/(compatible|webkit)/).test(a)}})();var guid=0;var readyList=[];var isReady=false;window.renderTwitters=function(c,g){function h(a){return document.createElement(a)}function l(a){return document.createTextNode(a)}var b=document.getElementById(g.twitterTarget);var p=null;var m=h('ul'),j,k,n,f,i=c.length>g.count?g.count:c.length;for(f=0;f<i&&c[f];f++){p=getTwitterData(c[f]);if(g.ignoreReplies&&c[f].text.substr(0,1)=='@'){i++;continue}j=h('li');if(g.template){j.innerHTML=g.template.replace(/%([a-z_\-\.]*)%/ig,function(a,d){var e=p[d]+""||"";if(d=='text'&&g.enableLinks)e=linkify(e);return e})}else{k=h('span');k.className='twitterStatus';n=h('span');n.className='twitterTime';k.innerHTML=c[f].text;if(g.enableLinks==true){k.innerHTML=linkify(k.innerHTML)}n.innerHTML=relative_time(c[f].created_at);if(g.prefix){var o=h('span');o.className='twitterPrefix';o.innerHTML=g.prefix.replace(/%(.*?)%/g,function(a,d){return c[f].user[d]});j.appendChild(o);j.appendChild(l(' '))}j.appendChild(k);j.appendChild(l(' '));j.appendChild(n)}m.appendChild(j)}if(g.clearContents){while(b.firstChild){b.removeChild(b.firstChild)}}b.appendChild(m)};window.getTwitters=function(g,h,l,b){guid++;if(typeof h=='object'){b=h;h=b.id;l=b.count}if(!l)l=1;if(b){b.count=l}else{b={}}if(!b.timeout&&typeof b.onTimeout=='function'){b.timeout=10}if(typeof b.clearContents=='undefined'){b.clearContents=true}if(b.withFriends)b.withFriends=false;b['twitterTarget']=g;if(typeof b.enableLinks=='undefined')b.enableLinks=true;window['twitterCallback'+guid]=function(a){if(b.timeout){clearTimeout(window['twitterTimeout'+guid])}renderTwitters(a,b)};ready((function(e,c){return function(){if(!document.getElementById(e.twitterTarget)){return}var a='http://www.twitter.com/statuses/'+(e.withFriends?'friends_timeline':'user_timeline')+'/'+h+'.json?callback=twitterCallback'+c+'&count=20';if(e.timeout){window['twitterTimeout'+c]=setTimeout(function(){if(e.onTimeoutCancel)window['twitterCallback'+c]=function(){};e.onTimeout.call(document.getElementById(e.twitterTarget))},e.timeout*1000)}var d=document.createElement('script');d.setAttribute('src',a);document.getElementsByTagName('head')[0].appendChild(d)}})(b,guid))};DOMReady();function getTwitterData(a){var d=a,e;for(e in a.user){d['user_'+e]=a.user[e]}d.time=relative_time(a.created_at);return d}function ready(a){if(!isReady){readyList.push(a)}else{a.call()}}function fireReady(){isReady=true;var a;while(a=readyList.shift()){a.call()}}function DOMReady(){if(browser.mozilla||browser.opera){document.addEventListener("DOMContentLoaded",fireReady,false)}else if(browser.msie){document.write("


<commit_before>import * as clamp from 'clamp';
import colorConstants from 'flockn/constants/color';

class Color {
  constructor(r = 0, g = 0, b = 0, a = 1) {
    this.set(r, g, b, a);
  }

  set(r = 0, g = 0, b = 0, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  lighten(factor) {
    factor = clamp(factor, 0, 1);

    this.r = clamp(this.r + (factor * 255) | 0, 0, 255);
    this.g = clamp(this.g + (factor * 255) | 0, 0, 255);
    this.b = clamp(this.b + (factor * 255) | 0, 0, 255);
  }

  darken(factor) {
    factor = clamp(factor, 0, 1);

    this.r = clamp(this.r - (factor * 255) | 0, 0, 255);
    this.g = clamp(this.g - (factor * 255) | 0, 0, 255);
    this.b = clamp(this.b - (factor * 255) | 0, 0, 255);
  }

  fadeIn(factor) {
    factor = clamp(factor, 0, 1);

    this.a = this.a + this.a * factor;
    if (this.a > 1) {
      this.a = 1;
    }
  }

  fadeOut(factor) {
    factor = clamp(factor, 0, 1);

    this.a = this.a - this.a * factor;
    if (this.a < 0) {
      this.a = 0;
    }
  }

  toString() {
    if (this.a < 1) {
      if (this.a === 0) {
        return 'transparent';
      } else {
        return `rgba(${this.r},${this.g},${this.b},${this.a})`;
      }
    } else {
      return `rgb(${this.r},${this.g},${this.b})`;
    }
  }

  toHex() {
    return `#${this.r.toString(16)}${this.g.toString(16)}${this.b.toString(16)}`;
  }

  // Getting a random color for debugging is quite useful sometimes
  static random() {
    var col = [0, 0, 0];

    col = col.map(function() {
      return ~~(Math.random() * 255);
    });

    return new Color(col[0], col[1], col[2]);
  }
}

// TODO: Reflect if it wouldn't be better to use functions rather than custom properties
for (var colorName in colorConstants) {
  var colorValue = colorConstants[colorName];

  (function(colorName, colorValue) {
    Object.defineProperty(Color, colorName, {
      get: function() {
        var col = new Color(colorValue.r, colorValue.g, colorValue.b, colorValue.a);
        col.name = colorName;
        return col;
      }
    });
  })(colorName, colorValue);
}

export default Color;
<commit_msg>Color constants are now functions<commit_after>import * as clamp from 'clamp';
import colorConstants from 'flockn/constants/color';

class Color {
  constructor(r = 0, g = 0, b = 0, a = 1) {
    this.set(r, g, b, a);
  }

  set(r = 0, g = 0, b = 0, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  lighten(factor) {
    factor = clamp(factor, 0, 1);

    this.r = clamp(this.r + (factor * 255) | 0, 0, 255);
    this.g = clamp(this.g + (factor * 255) | 0, 0, 255);
    this.b = clamp(this.b + (factor * 255) | 0, 0, 255);
  }

  darken(factor) {
    factor = clamp(factor, 0, 1);

    this.r = clamp(this.r - (factor * 255) | 0, 0, 255);
    this.g = clamp(this.g - (factor * 255) | 0, 0, 255);
    this.b = clamp(this.b - (factor * 255) | 0, 0, 255);
  }

  fadeIn(factor) {
    factor = clamp(factor, 0, 1);

    this.a = this.a + this.a * factor;
    if (this.a > 1) {
      this.a = 1;
    }
  }

  fadeOut(factor) {
    factor = clamp(factor, 0, 1);

    this.a = this.a - this.a * factor;
    if (this.a < 0) {
      this.a = 0;
    }
  }

  toString() {
    if (this.a < 1) {
      if (this.a === 0) {
        return 'transparent';
      } else {
        return `rgba(${this.r},${this.g},${this.b},${this.a})`;
      }
    } else {
      return `rgb(${this.r},${this.g},${this.b})`;
    }
  }

  toHex() {
    return `#${this.r.toString(16)}${this.g.toString(16)}${this.b.toString(16)}`;
  }

  // Getting a random color for debugging is quite useful sometimes
  static random() {
    var col = [0, 0, 0];

    col = col.map(function() {
      return ~~(Math.random() * 255);
    });

    return new Color(col[0], col[1], col[2]);
  }
}

for (var colorName in colorConstants) {
  var colorValue = colorConstants[colorName];

  (function(colorName, colorValue) {
    Color[colorName] = function() {
      var col = new Color(colorValue.r, colorValue.g, colorValue.b, colorValue.a);
      col.name = colorName;
      return col;
    };
  })(colorName, colorValue);
}

export default Color;
<|endoftext|>


<commit_before>/**
 * Filter the chromatogram based on a callback
 * The callback will take a time
 * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the peak picking
 * @param {function(number, number)} callback
 * @param {object} [options] - options object
 * @param {boolean} [options.copy = false] - return a copy of the original object
 * @return {Chromatogram} - Modified chromatogram
 */
export function filter(chromatogram, callback, options = {}) {
  const { copy = false } = options;
  let chromatogram;
  if (copy) {
    chromatogram = chromatogram.copy();
  } else {
    chromatogram = chromatogram;
  }

  let times = chromatogram.getTimes();
  let newTimes = [];
  let indexToKeep = [];
  for (let i = 0; i < times.length; i++) {
    if (callback(i, times[i])) {
      indexToKeep.push(i);
      newTimes.push(times[i]);
    }
  }
  chromatogram.setTimes(newTimes);

  for (let key of chromatogram.getSerieNames()) {
    let serie = chromatogram.getSerie(key);
    serie.keep(indexToKeep);
  }

  return chromatogram;
}
<commit_msg>fix: chromatogram duplicate<commit_after>/**
 * Filter the chromatogram based on a callback
 * The callback will take a time
 * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the peak picking
 * @param {function(number, number)} callback
 * @param {object} [options] - options object
 * @param {boolean} [options.copy = false] - return a copy of the original object
 * @return {Chromatogram} - Modified chromatogram
 */
export function filter(chromatogram, callback, options = {}) {
  const { copy = false } = options;
  if (copy) {
    chromatogram = chromatogram.copy();
  }

  let times = chromatogram.getTimes();
  let newTimes = [];
  let indexToKeep = [];
  for (let i = 0; i < times.length; i++) {
    if (callback(i, times[i])) {
      indexToKeep.push(i);
      newTimes.push(times[i]);
    }
  }
  chromatogram.setTimes(newTimes);

  for (let key of chromatogram.getSerieNames()) {
    let serie = chromatogram.getSerie(key);
    serie.keep(indexToKeep);
  }

  return chromatogram;
}
<|endoftext|>


<commit_before>import Checkit from 'checkit';

export const formatTheses = (theses, agreements, persons, roles) => {
    if (!theses || !persons || !agreements)
        return [];

    return theses.map(thesis => formatThesis(thesis, agreements, persons, roles));
};

export const formatThesis = (thesis, agreements, persons, roles) => {
    const agreement = agreements.find(agreement => agreement.thesisId === thesis.thesisId);
    const person = agreement ? persons.find(person => person.personId === agreement.authorId) : {};
    const formattedThesis = Object.assign({}, thesis);

    formattedThesis.programmeId = agreement.programmeId;

    if (roles) {
        formattedThesis.graders = persons.filter(person =>
            roles.find(role =>
                role.personId === person.personId &&
                role.agreementId === agreement.agreementId
            )
        );
    }

    if (person) {
        formattedThesis.authorEmail = person.email;
        formattedThesis.authorFirstname = person.firstname;
        formattedThesis.authorLastname = person.lastname;
    } else { // Thesis not linked to person yet, use invite link email
        formattedThesis.authorEmail = agreement.email;
    }

    return formattedThesis;
};

export const oldGradeFields = [
    { id: 'Approbatur', name: 'Approbatur' },
    { id: 'Lubenter Approbatur', name: 'Lubenter Approbatur' },
    { id: 'Non Sine Laude Approbatur', name: 'Non Sine Laude Approbatur' },
    { id: 'Cum Laude Approbatur', name: 'Cum Laude Approbatur' },
    { id: 'Magna Cum Laude Approbatur', name: 'Magna Cum Laude Approbatur' },
    { id: 'Eximia Cum Laude Approbatur', name: 'Eximia Cum Laude Approbatur' },
    { id: 'Laudatur', name: 'Laudatur' }
];

export const gradeFields = [
    { id: '1', name: '1' },
    { id: '2', name: '2' },
    { id: '3', name: '3' },
    { id: '4', name: '4' },
    { id: '5', name: '5' }
];

export const thesisValidationRules = {
    title: 'required',
    authorEmail: ['required', 'email'],
    urkund: ['required', 'url'],
    grade: 'required',
    programmeId: 'required'
};

export const thesisValidation = new Checkit(thesisValidationRules);
<commit_msg>Lint fixes<commit_after>import Checkit from 'checkit';

export const formatTheses = (theses, agreements, persons, roles) => {
    if (!theses || !persons || !agreements)
        return [];

    return theses.map(thesis => formatThesis(thesis, agreements, persons, roles));
};

export const formatThesis = (thesis, agreements, persons, roles) => {
    const thesisAgreement = agreements.find(agreement => agreement.thesisId === thesis.thesisId);
    const author = thesisAgreement ? persons.find(person => person.personId === thesisAgreement.authorId) : {};
    const formattedThesis = Object.assign({}, thesis);

    formattedThesis.programmeId = thesisAgreement.programmeId;

    if (roles) {
        formattedThesis.graders = persons.filter(person =>
            roles.find(role =>
                role.personId === person.personId &&
                role.agreementId === thesisAgreement.agreementId
            )
        );
    }

    if (author) {
        formattedThesis.authorEmail = author.email;
        formattedThesis.authorFirstname = author.firstname;
        formattedThesis.authorLastname = author.lastname;
    } else { // Thesis not linked to person yet, use invite link email
        formattedThesis.authorEmail = thesisAgreement.email;
    }

    return formattedThesis;
};

export const oldGradeFields = [
    { id: 'Approbatur', name: 'Approbatur' },
    { id: 'Lubenter Approbatur', name: 'Lubenter Approbatur' },
    { id: 'Non Sine Laude Approbatur', name: 'Non Sine Laude Approbatur' },
    { id: 'Cum Laude Approbatur', name: 'Cum Laude Approbatur' },
    { id: 'Magna Cum Laude Approbatur', name: 'Magna Cum Laude Approbatur' },
    { id: 'Eximia Cum Laude Approbatur', name: 'Eximia Cum Laude Approbatur' },
    { id: 'Laudatur', name: 'Laudatur' }
];

export const gradeFields = [
    { id: '1', name: '1' },
    { id: '2', name: '2' },
    { id: '3', name: '3' },
    { id: '4', name: '4' },
    { id: '5', name: '5' }
];

export const thesisValidationRules = {
    title: 'required',
    authorEmail: ['required', 'email'],
    urkund: ['required', 'url'],
    grade: 'required',
    programmeId: 'required'
};

export const thesisValidation = new Checkit(thesisValidationRules);
<|endoftext|>


<commit_before>/**
 * Recursively flattens an array
 * @param {Array} arr array to flatten
 * @example flatten([1, [2], [[3, 4], 5], 6]) => [1, 2, 3, 4, 5, 6]
 * @returns {Array} flatted array
 */
const flatten = arr => arr.reduce((agg, el) => {
	if (el instanceof Array) {
		return agg.concat(flatten(el));
	}
	return agg.concat(el);
}, []);

/**
 * Emulates Feathers-style pagination on a given array.
 * @param {Array} data Array-like collection to paginate
 * @param {Object} params Feathers request params containing $paginate, $limit, and $skip
 * @returns {Object} { total, limit, skip, data }
 */
const paginate = (data, params) => {
	if (!params || !params.$paginate) {
		return data;
	}
	let limit = params.$limit || data.length;
	if (limit < 0) {
		limit = data.length;
	}
	let skip = params.$skip || 0;
	if (skip < 0) {
		skip = 0;
	}
	const paginatedData = data.slice(skip, skip + limit);
	return {
		total: data.length,
		limit,
		skip,
		data: paginatedData,
	};
};

const sort = (data, sortOrder) => {
	if (!data) {
		return data;
	}
	let valueOp = v => v;
	let descending = false;
	if (typeof sortOrder === 'string') {
		descending = /^-/.test(sortOrder);
		const match = /^-?(.*)/.test(sortOrder);
		if (match) {
			const attributeName = sortOrder.match(/-?(.*)/)[1];
			if (attributeName.length > 0) {
				valueOp = item => item[attributeName];
			}
		}
	}
	return data.sort((a, b) => {
		if (descending) {
			return (`${valueOp(b)}`).localeCompare(`${valueOp(a)}`);
		}
		return (`${valueOp(a)}`).localeCompare(`${valueOp(b)}`);
	});
};

module.exports = {
	flatten,
	paginate,
	sort,
};
<commit_msg>Update sort docs<commit_after>/**
 * Recursively flattens an array
 * @param {Array} arr array to flatten
 * @example flatten([1, [2], [[3, 4], 5], 6]) => [1, 2, 3, 4, 5, 6]
 * @returns {Array} flatted array
 */
const flatten = arr => arr.reduce((agg, el) => {
	if (el instanceof Array) {
		return agg.concat(flatten(el));
	}
	return agg.concat(el);
}, []);

/**
 * Emulates Feathers-style pagination on a given array.
 * @param {Array} data Array-like collection to paginate
 * @param {Object} params Feathers request params containing $paginate, $limit, and $skip
 * @returns {Object} { total, limit, skip, data }
 */
const paginate = (data, params) => {
	if (!params || !params.$paginate) {
		return data;
	}
	let limit = params.$limit || data.length;
	if (limit < 0) {
		limit = data.length;
	}
	let skip = params.$skip || 0;
	if (skip < 0) {
		skip = 0;
	}
	const paginatedData = data.slice(skip, skip + limit);
	return {
		total: data.length,
		limit,
		skip,
		data: paginatedData,
	};
};

/**
 * Sort an array using mongodb-notation
 * @param {Array} data the array to sort
 * @param {String} sortOrder mongodb-like sort order (see examples)
 * @example
 * sort([]) => [];
 * sort([1, 3, 2]) => [1, 2, 3];
 * sort([1, 3, 2], '-') => [3, 2, 1];
 * sort([{a: 2}, {a: 1}], 'a') => [{a: 1}, {a: 2}];
 * sort([{b: 4}, {b: 5}], '-b') => [{b: 5}, {b: 4}];
 */
const sort = (data, sortOrder) => {
	if (!data) {
		return data;
	}
	// if not otherwise specified, sort by value (descending):
	let valueOp = v => v;
	let descending = false;

	if (typeof sortOrder === 'string') {
		descending = /^-/.test(sortOrder);
		const match = /^-?(.*)/.test(sortOrder);
		if (match) {
			// sort by attribute value
			const attributeName = sortOrder.match(/-?(.*)/)[1];
			if (attributeName.length > 0) {
				valueOp = item => item[attributeName];
			}
		}
	}
	return data.sort((a, b) => {
		// use string.localeCompare to work on Strings, dates, and numbers
		if (descending) {
			return (`${valueOp(b)}`).localeCompare(`${valueOp(a)}`);
		}
		return (`${valueOp(a)}`).localeCompare(`${valueOp(b)}`);
	});
};

module.exports = {
	flatten,
	paginate,
	sort,
};
<|endoftext|>


<commit_before>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed 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 {dev} from '../log';
import {toArray} from '../types';

/**
 * Interpret a byte array as a UTF-8 string.
 * @param {!BufferSource} bytes
 * @return {!Promise<string>}
 */
export function utf8Decode(bytes) {
  if (typeof TextDecoder !== 'undefined') {
    return Promise.resolve(new TextDecoder('utf-8').decode(bytes));
  }
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => {
      reject(reader.error);
    };
    reader.onloadend = () => {
      resolve(reader.result);
    };
    reader.readAsText(new Blob([bytes]));
  });
}

// TODO(aghassemi, #6139): Remove the async version of utf8 encoding and rename
// the sync versions to the canonical utf8Decode/utf8Encode.
/**
 * Interpret a byte array as a UTF-8 string.
 * @param {!BufferSource} bytes
 * @return {!string}
 */
export function utf8DecodeSync(bytes) {
  if (typeof TextDecoder !== 'undefined') {
    return new TextDecoder('utf-8').decode(bytes);
  }
  const asciiString = bytesToString(new Uint8Array(bytes.buffer || bytes));
  return decodeURIComponent(escape(asciiString));
}

/**
 * Turn a string into UTF-8 bytes.
 * @param {string} string
 * @return {!Promise<!Uint8Array>}
 */
export function utf8Encode(string) {
  if (typeof TextEncoder !== 'undefined') {
    return Promise.resolve(new TextEncoder('utf-8').encode(string));
  }
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => {
      reject(reader.error);
    };
    reader.onloadend = () => {
      // Because we used readAsArrayBuffer, we know the result must be an
      // ArrayBuffer.
      resolve(new Uint8Array(/** @type {ArrayBuffer} */ (reader.result)));
    };
    reader.readAsArrayBuffer(new Blob([string]));
  });
}

/**
 * Turn a string into UTF-8 bytes.
 * @param {string} string
 * @return {!Uint8Array}
 */
export function utf8EncodeSync(string) {
  if (typeof TextEncoder !== 'undefined') {
    return new TextEncoder('utf-8').encode(string);
  }
  return stringToBytes(unescape(encodeURIComponent(string)));
}

/**
 * Converts a string which holds 8-bit code points, such as the result of atob,
 * into a Uint8Array with the corresponding bytes.
 * If you have a string of characters, you probably want to be using utf8Encode.
 * @param {string} str
 * @return {!Uint8Array}
 */
export function stringToBytes(str) {
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    const charCode = str.charCodeAt(i);
    dev().assert(charCode <= 255, 'Characters must be in range [0,255]');
    bytes[i] = charCode;
  }
  return bytes;
};

/**
 * Converts a 8-bit bytes array into a string
 * @param {!Uint8Array} bytes
 * @return {string}
 */
export function bytesToString(bytes) {
  return String.fromCharCode.apply(String, toArray(bytes));
};

/**
 * Converts a 4-item byte array to an unsigned integer.
 * Assumes bytes are big endian.
 * @param {!Uint8Array} bytes
 * @return {number}
 */
export function bytesToUInt32(bytes) {
  if (bytes.length != 4) {
    throw new Error('Received byte array with length != 4');
  }
  const val = (bytes[0] & 0xFF) << 24 |
     (bytes[1] & 0xFF) << 16 |
     (bytes[2] & 0xFF) << 8 |
     (bytes[3] & 0xFF);
  // Convert to unsigned.
  return val >>> 0;
}

/**
 * Generate a random bytes array with specific length using
 * win.crypto.getRandomValues. Return null if it is not available.
 * @param {!number} length
 * @return {?Uint8Array}
 */
export function getCryptoRandomBytesArray(win, length) {
  if (!win.crypto || !win.crypto.getRandomValues) {
    return null;
  }

  // Widely available in browsers we support:
  // http://caniuse.com/#search=getRandomValues
  const uint8array = new Uint8Array(length);
  win.crypto.getRandomValues(uint8array);
  return uint8array;
}
<commit_msg>Fix bytesToString stack overflow (#10504)<commit_after>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed 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 {dev} from '../log';

/**
 * Interpret a byte array as a UTF-8 string.
 * @param {!BufferSource} bytes
 * @return {!Promise<string>}
 */
export function utf8Decode(bytes) {
  if (typeof TextDecoder !== 'undefined') {
    return Promise.resolve(new TextDecoder('utf-8').decode(bytes));
  }
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => {
      reject(reader.error);
    };
    reader.onloadend = () => {
      resolve(reader.result);
    };
    reader.readAsText(new Blob([bytes]));
  });
}

// TODO(aghassemi, #6139): Remove the async version of utf8 encoding and rename
// the sync versions to the canonical utf8Decode/utf8Encode.
/**
 * Interpret a byte array as a UTF-8 string.
 * @param {!BufferSource} bytes
 * @return {!string}
 */
export function utf8DecodeSync(bytes) {
  if (typeof TextDecoder !== 'undefined') {
    return new TextDecoder('utf-8').decode(bytes);
  }
  const asciiString = bytesToString(new Uint8Array(bytes.buffer || bytes));
  return decodeURIComponent(escape(asciiString));
}

/**
 * Turn a string into UTF-8 bytes.
 * @param {string} string
 * @return {!Promise<!Uint8Array>}
 */
export function utf8Encode(string) {
  if (typeof TextEncoder !== 'undefined') {
    return Promise.resolve(new TextEncoder('utf-8').encode(string));
  }
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => {
      reject(reader.error);
    };
    reader.onloadend = () => {
      // Because we used readAsArrayBuffer, we know the result must be an
      // ArrayBuffer.
      resolve(new Uint8Array(/** @type {ArrayBuffer} */ (reader.result)));
    };
    reader.readAsArrayBuffer(new Blob([string]));
  });
}

/**
 * Turn a string into UTF-8 bytes.
 * @param {string} string
 * @return {!Uint8Array}
 */
export function utf8EncodeSync(string) {
  if (typeof TextEncoder !== 'undefined') {
    return new TextEncoder('utf-8').encode(string);
  }
  return stringToBytes(unescape(encodeURIComponent(string)));
}

/**
 * Converts a string which holds 8-bit code points, such as the result of atob,
 * into a Uint8Array with the corresponding bytes.
 * If you have a string of characters, you probably want to be using utf8Encode.
 * @param {string} str
 * @return {!Uint8Array}
 */
export function stringToBytes(str) {
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    const charCode = str.charCodeAt(i);
    dev().assert(charCode <= 255, 'Characters must be in range [0,255]');
    bytes[i] = charCode;
  }
  return bytes;
};

/**
 * Converts a 8-bit bytes array into a string
 * @param {!Uint8Array} bytes
 * @return {string}
 */
export function bytesToString(bytes) {
  const array = new Array(bytes.length);
  for (let i = 0; i < bytes.length; i++) {
    array[i] = String.fromCharCode(bytes[i]);
  }
  return array.join('');
};

/**
 * Converts a 4-item byte array to an unsigned integer.
 * Assumes bytes are big endian.
 * @param {!Uint8Array} bytes
 * @return {number}
 */
export function bytesToUInt32(bytes) {
  if (bytes.length != 4) {
    throw new Error('Received byte array with length != 4');
  }
  const val = (bytes[0] & 0xFF) << 24 |
     (bytes[1] & 0xFF) << 16 |
     (bytes[2] & 0xFF) << 8 |
     (bytes[3] & 0xFF);
  // Convert to unsigned.
  return val >>> 0;
}

/**
 * Generate a random bytes array with specific length using
 * win.crypto.getRandomValues. Return null if it is not available.
 * @param {!number} length
 * @return {?Uint8Array}
 */
export function getCryptoRandomBytesArray(win, length) {
  if (!win.crypto || !win.crypto.getRandomValues) {
    return null;
  }

  // Widely available in browsers we support:
  // http://caniuse.com/#search=getRandomValues
  const uint8array = new Uint8Array(length);
  win.crypto.getRandomValues(uint8array);
  return uint8array;
}
<|endoftext|>


<commit_before>var Vector = require('../math/Vector'),
    Circle = require('../geom/Circle'),
    Rectangle = require('../geom/Rectangle'),
    Polygon = require('../geom/Polygon');

/**
 * The grapefruit utility object, used for misc functions used throughout the code base
 *
 * @class utils
 * @extends Object
 * @static
 */
var utils = {
    _arrayDelim: /[|,]/,
    /**
     * An empty function that performs no action
     *
     * @method noop
     */
    noop: function() {},
    /**
     * Gets the absolute url from a relative one
     *
     * @method getAbsoluteUrl
     * @param url {String} The relative url to translate into absolute
     * @return {String} The absolute url (fully qualified)
     */
    getAbsoluteUrl: function(url) {
        var a = document.createElement('a');
        a.href = url;
        return a.href;
    },
    /**
     * Performs an ajax request, and manages the callbacks passed in
     *
     * @method ajax
     * @param settings {Object} The settings of the ajax request, similar to jQuery's ajax function
     * @return {XMLHttpRequest|ActiveXObject} An XHR object
     */
    ajax: function(sets) {
        //base settings
        sets = sets || {};
        sets.method = sets.method || 'GET';
        sets.dataType = sets.dataType || 'text';

        if(!sets.url)
            throw new TypeError('Undefined URL passed to ajax');

        //callbacks
        sets.progress = sets.progress || utils.noop;
        sets.load = sets.load || utils.noop;
        sets.error = sets.error || utils.noop;
        sets.abort = sets.abort || utils.noop;
        sets.complete = sets.complete || utils.noop;

        var xhr = utils.createAjaxRequest(),
            protocol = utils.getAbsoluteUrl(sets.url).split('/')[0];

        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                var res = xhr.response || xhr.responseText,
                    err = null;

                //The 'file:' protocol doesn't give response codes
                if(protocol !== 'file:' && xhr.status !== 200)
                    err = 'Non-200 status code returned: ' + xhr.status;

                if(!err && typeof res === 'string') {
                    if(sets.dataType === 'json') {
                        try {
                            res = JSON.parse(res);
                        } catch(e) {
                            err = e;
                        }
                    } else if(sets.dataType === 'xml') {
                        try {
                            res = utils.parseXML(res);
                        } catch(e) {
                            err = e;
                        }
                    }
                }

                if(err) {
                    if(sets.error) sets.error.call(xhr, err);
                } else {
                    if(sets.load) sets.load.call(xhr, res);
                }
            }
        };

        //chrome doesn't support json responseType, some browsers choke on XML type
        if(sets.dataType !== 'json' && sets.dataType !== 'xml')
            xhr.responseType = sets.dataType;
        else
            xhr.responseType = 'text';

        xhr.open(sets.method, sets.url, true);
        xhr.send();

        return xhr;
    },
    /**
     * Wraps XMLHttpRequest in a cross-browser way.
     *
     * @method AjaxRequest
     * @return {XMLHttpRequest|ActiveXObject}
     */
    //from pixi.js
    createAjaxRequest: function() {
        //activeX versions to check for in IE
        var activexmodes = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'];

        //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
        if(window.ActiveXObject) {
            for(var i=0; i<activexmodes.length; i++) {
                try {
                    return new window.ActiveXObject(activexmodes[i]);
                }
                catch(e) {
                    //suppress error
                }
            }
        }
        // if Mozilla, Safari etc
        else if(window.XMLHttpRequest) {
            return new XMLHttpRequest();
        }
        else {
            return false;
        }
    },
    /**
     * This will take values and override the passed obj's properties with those values.
     * The difference from a normal object extend is that this will try to massage the passed
     * value into the same type as the object's property. Also if the key for the value is not
     * in the original object, it is not copied.
     *
     * @method setValues
     * @param obj {Object} The object to extend the values into
     * @param values {Object} The values to put into the object
     * @return {Object} returns the updated object
     * @example
     *      var obj = { vec: new Vector(), arr: [] },
     *          vals = { vec: '2|5', arr: '5|10|11' };
     *      utils.setValues(obj, vals);
     *      //now obj is:
     *      // { vec: Vector(2, 5), arr: [5, 10, 11] }
     *
     */
    //similar to https://github.com/mrdoob/three.js/blob/master/src/materials/Material.js#L42
    setValues: function(obj, values) {
        if(!values) return;

        for(var key in values) {
            var newVal = values[key];

            if(newVal === undefined) {
                //console.warn('Object parameter '' + key + '' is undefined.');
                continue;
            }
            if(key in obj) {
                var curVal = obj[key];

                //massage strings into numbers
                if(typeof curVal === 'number' && typeof newVal === 'string') {
                    var n;
                    if(newVal.indexOf('0x') === 0) n = parseInt(newVal, 16);
                    else n = parseInt(newVal, 10);

                    if(!isNaN(n))
                        obj[key] = n;
                    /*else
                        console.warn('Object parameter '' + key + '' evaluated to NaN, using default. Value passed: ' + newVal);*/

                }
                //massage vectors
                else if(curVal instanceof Vector && newVal instanceof Array) {
                    curVal.set(parseFloat(newVal[0], 10) || 0, parseFloat(newVal[1], 10) || parseFloat(newVal[0], 10) || 0);
                } else if(curVal instanceof Vector && typeof newVal === 'string') {
                    var a = newVal.split(utils._arrayDelim, 2);
                    curVal.set(parseFloat(a[0], 10) || 0, parseFloat(a[1], 10) || parseFloat(a[0], 10) || 0);
                } else if(curVal instanceof Vector && typeof newVal === 'number') {
                    curVal.set(newVal, newVal);
                }
                //massage arrays
                else if(curVal instanceof Array && typeof newVal === 'string') {
                    obj[key] = newVal.split(utils._arrayDelim);
                    for(var i = 0, il = obj[key].length; i < il; ++i) {
                        var val = obj[key][i];
                        if(!isNaN(val)) obj[key][i] = parseFloat(val, 10);
                    }
                } else {
                    obj[key] = newVal;
                }
            }
        }

        return obj;
    },
    /**
     * From jQuery.extend, extends one object into another
     * taken straight from jQuery 2.0.3
     *
     * @method extend
     */
    extend: function() {
        var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        // Handle a deep copy situation
        if (typeof target === 'boolean') {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if (typeof target !== 'object' && typeof target !== 'function') {
            target = {};
        }

        // extend jQuery itself if only one argument is passed
        //if (length === i) {
        //    target = this;
        //    --i;
        //}

        for (; i < length; i++) {
            // Only deal with non-null/undefined values
            options = arguments[i];
            if (options !== null && options !== undefined) {
                // Extend the base object
                for (name in options) {
                    src = target[name];
                    copy = options[name];

                    // Prevent never-ending loop
                    if (target === copy) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (utils.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
                        if (copyIsArray) {
                            copyIsArray = false;
                            clone = src && Array.isArray(src) ? src : [];

                        } else {
                            clone = src && utils.isPlainObject(src) ? src : {};
                        }

                        // Never move original objects, clone them
                        target[name] = utils.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    },
    /**
     * From jQuery.isPlainObject, checks if an object is a plain object
     * taken straight from jQuery 2.0.3
     *
     * @method isPlainObject
     * @param obj {mixed} The object to test
     * @return {Boolean}
     */
    isPlainObject: function(obj) {
        // Not plain objects:
        // - Any object or value whose internal [[Class]] property is not "[object Object]"
        // - DOM nodes
        // - window
        if (typeof obj !== 'object' || obj.nodeType || obj === obj.window) {
            return false;
        }

        // Support: Firefox <20
        // The try/catch suppresses exceptions thrown when attempting to access
        // the "constructor" property of certain host objects, ie. |window.location|
        // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
        try {
            if (obj.constructor && !Object.hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')) {
                return false;
            }
        } catch(e) {
            return false;
        }

        // If the function hasn't returned already, we're confident that
        // |obj| is a plain object, created by {} or constructed with new Object
        return true;
    },
    /**
     * Get the DOM offset values of any given element
     *
     * @method getOffset
     * @param element {HTMLElement} The targeted element that we want to retrieve the offset
     * @return {Vector} The offset of the element
     */
    getOffset: function(element) {
        var box = element.getBoundingClientRect(),
            clientTop = element.clientTop || document.body.clientTop || 0,
            clientLeft = element.clientLeft || document.body.clientLeft || 0,
            scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop,
            scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft;

        return new Vector(
            box.left + scrollLeft - clientLeft,
            box.top + scrollTop - clientTop
        );
    },
    /**
     * Parses an array of numbers that represent a hitArea into the actual shape.
     *
     * For example: `[1, 1, 15]` is a Circle (`[x, y, radius]`); `[1, 1, 15, 15]` is a Rectangle
     * (`[x, y, width, height]`); and any length >= 5 is a polygon in the form `[x1, y1, x2, y2, ..., xN, yN]`.
     *
     * @method parseHitArea
     * @param value {Array<Number>} The array to parse
     * @return {Circle|Rectangle|Polygon} The parsed out shape
     */
    parseHitArea: function(hv) {
        var ha;

        //odd number of values
        if(hv.length % 2 !== 0 && hv.length !== 3) {
            throw new RangeError('Strange number of values for hitArea! Should be a flat array of values, like: [x,y,r] for a circle, [x,y,w,h] for a rectangle, or [x,y,x,y,...] for other polygons.');
        }

        //a circle x,y,r
        if(hv.length === 3) {
            ha = new Circle(hv[0], hv[1], hv[2]);
        }
        //a rectangle x,y,w,h
        else if(hv.length === 4) {
            ha = new Rectangle(hv[0], hv[1], hv[2], hv[3]);
        }
        //generic polygon
        else {
            ha = new Polygon(0, 0, hv);
        }

        return ha;
    },
    /**
     * Parses an object of string properties into potential javascript types. First it attempts to
     * convert to a number, if that fails it checks for the string 'true' or 'false' and changes it
     * to the actual Boolean value, then it attempts to parse a string as JSON.
     *
     * @method parseTiledProperties
     * @param value {Array<Number>} The array to parse
     * @return {Circle|Rectangle|Polygon} The parsed out shape
     */
    parseTiledProperties: function(obj) {
        if(!obj || obj.__tiledparsed)
            return obj;

        for(var k in obj) {
            var v = obj[k],
                n = parseFloat(v, 10);

            //try to massage numbers
            if(n === 0 || n)
                obj[k] = n;
            //true values
            else if(v === 'true')
                obj[k] = true;
            //false values
            else if(v === 'false')
                obj[k] = false;
            //anything else is either a string or json, try json
            else {
                try{
                    v = JSON.parse(v);
                    obj[k] = v;
                } catch(e) {}
            }
        }

        //after parsing, check some other things
        if(obj.hitArea)
            obj.hitArea = utils.parseHitArea(obj.hitArea);

        if(obj.body === 'static' || obj.sensor) {
            obj.mass = Infinity;
            obj.inertia = Infinity;
        }

        obj.__tiledparsed = true;

        return obj;
    },
    _logger: window.console || {},
    /**
     * Safe way to log to console, if console.log doesn't exist nothing happens.
     *
     * @method log
     */
    log: function() {
        if(utils._logger.log)
            utils._logger.log.apply(utils._logger, arguments);
    },
    /**
     * Safe way to warn to console, if console.warn doesn't exist nothing happens.
     *
     * @method warn
     */
    warn: function() {
        if(utils._logger.warn)
            utils._logger.warn.apply(utils._logger, arguments);
    },
    /**
     * Safe way to error to console, if console.error doesn't exist nothing happens.
     *
     * @method error
     */
    error: function() {
        if(utils._logger.error)
            utils._logger.error.apply(utils._logger, arguments);
    }
};

/**
 * Parses an XML string into a Document object. Will use window.DOMParser
 * if available, falling back to Microsoft.XMLDOM ActiveXObject in IE.
 *
 * Eventually, it would be nice to include a node.js alternative as well
 * for running in that environment.
 *
 * @method parseXML
 * @param xmlStr {String} The xml string to parse
 * @return {Document} An XML Document
 */
//XML Parser in window
if(typeof window.DOMParser !== 'undefined') {
    utils.parseXML = function(xmlStr) {
        return (new window.DOMParser()).parseFromString(xmlStr, 'text/xml');
    };
}
//IE specific XML parser
else if(typeof window.ActiveXObject !== 'undefined' && new window.ActiveXObject('Microsoft.XMLDOM')) {
    utils.parseXML = function(xmlStr) {
        var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
        xmlDoc.async = 'false';
        xmlDoc.loadXML(xmlStr);
        return xmlDoc;
    };
}
//node.js environment
/*else if(__isNode) {
    utils.parseXML = function(xmlStr) {
        var DOMParser = require('xmldom').DOMParser;
        return (new DOMParser()).parseFromString(xmlStr, "text/xml");
    };
}*/
// no parser available
else {
    utils.warn('XML parser not available, trying to parse any XML will result in an error.');
    utils.parseXML = function() {
        throw new Error('Trying to parse XML, but not XML parser is available in this environment');
    };
}

module.exports = utils;
<commit_msg>fix issue with new way XHR request works<commit_after>var Vector = require('../math/Vector'),
    Circle = require('../geom/Circle'),
    Rectangle = require('../geom/Rectangle'),
    Polygon = require('../geom/Polygon');

/**
 * The grapefruit utility object, used for misc functions used throughout the code base
 *
 * @class utils
 * @extends Object
 * @static
 */
var utils = {
    _arrayDelim: /[|,]/,
    /**
     * An empty function that performs no action
     *
     * @method noop
     */
    noop: function() {},
    /**
     * Gets the absolute url from a relative one
     *
     * @method getAbsoluteUrl
     * @param url {String} The relative url to translate into absolute
     * @return {String} The absolute url (fully qualified)
     */
    getAbsoluteUrl: function(url) {
        var a = document.createElement('a');
        a.href = url;
        return a.href;
    },
    /**
     * Performs an ajax request, and manages the callbacks passed in
     *
     * @method ajax
     * @param settings {Object} The settings of the ajax request, similar to jQuery's ajax function
     * @return {XMLHttpRequest|ActiveXObject} An XHR object
     */
    ajax: function(sets) {
        //base settings
        sets = sets || {};
        sets.method = sets.method || 'GET';
        sets.dataType = sets.dataType || 'text';

        if(!sets.url)
            throw new TypeError('Undefined URL passed to ajax');

        //callbacks
        sets.progress = sets.progress || utils.noop;
        sets.load = sets.load || utils.noop;
        sets.error = sets.error || utils.noop;
        sets.abort = sets.abort || utils.noop;
        sets.complete = sets.complete || utils.noop;

        var xhr = utils.createAjaxRequest(),
            protocol = utils.getAbsoluteUrl(sets.url).split('/')[0];

        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                var res = xhr.response || xhr.responseText,
                    err = null;

                //The 'file:' protocol doesn't give response codes
                if(protocol !== 'file:' && xhr.status !== 200)
                    err = 'Non-200 status code returned: ' + xhr.status;

                if(!err && typeof res === 'string') {
                    if(sets.dataType === 'json') {
                        try {
                            res = JSON.parse(res);
                        } catch(e) {
                            err = e;
                        }
                    } else if(sets.dataType === 'xml') {
                        try {
                            res = utils.parseXML(res);
                        } catch(e) {
                            err = e;
                        }
                    }
                }

                if(err) {
                    if(sets.error) sets.error.call(xhr, err);
                } else {
                    if(sets.load) sets.load.call(xhr, res);
                }
            }
        };

        xhr.open(sets.method, sets.url, true);

        //chrome doesn't support json responseType, some browsers choke on XML type
        if(sets.dataType === 'arraybuffer')
            xhr.responseType = 'arraybuffer';
        else
            xhr.responseType = 'text';

        xhr.send();

        return xhr;
    },
    /**
     * Wraps XMLHttpRequest in a cross-browser way.
     *
     * @method AjaxRequest
     * @return {XMLHttpRequest|ActiveXObject}
     */
    //from pixi.js
    createAjaxRequest: function() {
        //activeX versions to check for in IE
        var activexmodes = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'];

        //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
        if(window.ActiveXObject) {
            for(var i=0; i<activexmodes.length; i++) {
                try {
                    return new window.ActiveXObject(activexmodes[i]);
                }
                catch(e) {
                    //suppress error
                }
            }
        }
        // if Mozilla, Safari etc
        else if(window.XMLHttpRequest) {
            return new XMLHttpRequest();
        }
        else {
            return false;
        }
    },
    /**
     * This will take values and override the passed obj's properties with those values.
     * The difference from a normal object extend is that this will try to massage the passed
     * value into the same type as the object's property. Also if the key for the value is not
     * in the original object, it is not copied.
     *
     * @method setValues
     * @param obj {Object} The object to extend the values into
     * @param values {Object} The values to put into the object
     * @return {Object} returns the updated object
     * @example
     *      var obj = { vec: new Vector(), arr: [] },
     *          vals = { vec: '2|5', arr: '5|10|11' };
     *      utils.setValues(obj, vals);
     *      //now obj is:
     *      // { vec: Vector(2, 5), arr: [5, 10, 11] }
     *
     */
    //similar to https://github.com/mrdoob/three.js/blob/master/src/materials/Material.js#L42
    setValues: function(obj, values) {
        if(!values) return;

        for(var key in values) {
            var newVal = values[key];

            if(newVal === undefined) {
                //console.warn('Object parameter '' + key + '' is undefined.');
                continue;
            }
            if(key in obj) {
                var curVal = obj[key];

                //massage strings into numbers
                if(typeof curVal === 'number' && typeof newVal === 'string') {
                    var n;
                    if(newVal.indexOf('0x') === 0) n = parseInt(newVal, 16);
                    else n = parseInt(newVal, 10);

                    if(!isNaN(n))
                        obj[key] = n;
                    /*else
                        console.warn('Object parameter '' + key + '' evaluated to NaN, using default. Value passed: ' + newVal);*/

                }
                //massage vectors
                else if(curVal instanceof Vector && newVal instanceof Array) {
                    curVal.set(parseFloat(newVal[0], 10) || 0, parseFloat(newVal[1], 10) || parseFloat(newVal[0], 10) || 0);
                } else if(curVal instanceof Vector && typeof newVal === 'string') {
                    var a = newVal.split(utils._arrayDelim, 2);
                    curVal.set(parseFloat(a[0], 10) || 0, parseFloat(a[1], 10) || parseFloat(a[0], 10) || 0);
                } else if(curVal instanceof Vector && typeof newVal === 'number') {
                    curVal.set(newVal, newVal);
                }
                //massage arrays
                else if(curVal instanceof Array && typeof newVal === 'string') {
                    obj[key] = newVal.split(utils._arrayDelim);
                    for(var i = 0, il = obj[key].length; i < il; ++i) {
                        var val = obj[key][i];
                        if(!isNaN(val)) obj[key][i] = parseFloat(val, 10);
                    }
                } else {
                    obj[key] = newVal;
                }
            }
        }

        return obj;
    },
    /**
     * From jQuery.extend, extends one object into another
     * taken straight from jQuery 2.0.3
     *
     * @method extend
     */
    extend: function() {
        var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        // Handle a deep copy situation
        if (typeof target === 'boolean') {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if (typeof target !== 'object' && typeof target !== 'function') {
            target = {};
        }

        // extend jQuery itself if only one argument is passed
        //if (length === i) {
        //    target = this;
        //    --i;
        //}

        for (; i < length; i++) {
            // Only deal with non-null/undefined values
            options = arguments[i];
            if (options !== null && options !== undefined) {
                // Extend the base object
                for (name in options) {
                    src = target[name];
                    copy = options[name];

                    // Prevent never-ending loop
                    if (target === copy) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (utils.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
                        if (copyIsArray) {
                            copyIsArray = false;
                            clone = src && Array.isArray(src) ? src : [];

                        } else {
                            clone = src && utils.isPlainObject(src) ? src : {};
                        }

                        // Never move original objects, clone them
                        target[name] = utils.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    },
    /**
     * From jQuery.isPlainObject, checks if an object is a plain object
     * taken straight from jQuery 2.0.3
     *
     * @method isPlainObject
     * @param obj {mixed} The object to test
     * @return {Boolean}
     */
    isPlainObject: function(obj) {
        // Not plain objects:
        // - Any object or value whose internal [[Class]] property is not "[object Object]"
        // - DOM nodes
        // - window
        if (typeof obj !== 'object' || obj.nodeType || obj === obj.window) {
            return false;
        }

        // Support: Firefox <20
        // The try/catch suppresses exceptions thrown when attempting to access
        // the "constructor" property of certain host objects, ie. |window.location|
        // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
        try {
            if (obj.constructor && !Object.hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')) {
                return false;
            }
        } catch(e) {
            return false;
        }

        // If the function hasn't returned already, we're confident that
        // |obj| is a plain object, created by {} or constructed with new Object
        return true;
    },
    /**
     * Get the DOM offset values of any given element
     *
     * @method getOffset
     * @param element {HTMLElement} The targeted element that we want to retrieve the offset
     * @return {Vector} The offset of the element
     */
    getOffset: function(element) {
        var box = element.getBoundingClientRect(),
            clientTop = element.clientTop || document.body.clientTop || 0,
            clientLeft = element.clientLeft || document.body.clientLeft || 0,
            scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop,
            scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft;

        return new Vector(
            box.left + scrollLeft - clientLeft,
            box.top + scrollTop - clientTop
        );
    },
    /**
     * Parses an array of numbers that represent a hitArea into the actual shape.
     *
     * For example: `[1, 1, 15]` is a Circle (`[x, y, radius]`); `[1, 1, 15, 15]` is a Rectangle
     * (`[x, y, width, height]`); and any length >= 5 is a polygon in the form `[x1, y1, x2, y2, ..., xN, yN]`.
     *
     * @method parseHitArea
     * @param value {Array<Number>} The array to parse
     * @return {Circle|Rectangle|Polygon} The parsed out shape
     */
    parseHitArea: function(hv) {
        var ha;

        //odd number of values
        if(hv.length % 2 !== 0 && hv.length !== 3) {
            throw new RangeError('Strange number of values for hitArea! Should be a flat array of values, like: [x,y,r] for a circle, [x,y,w,h] for a rectangle, or [x,y,x,y,...] for other polygons.');
        }

        //a circle x,y,r
        if(hv.length === 3) {
            ha = new Circle(hv[0], hv[1], hv[2]);
        }
        //a rectangle x,y,w,h
        else if(hv.length === 4) {
            ha = new Rectangle(hv[0], hv[1], hv[2], hv[3]);
        }
        //generic polygon
        else {
            ha = new Polygon(0, 0, hv);
        }

        return ha;
    },
    /**
     * Parses an object of string properties into potential javascript types. First it attempts to
     * convert to a number, if that fails it checks for the string 'true' or 'false' and changes it
     * to the actual Boolean value, then it attempts to parse a string as JSON.
     *
     * @method parseTiledProperties
     * @param value {Array<Number>} The array to parse
     * @return {Circle|Rectangle|Polygon} The parsed out shape
     */
    parseTiledProperties: function(obj) {
        if(!obj || obj.__tiledparsed)
            return obj;

        for(var k in obj) {
            var v = obj[k],
                n = parseFloat(v, 10);

            //try to massage numbers
            if(n === 0 || n)
                obj[k] = n;
            //true values
            else if(v === 'true')
                obj[k] = true;
            //false values
            else if(v === 'false')
                obj[k] = false;
            //anything else is either a string or json, try json
            else {
                try{
                    v = JSON.parse(v);
                    obj[k] = v;
                } catch(e) {}
            }
        }

        //after parsing, check some other things
        if(obj.hitArea)
            obj.hitArea = utils.parseHitArea(obj.hitArea);

        if(obj.body === 'static' || obj.sensor) {
            obj.mass = Infinity;
            obj.inertia = Infinity;
        }

        obj.__tiledparsed = true;

        return obj;
    },
    _logger: window.console || {},
    /**
     * Safe way to log to console, if console.log doesn't exist nothing happens.
     *
     * @method log
     */
    log: function() {
        if(utils._logger.log)
            utils._logger.log.apply(utils._logger, arguments);
    },
    /**
     * Safe way to warn to console, if console.warn doesn't exist nothing happens.
     *
     * @method warn
     */
    warn: function() {
        if(utils._logger.warn)
            utils._logger.warn.apply(utils._logger, arguments);
    },
    /**
     * Safe way to error to console, if console.error doesn't exist nothing happens.
     *
     * @method error
     */
    error: function() {
        if(utils._logger.error)
            utils._logger.error.apply(utils._logger, arguments);
    }
};

/**
 * Parses an XML string into a Document object. Will use window.DOMParser
 * if available, falling back to Microsoft.XMLDOM ActiveXObject in IE.
 *
 * Eventually, it would be nice to include a node.js alternative as well
 * for running in that environment.
 *
 * @method parseXML
 * @param xmlStr {String} The xml string to parse
 * @return {Document} An XML Document
 */
//XML Parser in window
if(typeof window.DOMParser !== 'undefined') {
    utils.parseXML = function(xmlStr) {
        return (new window.DOMParser()).parseFromString(xmlStr, 'text/xml');
    };
}
//IE specific XML parser
else if(typeof window.ActiveXObject !== 'undefined' && new window.ActiveXObject('Microsoft.XMLDOM')) {
    utils.parseXML = function(xmlStr) {
        var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
        xmlDoc.async = 'false';
        xmlDoc.loadXML(xmlStr);
        return xmlDoc;
    };
}
//node.js environment
/*else if(__isNode) {
    utils.parseXML = function(xmlStr) {
        var DOMParser = require('xmldom').DOMParser;
        return (new DOMParser()).parseFromString(xmlStr, "text/xml");
    };
}*/
// no parser available
else {
    utils.warn('XML parser not available, trying to parse any XML will result in an error.');
    utils.parseXML = function() {
        throw new Error('Trying to parse XML, but not XML parser is available in this environment');
    };
}

module.exports = utils;
<|endoftext|>