<commit_before>
var _ = require('./underscore_ext');
var Rx = require('./rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./views/Legend');
var BandLegend = require('./views/BandLegend');
import PureComponent from './PureComponent';
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {rxEvents} = require('./react-utils');
var {drawHeatmap} = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var posString = p => `${p.chrom}:${util.addCommas(p.chromstart)}-${util.addCommas(p.chromend)}`;
//gb position string of the segment with 15bp extra on each side, centered at segment
var posRegionString = p => {
	var pad = Math.round((p.chromend - p.chromstart) / 2);
	return `${p.chrom}:${util.addCommas(p.chromstart - pad)}-${util.addCommas(p.chromend + pad)}`;
};
var gbURL = (assembly, pos, hgtCustomtext, hubUrl) => {
	var assemblyString = encodeURIComponent(assembly),
		positionString = encodeURIComponent(posString(pos)),
		regionString = encodeURIComponent(posRegionString(pos));
	return `http://genome.ucsc.edu/cgi-bin/hgTracks?db=${assemblyString}
			&highlight=${assemblyString}.${positionString}
			&position=${regionString}
			${hubUrl ? `&hubUrl=${hubUrl}` : ''}
			${hgtCustomtext ? `&hgt.customText=${hgtCustomtext}` : ''}`;
};

function sigTooltip(genes, missing, val) {
	let visibleCount = 2,
		moreCount = genes.length - visibleCount,
		visible = genes.slice(0, visibleCount),
		moreLabel = moreCount > 0 ? ` + ${moreCount} more` : '',
		missingLabel = missing.length ? `(missing terms: ${missing.join(' ')}) ` : '';
	return [['sig',
			 // label on hover
			 `signature ${missingLabel}(= ${visible.join(' + ')}${moreLabel})`,
			 // label on freeze
			 `signature ${missingLabel}(= ${genes.join(' + ')})`,
			 val]];
}

function tooltip(id, heatmap, avg, assembly, hgtCustomtext, hubUrl,
	fields, sampleFormat, fieldFormat, codes, position, width, zoom, samples, {genes, missing}, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / width)),
		pos = _.get(position, fieldIndex),
		field = fields[fieldIndex],
		sig = !!genes;

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.get(codes, val),
		label = sig ? 'signature' : fieldFormat(field);

	val = code ? code : prec(val);
	let mean = avg && prec(avg.mean[fieldIndex]),
		median = avg && prec(avg.median[fieldIndex]);

	return {
		sampleID: sampleFormat(sampleID),
		id,
		fieldIndex,
		rows: [
			sig ? sigTooltip(genes, missing, val) : [['labelValue', label, val]],
			...(pos && assembly ? [[['url', `${assembly} ${posString(pos)}`, gbURL(assembly, pos, hgtCustomtext, hubUrl)]]] : []),
			...(!codes && (mean !== 'NA') && (median !== 'NA') ? [[['label', `Mean: ${mean} Median: ${median}`]]] : [])]
	};
}

//
// Legends
//

function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: []};
	}
	// only finds categories for the current data in the column
	var data = _.reject(_.uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		colors = _.map(data, colorScale),
		labels = _.map(data, d => codes[d]);

	return {colors: colors, labels: labels, titles: labels};
}

// might want to use <wbr> here, instead, so cut & paste work better, but that
// will require a recursive split/flatmap to inject the <wbr> elements.
var addWordBreaks = str => str.replace(/([_/])/g, '\u200B$1\u200B');

function renderFloatLegend(props) {
	var {units, colors, data, vizSettings} = props;

	if (_.isEmpty(data)) {
		return null;
	}

	var colorSpec = _.max(colors, colorList => _.uniq(colorList.slice(Math.ceil(colorList.length / 2.0))).length);

	if (colorSpec[0] === 'no-data') {
		return null;
	}

	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		footnotes = units && units[0] ? [{addWordBreaks(units[0])}] : null,
		hasViz = !isNaN(_.getIn(vizSettings, ['min'])),
		multiScaled = colors && colors.length > 1 && !hasViz;

	return (
		<BandLegend
			multiScaled={multiScaled}
			range={{min: _.first(values), max: _.last(values)}}
			colorScale={scale}
			footnotes={footnotes}
			width={50}
			height={20}/>);
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes);
	} else {
		return ;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(class extends PureComponent {
	render() {
		var {column} = this.props,
			{units, heatmap, colors, valueType, vizSettings, defaultNormalization, codes} = column,
			props = {
				units,
				colors,
				vizSettings,
				defaultNormalization,
				data: heatmap,
				coded: valueType === 'coded',
				codes: codes,
			};

		return (props.coded ? renderCodedLegend :
			renderFloatLegend)(props);
	}
});

//
// plot rendering
//

var HeatmapColumn = hotOrNot(//
// plot rendering
//

class extends PureComponent {
	componentWillMount() {
		var events = rxEvents(this, 'mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = events.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.flatMap(() => {
				return events.mousemove
					.takeUntil(events.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true
					})) // look up current data
					.concat(Rx.Observable.of({open: false}));
			}).subscribe(this.props.tooltip);
	}

	componentWillUnmount() {
		this.ttevents.unsubscribe();
	}

	tooltip = (ev) => {
		var {samples, data, column, zoom, sampleFormat, fieldFormat, id} = this.props,
			{codes, avg} = column,
			// support data.req.position for old bookmarks.
			position = column.position || _.getIn(data, ['req', 'position']),
			{assembly, fields, heatmap, width, dataset, missing, signature} = column,
			[, , genes] = signature || [],
			hgtCustomtext = _.getIn(dataset, ['probemapMeta', 'hgt.customtext']),
			hubUrl = _.getIn(dataset, ['probemapMeta', 'huburl']);
		return tooltip(id, heatmap, avg, assembly, hgtCustomtext, hubUrl, fields, sampleFormat, fieldFormat(id),
			codes, position, width, zoom, samples, {genes, missing}, ev);
	};

	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	// Might also want to copy fields into 'display', so we can drop req probes
	render() {
		var {column, zoom} = this.props,
			{heatmap, colors, codes} = column;

		return (
			<CanvasDrawing
					ref='plot'
					draw={drawHeatmap}
					wrapperProps={{
						className: 'Tooltip-target',
						onMouseMove: this.on.mousemove,
						onMouseOut: this.on.mouseout,
						onMouseOver: this.on.mouseover,
						onClick: this.props.onClick
					}}
					codes={codes}
					width={_.get(column, 'width')}
					zoom={zoom}
					colors={colors}
					heatmapData={heatmap}/>);
	}
});

var getColumn = props => <HeatmapColumn {...props} />;

widgets.column.add("probes", getColumn);
widgets.column.add("geneProbes", getColumn);
widgets.column.add("genes", getColumn);
widgets.column.add("clinical", getColumn);

var getLegend = props => <HeatmapLegend {...props} />;

widgets.legend.add('probes', getLegend);
widgets.legend.add('geneProbes', getLegend);
widgets.legend.add('genes', getLegend);
widgets.legend.add('clinical', getLegend);
<commit_msg>Fix filter column tooltip<commit_after>
var _ = require('./underscore_ext');
var Rx = require('./rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./views/Legend');
var BandLegend = require('./views/BandLegend');
import PureComponent from './PureComponent';
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {rxEvents} = require('./react-utils');
var {drawHeatmap} = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var posString = p => `${p.chrom}:${util.addCommas(p.chromstart)}-${util.addCommas(p.chromend)}`;
//gb position string of the segment with 15bp extra on each side, centered at segment
var posRegionString = p => {
	var pad = Math.round((p.chromend - p.chromstart) / 2);
	return `${p.chrom}:${util.addCommas(p.chromstart - pad)}-${util.addCommas(p.chromend + pad)}`;
};
var gbURL = (assembly, pos, hgtCustomtext, hubUrl) => {
	var assemblyString = encodeURIComponent(assembly),
		positionString = encodeURIComponent(posString(pos)),
		regionString = encodeURIComponent(posRegionString(pos));
	return `http://genome.ucsc.edu/cgi-bin/hgTracks?db=${assemblyString}
			&highlight=${assemblyString}.${positionString}
			&position=${regionString}
			${hubUrl ? `&hubUrl=${hubUrl}` : ''}
			${hgtCustomtext ? `&hgt.customText=${hgtCustomtext}` : ''}`;
};

function sigTooltip(genes, missing, val) {
	let visibleCount = 2,
		moreCount = genes.length - visibleCount,
		visible = genes.slice(0, visibleCount),
		moreLabel = moreCount > 0 ? ` + ${moreCount} more` : '',
		missingLabel = _.get(missing, 'length') ? `(missing terms: ${missing.join(' ')}) ` : '';
	return [['sig',
			 // label on hover
			 `signature ${missingLabel}(= ${visible.join(' + ')}${moreLabel})`,
			 // label on freeze
			 `signature ${missingLabel}(= ${genes.join(' + ')})`,
			 val]];
}

function tooltip(id, heatmap, avg, assembly, hgtCustomtext, hubUrl,
	fields, sampleFormat, fieldFormat, codes, position, width, zoom, samples, {genes, missing}, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / width)),
		pos = _.get(position, fieldIndex),
		field = fields[fieldIndex],
		sig = !!genes;

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.get(codes, val),
		label = sig ? 'signature' : fieldFormat(field);

	val = code ? code : prec(val);
	let mean = avg && prec(avg.mean[fieldIndex]),
		median = avg && prec(avg.median[fieldIndex]);

	return {
		sampleID: sampleFormat(sampleID),
		id,
		fieldIndex,
		rows: [
			sig ? sigTooltip(genes, missing, val) : [['labelValue', label, val]],
			...(pos && assembly ? [[['url', `${assembly} ${posString(pos)}`, gbURL(assembly, pos, hgtCustomtext, hubUrl)]]] : []),
			...(!codes && (mean !== 'NA') && (median !== 'NA') ? [[['label', `Mean: ${mean} Median: ${median}`]]] : [])]
	};
}

//
// Legends
//

function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: []};
	}
	// only finds categories for the current data in the column
	var data = _.reject(_.uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		colors = _.map(data, colorScale),
		labels = _.map(data, d => codes[d]);

	return {colors: colors, labels: labels, titles: labels};
}

// might want to use <wbr> here, instead, so cut & paste work better, but that
// will require a recursive split/flatmap to inject the <wbr> elements.
var addWordBreaks = str => str.replace(/([_/])/g, '\u200B$1\u200B');

function renderFloatLegend(props) {
	var {units, colors, data, vizSettings} = props;

	if (_.isEmpty(data)) {
		return null;
	}

	var colorSpec = _.max(colors, colorList => _.uniq(colorList.slice(Math.ceil(colorList.length / 2.0))).length);

	if (colorSpec[0] === 'no-data') {
		return null;
	}

	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		footnotes = units && units[0] ? [{addWordBreaks(units[0])}] : null,
		hasViz = !isNaN(_.getIn(vizSettings, ['min'])),
		multiScaled = colors && colors.length > 1 && !hasViz;

	return (
		<BandLegend
			multiScaled={multiScaled}
			range={{min: _.first(values), max: _.last(values)}}
			colorScale={scale}
			footnotes={footnotes}
			width={50}
			height={20}/>);
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes);
	} else {
		return ;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(class extends PureComponent {
	render() {
		var {column} = this.props,
			{units, heatmap, colors, valueType, vizSettings, defaultNormalization, codes} = column,
			props = {
				units,
				colors,
				vizSettings,
				defaultNormalization,
				data: heatmap,
				coded: valueType === 'coded',
				codes: codes,
			};

		return (props.coded ? renderCodedLegend :
			renderFloatLegend)(props);
	}
});

//
// plot rendering
//

var HeatmapColumn = hotOrNot(//
// plot rendering
//

class extends PureComponent {
	componentWillMount() {
		var events = rxEvents(this, 'mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = events.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.flatMap(() => {
				return events.mousemove
					.takeUntil(events.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true
					})) // look up current data
					.concat(Rx.Observable.of({open: false}));
			}).subscribe(this.props.tooltip);
	}

	componentWillUnmount() {
		this.ttevents.unsubscribe();
	}

	tooltip = (ev) => {
		var {samples, data, column, zoom, sampleFormat, fieldFormat, id} = this.props,
			{codes, avg} = column,
			// support data.req.position for old bookmarks.
			position = column.position || _.getIn(data, ['req', 'position']),
			{assembly, fields, heatmap, width, dataset, missing, signature} = column,
			[, , genes] = signature || [],
			hgtCustomtext = _.getIn(dataset, ['probemapMeta', 'hgt.customtext']),
			hubUrl = _.getIn(dataset, ['probemapMeta', 'huburl']);
		return tooltip(id, heatmap, avg, assembly, hgtCustomtext, hubUrl, fields, sampleFormat, fieldFormat(id),
			codes, position, width, zoom, samples, {genes, missing}, ev);
	};

	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	// Might also want to copy fields into 'display', so we can drop req probes
	render() {
		var {column, zoom} = this.props,
			{heatmap, colors, codes} = column;

		return (
			<CanvasDrawing
					ref='plot'
					draw={drawHeatmap}
					wrapperProps={{
						className: 'Tooltip-target',
						onMouseMove: this.on.mousemove,
						onMouseOut: this.on.mouseout,
						onMouseOver: this.on.mouseover,
						onClick: this.props.onClick
					}}
					codes={codes}
					width={_.get(column, 'width')}
					zoom={zoom}
					colors={colors}
					heatmapData={heatmap}/>);
	}
});

var getColumn = props => <HeatmapColumn {...props} />;

widgets.column.add("probes", getColumn);
widgets.column.add("geneProbes", getColumn);
widgets.column.add("genes", getColumn);
widgets.column.add("clinical", getColumn);

var getLegend = props => <HeatmapLegend {...props} />;

widgets.legend.add('probes', getLegend);
widgets.legend.add('geneProbes', getLegend);
widgets.legend.add('genes', getLegend);
widgets.legend.add('clinical', getLegend);
<|endoftext|>


<commit_before>/**
 * Created by s.neidig on 16/07/17.
 */

"use strict";

var Bots = Bots || {};

Bots.Minimap = function (state, name, position, properties) {
    const x = state.game.width - Bots.minimapPadding.x - Bots.minimapWidth;
    const y = Bots.minimapPadding.y;

    Phaser.Graphics.call(this, state.game, x, y);

    this.state = state;
    this.name = name;
    this.alpha = properties.alpha;
    this.fixedToCamera = true;
    this.properties = properties;
    this.state.prefabs[name] = this;

    if (properties.group) {
        this.state.groups[properties.group].add(this);
    }
};

Bots.Minimap.prototype = Object.create(Phaser.Graphics.prototype);
Bots.Minimap.prototype.constructor = Bots.Minimap;

Bots.Minimap.prototype.update = function () {
    this.clear();

    this.drawRectangle(0, 0, Bots.minimapWidth, Bots.minimapHeight);

    this.state.groups.robots.forEachAlive((robot) => this.drawMinimapObject(robot));
    this.state.groups.chests.forEachAlive((chest) => this.drawMinimapObject(chest, 0x00ff00));
}

Bots.Minimap.prototype.drawRectangle = function (x, y, w, h, color = 0x000000) {
    this.beginFill(color);
    this.drawRect(x, y, w, h);
    this.endFill();
}

Bots.Minimap.prototype.drawMinimapObject = function (object, color = 0xffffff) {
    const x = object.world.x + Bots.worldSize.x / 2;
    const y = object.world.y + Bots.worldSize.y / 2;

    const mapX = (x / (1.0 * Bots.worldSize.x)) * Bots.minimapWidth;
    const mapY = (y / (1.0 * Bots.worldSize.y)) * Bots.minimapHeight;

    this.drawRectangle(mapX - 1, mapY - 1, 3, 3, color);
}<commit_msg>Set custom minimap color for player<commit_after>/**
 * Created by s.neidig on 16/07/17.
 */

"use strict";

var Bots = Bots || {};

Bots.Minimap = function (state, name, position, properties) {
    const x = state.game.width - Bots.minimapPadding.x - Bots.minimapWidth;
    const y = Bots.minimapPadding.y;

    Phaser.Graphics.call(this, state.game, x, y);

    this.state = state;
    this.name = name;
    this.alpha = properties.alpha;
    this.fixedToCamera = true;
    this.properties = properties;
    this.state.prefabs[name] = this;

    if (properties.group) {
        this.state.groups[properties.group].add(this);
    }
};

Bots.Minimap.prototype = Object.create(Phaser.Graphics.prototype);
Bots.Minimap.prototype.constructor = Bots.Minimap;

Bots.Minimap.prototype.update = function () {
    this.clear();

    this.drawRectangle(0, 0, Bots.minimapWidth, Bots.minimapHeight);

    this.state.groups.robots.forEachAlive((robot) => this.drawMinimapObject(robot, robot.human ? 0x0000ff : 0xffffff));
    this.state.groups.chests.forEachAlive((chest) => this.drawMinimapObject(chest, 0x00ff00));
}

Bots.Minimap.prototype.drawRectangle = function (x, y, w, h, color = 0x000000) {
    this.beginFill(color);
    this.drawRect(x, y, w, h);
    this.endFill();
}

Bots.Minimap.prototype.drawMinimapObject = function (object, color = 0xffffff) {
    const x = object.world.x + Bots.worldSize.x / 2;
    const y = object.world.y + Bots.worldSize.y / 2;

    const mapX = (x / (1.0 * Bots.worldSize.x)) * Bots.minimapWidth;
    const mapY = (y / (1.0 * Bots.worldSize.y)) * Bots.minimapHeight;

    this.drawRectangle(mapX - 1, mapY - 1, 3, 3, color);
}<|endoftext|>


<commit_before>$(document).bind("ghp:ready", function () {
    $("a.message:contains('…')").each(function () {
        var $commit = $(this).parent(".commit-title").parent();
        if (!$commit.length) {
            $commit = $(this).closest(".commit-message");
            if (!$commit.length) {
                return;
            }
        }
        var $desc = $commit.children(".commit-desc").children("pre");

        // set full title
        var preTitle = $(this).text().slice(0, -1);
        var postTitle = /^….*$/m.exec($desc.text())[0].slice(1);
        $(this).text(preTitle + postTitle);

        // remove fist line with breaks from description
        $desc.text($desc.text().replace(/^.*$/m, "").replace(/^\n+/, ""));
        if ($desc.text() == "") {
            // hide ellipsis button
            $commit.find(".hidden-text-expander").hide();
        }
    });
});
<commit_msg>Fix: Error in not-expandable commit message that contains '...' normally<commit_after>$(document).bind("ghp:ready", function () {
    $("a.message:contains('…')").each(function () {
        var $commit = $(this).parent(".commit-title").parent();
        if (!$commit.length) {
            $commit = $(this).closest(".commit-message");
            if (!$commit.length) {
                return;
            }
        }
        var $desc = $commit.children(".commit-desc").children("pre");
        if (!$desc.text()) {
            return;
        }

        // set full title
        var preTitle = $(this).text().slice(0, -1);
        var postTitle = /^….*$/m.exec($desc.text())[0].slice(1);
        $(this).text(preTitle + postTitle);

        // remove fist line with breaks from description
        $desc.text($desc.text().replace(/^.*$/m, "").replace(/^\n+/, ""));
        if ($desc.text() == "") {
            // hide ellipsis button
            $commit.find(".hidden-text-expander").hide();
        }
    });
});
<|endoftext|>


<commit_before>import presage from 'presage';
import request from 'request';

export default url => {
    const {
        callbackFunction,
        promise
    } = presage.promiseWithCallback();

    request(url, (error, response, body) => {
        if (error) {
            callbackFunction(error);
            return;
        }

        callbackFunction(null, {
            body,
            response
        });
    });

    return promise;
};
<commit_msg>Properly document request-promise with JSDoc<commit_after>import presage from 'presage';
import request from 'request';

/**
 * A Promise-based wrapper for `request`.
 * @arg {string} url An HTTP url.
 * @returns {Promise<Object>} An object containing the response object and response body.
 */
export default url => {
    const {
        callbackFunction,
        promise
    } = presage.promiseWithCallback();

    request(url, (error, response, body) => {
        if (error) {
            callbackFunction(error);
            return;
        }

        callbackFunction(null, {
            body,
            response
        });
    });

    return promise;
};
<|endoftext|>


<commit_before>/**
 * An abstract class for all smellable objects.
 */
class SmellableObject extends Collider {

	/**
	* Creates a smellable object
	* @constructor
	* @abstract
	* @param {object} canvas - The canvas used to draw.
    * @param {object} position - 2D position of where the object shall be created (if no collision occurs).
    * @param {number} size - Size of the objects collider.
    * @param {number} sizeSmellingFactor - Size of the objects smell.
    * @param {Objects[]} collisionObjs - Objects in simulation that can collide with this.
    * @param {number} [rotation=0]rotation - ID of the ants hive.
	*/
	constructor(canvas, position, size, sizeSmellingFactor, collisionObjs, rotation = 0){
		if (new.target === SmellableObject) {
			throw new TypeError("Cannot construct SmellableObject instances directly");
		}
		super(canvas, position, ShapeType.CIRCLE, size, rotation, collisionObjs);
		this.sizeSmellingFactor = sizeSmellingFactor;
	}

	getSmellDistance(){
		var smellingDistance = this.getSize() * this.sizeSmellingFactor;
		return smellingDistance;
	}

	canBeSmelledBy(ant)
	{
		var distance = getDistance(this.getPosition(), ant.getPosition());
		return (distance - this.getSmellDistance() - ant.getSmellingDistance() <= 0);
	}

	smellPositionFrom(ant)
	{
		if (!this.canBeSmelledBy(ant)){
			// should not be able to smell it.... did someone cheat here?!
			return math.matrix([rand(-1000,1000), rand(-1000,1000)])
		}

		var distance = getDistance(this.getPosition(), ant.getPosition());
		// [0, 1] <-> [1/4 smellingDistance, smellingDistance+smellDistance]
		var farthest = ant.getSmellingDistance() + this.getSmellDistance();
		var nearest  = ant.getSmellingDistance()/4;
		// [0,1]
		var smellPos = {x: this.getPosition().x, y:this.getPosition().y }; // clone position
		if (distance > nearest){
			var percentage = distance / (farthest-nearest);
			var smellPosDist = percentage * this.getSmellDistance(); // distance from actual position
			var addVec = rotateVector({x: smellPosDist, y:0}, rand(0, Math.PI*2));
			var smellPos = { x: this.getPosition().x + addVec.x,
						     y: this.getPosition().y + addVec.y };
		}
		return smellPos;
	}

	draw(){
		super.draw();
		if (Debug.getShowSmellingDistance()){
			var pos = this.getPosition()
			this._context.beginPath();
			this._context.lineWidth = 2;
			this._context.arc(pos.x, pos.y, this.getSmellDistance() - 2, 0, 2 * Math.PI, false);
			this._context.strokeStyle = '#ddaa99';
			this._context.stroke();
		}
	}
}<commit_msg>- changed smell position distance to square<commit_after>/**
 * An abstract class for all smellable objects.
 */
class SmellableObject extends Collider {

	/**
	* Creates a smellable object
	* @constructor
	* @abstract
	* @param {object} canvas - The canvas used to draw.
    * @param {object} position - 2D position of where the object shall be created (if no collision occurs).
    * @param {number} size - Size of the objects collider.
    * @param {number} sizeSmellingFactor - Size of the objects smell.
    * @param {Objects[]} collisionObjs - Objects in simulation that can collide with this.
    * @param {number} [rotation=0]rotation - ID of the ants hive.
	*/
	constructor(canvas, position, size, sizeSmellingFactor, collisionObjs, rotation = 0){
		if (new.target === SmellableObject) {
			throw new TypeError("Cannot construct SmellableObject instances directly");
		}
		super(canvas, position, ShapeType.CIRCLE, size, rotation, collisionObjs);
		this.sizeSmellingFactor = sizeSmellingFactor;
	}

	getSmellDistance(){
		var smellingDistance = this.getSize() * this.sizeSmellingFactor;
		return smellingDistance;
	}

	canBeSmelledBy(ant)
	{
		var distance = getDistance(this.getPosition(), ant.getPosition());
		return (distance - this.getSmellDistance() - ant.getSmellingDistance() <= 0);
	}

	smellPositionFrom(ant)
	{
		if (!this.canBeSmelledBy(ant)){
			// should not be able to smell it.... did someone cheat here?!
			return math.matrix([rand(-1000,1000), rand(-1000,1000)])
		}

		var distance = getDistance(this.getPosition(), ant.getPosition());
		// [0, 1] <-> [0, smellingDistance+smellDistance]
		var farthest = ant.getSmellingDistance() + this.getSmellDistance();
		var nearest  = 0;
		// [0,1]
		var smellPos = {x: this.getPosition().x, y:this.getPosition().y }; // clone position
		if (distance > nearest){
			var percentage = distance / (farthest-nearest);
			var smellPosDist = percentage * percentage * this.getSmellDistance(); // distance from actual position
			var addVec = rotateVector({x: smellPosDist, y:0}, rand(0, Math.PI*2));
			var smellPos = { x: this.getPosition().x + addVec.x,
						     y: this.getPosition().y + addVec.y };
		}
		return smellPos;
	}

	draw(){
		super.draw();
		if (Debug.getShowSmellingDistance()){
			var pos = this.getPosition()
			this._context.beginPath();
			this._context.lineWidth = 2;
			this._context.arc(pos.x, pos.y, this.getSmellDistance() - 2, 0, 2 * Math.PI, false);
			this._context.strokeStyle = '#ddaa99';
			this._context.stroke();
		}
	}
}<|endoftext|>


<commit_before><cheatsheet>

Cheatsheet
 {cheatsheet.version}

Written with <3 by @{cheatsheet.author}

Part of happy-css.com

HTML CONSTRUCTS

Foreach

Arrays

						<div each=\{item, index in items\}>
							\{index\} - \{item\}
						</div>
						<script>
							this.items = ['this', 'is', 'a' 'riot!']
						</script>

Objects

						<div each=\{key, value in items\}>
							\{key\} - \{value\}
						</div>
						<script>
							this.items =
							\{
								file: 'riot.png',
								path: 'images/'
							\}
						</script>

Yield

					<yield/>

Conditions

Block Element

					<div if=\{ condition \}></div>

Inline

					<div class=\{ active : condition\}></div>

Ternary

				This is a JS expression, you need quotes for strings

					<div class=\{ condition ? 'enabled' : 'disabled'\}></div>

Tag Object

			Retrievable within Tag Scope (this)

Methods

mixin()

TRIGGER EVENTS

update()

mount()

unmount()

TRIGGER METHODS

trigger()

on()

one()

off()

Properties

isMounted

opts

parent

root - tag element

tags - children riot tags: defined by their name attribute or their tagname

Triggers - Observable

			The Riot event system

Events

this.update() is same as this.trigger('update')

update()

updated()

mount()

unmount()

Trigger

trigger('event') - trigger event

on('event', function()\{\}) - react on event trigger

one('event', function()\{\}) - only once

off() - don't listen to event anymore

Riot Object

Methods

compile()

mixin()

mount()

mountTo()

observable()

route()

tag()

update()

Properties

parsers

version

Router

			Listening to and changing the url hash (#customer/edit/3)

riot.route.route(args, function)

riot.route.route('customer/list')

riot.route.start()

riot.route.stop()

riot.route.exec()

riot.route.parser()

Mixin

			Reusable functions

Defining Mixins

					var object = \{ init : function()\{\} \}

load mixin under namespace

					riot.mixin('name', object)

Using mixin within tag

					this.mixin('name')

Global Events

		All GlobalEventHandlers

			Usage:

				<div onclick=\{ method \}></div>

{section}

					{event}

</cheatsheet><commit_msg>Added happy-css url<commit_after><cheatsheet>

Cheatsheet
 {cheatsheet.version}

Written with <3 by @{cheatsheet.author}

Part of happy-css.com

HTML CONSTRUCTS

Foreach

Arrays

						<div each=\{item, index in items\}>
							\{index\} - \{item\}
						</div>
						<script>
							this.items = ['this', 'is', 'a' 'riot!']
						</script>

Objects

						<div each=\{key, value in items\}>
							\{key\} - \{value\}
						</div>
						<script>
							this.items =
							\{
								file: 'riot.png',
								path: 'images/'
							\}
						</script>

Yield

					<yield/>

Conditions

Block Element

					<div if=\{ condition \}></div>

Inline

					<div class=\{ active : condition\}></div>

Ternary

				This is a JS expression, you need quotes for strings

					<div class=\{ condition ? 'enabled' : 'disabled'\}></div>

Tag Object

			Retrievable within Tag Scope (this)

Methods

mixin()

TRIGGER EVENTS

update()

mount()

unmount()

TRIGGER METHODS

trigger()

on()

one()

off()

Properties

isMounted

opts

parent

root - tag element

tags - children riot tags: defined by their name attribute or their tagname

Triggers - Observable

			The Riot event system

Events

this.update() is same as this.trigger('update')

update()

updated()

mount()

unmount()

Trigger

trigger('event') - trigger event

on('event', function()\{\}) - react on event trigger

one('event', function()\{\}) - only once

off() - don't listen to event anymore

Riot Object

Methods

compile()

mixin()

mount()

mountTo()

observable()

route()

tag()

update()

Properties

parsers

version

Router

			Listening to and changing the url hash (#customer/edit/3)

riot.route.route(args, function)

riot.route.route('customer/list')

riot.route.start()

riot.route.stop()

riot.route.exec()

riot.route.parser()

Mixin

			Reusable functions

Defining Mixins

					var object = \{ init : function()\{\} \}

load mixin under namespace

					riot.mixin('name', object)

Using mixin within tag

					this.mixin('name')

Global Events

		All GlobalEventHandlers

			Usage:

				<div onclick=\{ method \}></div>

{section}

					{event}

</cheatsheet><|endoftext|>


<commit_before>// twister_newmsgs.js
// 2013 Miguel Freitas
//
// Periodically check for new mentions and private messages (DMs)
// Update UI counters in top bar. Load/save state to localStorage.

// --- mentions ---

var _knownMentions = {}
var _lastMentionTime = 0;
var _newMentions = 0;
var _lastLocalMentionId = -1;
var PURGE_OLD_MENTIONS_TIMEOUT = 3600 * 24 * 30; // one month
var _newMentionsUpdated = false;
var _newDMsUpdated = false;
var groupChatAliases = []

// process a mention received to check if it is really new
function processMention(user, mentionTime, data) {
    var key = user + ';' + mentionTime;
    var curTime = new Date().getTime() / 1000;
    if (mentionTime > curTime + 7200)  // 3600 * 2
        console.warn('ignoring mention from the future');
    else {
        if (!_knownMentions[key]) {
            // mention must be somewhat recent compared to last known one to be considered new
            if (mentionTime + 259200 > _lastMentionTime) {  // 3600 * 24 * 3
                _newMentions++;
                _newMentionsUpdated = true;
                _lastMentionTime = Math.max(mentionTime, _lastMentionTime);
                data.isNew = true;

                var reqId = defaultScreenName + '@mention';
                if (typeof _queryPendingPosts[reqId] !== 'object')
                    _queryPendingPosts[reqId] = [];
                _queryPendingPosts[reqId].push(data);
            }
            _knownMentions[key] = {mentionTime: mentionTime, data: data};
            purgeOldMentions();
            saveMentionsToStorage();
        }
    }
}

function purgeOldMentions() {
    for (var key in _knownMentions) {
        if (_knownMentions[key]) {
            if (!_knownMentions[key].mentionTime || !_knownMentions[key].data ||
                _knownMentions[key].mentionTime + PURGE_OLD_MENTIONS_TIMEOUT < _lastMentionTime) {
                delete _knownMentions[key];
            }
        }
    }
}

function saveMentionsToStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    ns.localStorage.set('knownMentions', _knownMentions);
    ns.localStorage.set('lastMentionTime', _lastMentionTime);
    ns.localStorage.set('newMentions', _newMentions);
    ns.localStorage.set('lastLocalMentionId', _lastLocalMentionId);
}

function loadMentionsFromStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    if (ns.localStorage.isSet('knownMentions'))
        _knownMentions = ns.localStorage.get('knownMentions');
    if (ns.localStorage.isSet('lastMentionTime'))
        _lastMentionTime = ns.localStorage.get('lastMentionTime');
    if (ns.localStorage.isSet('newMentions'))
        _newMentions = ns.localStorage.get('newMentions');
    if (ns.localStorage.isSet('lastLocalMentionId'))
        _lastLocalMentionId = ns.localStorage.get('lastLocalMentionId');
}

function requestMentionsCount() {
    // first: getmentions from torrents we follow
    twisterRpc('getmentions', [defaultScreenName, 100, {since_id: _lastLocalMentionId}],
        function(args, data) {
            if (data) {
                for (var i = 0; i < data.length; i++) {
                    _lastLocalMentionId = Math.max(_lastLocalMentionId, data[i].id);
                    var userpost = data[i].userpost;
                    processMention(userpost.n, userpost.time, data[i]);
                }
                $.MAL.updateNewMentionsUI(_newMentions);
            }
        }, null,
        function(req, ret) {console.warn('getmentions API requires twister-core > 0.9.27');}, null
    );
    // second: get mentions from dht (not-following)
    dhtget(defaultScreenName, 'mention', 'm',
            function(args, data) {
                if (data) {
                    for (var i = 0; i < data.length; i++) {
                        var userpost = data[i].userpost;
                        processMention(userpost.n, userpost.time, data[i]);
                    }
                    $.MAL.updateNewMentionsUI(_newMentions);
                }
            }, {},
            [10000, 2000, 3]  // use extended timeout parameters (requires twister_core >= 0.9.14)
    );

    if (_newMentionsUpdated) {
        _newMentionsUpdated = false;

        if (_newMentions) {
            $.MAL.soundNotifyMentions();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifMentions.val === 'enable')
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_mentions', _newMentions) + '.',
                    tag: 'twister_notification_new_mentions',
                    timeout: $.Options.showDesktopNotifMentionsTimer.val,
                    funcClick: function () {
                        var postboardSelector =
                            '.postboard-posts[data-request-id="' + defaultScreenName + '@mention"]';
                        if (!focusModalWithElement(postboardSelector,
                            function (req) {
                                var postboard = $(req.postboardSelector);
                                postboard.closest('.postboard').find('.postboard-news').hide();
                                displayQueryPending(postboard);
                                resetMentionsCount();
                            }, {postboardSelector: postboardSelector}
                        ))
                            $.MAL.showMentions(defaultScreenName);
                    }
                });
        }
    }U

    // was moved here from requestDMsCount() because that is not ticking right
    // we would place it with other notifications into separate notification center
    if (_newDMsUpdated) {
        _newDMsUpdated = false;

        var newDMs = getNewDMsCount();
        if (newDMs) {
            $.MAL.soundNotifyDM();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable') {
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newDMs) + '.',
                    tag: 'twister_notification_new_DMs',
                    timeout: $.Options.showDesktopNotifDMsTimer.val,
                    funcClick: function () {$.MAL.showDMchat();}
                });
            }
        }
        var newDMs = getNewGroupDMsCount();
        if (newDMs) {
            $.MAL.soundNotifyDM();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable') {
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newDMs) + '.',
                    tag: 'twister_notification_new_DMs',
                    timeout: $.Options.showDesktopNotifDMsTimer.val,
                    funcClick: function () {$.MAL.showDMchat({group: true});}
                });
            }
        }
    }
}

function resetMentionsCount() {
    _newMentions = 0;
    for (var key in _knownMentions) {
        if (_knownMentions[key] && _knownMentions[key].data)
            delete _knownMentions[key].data.isNew
    }
    saveMentionsToStorage();
    $.MAL.updateNewMentionsUI(_newMentions);
}

function initMentionsCount() {
    // polling mentions is a temporary solution
    loadMentionsFromStorage();
    $.MAL.updateNewMentionsUI(_newMentions);
    requestMentionsCount();
    setInterval(requestMentionsCount, 10000);
}

function getMentionsData() {
    mentions = []
    for (var key in _knownMentions) {
        if (_knownMentions[key] && _knownMentions[key].data) {
            mentions.push(_knownMentions[key].data);
        }
    }
    return mentions;
}

// --- direct messages ---

var _lastDMIdPerUser = {};
var _newDMsPerUser = {};

function saveDMsToStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    ns.localStorage.set('lastDMIdPerUser', _lastDMIdPerUser);
    ns.localStorage.set('newDMsPerUser', _newDMsPerUser);
}

function loadDMsFromStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    if (ns.localStorage.isSet('lastDMIdPerUser'))
        _lastDMIdPerUser = ns.localStorage.get('lastDMIdPerUser');
    if (ns.localStorage.isSet('newDMsPerUser'))
        _newDMsPerUser = ns.localStorage.get('newDMsPerUser');
}

function requestDMsCount() {
    var followList = [];
    for (var i = 0; i < followingUsers.length; i++)
        followList.push({username: followingUsers[i]});
    for (var i = 0; i < groupChatAliases.length; i++ )
        followList.push({username: groupChatAliases[i]});

    twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList],
        function(req, dmUsers) {
            for (var u in dmUsers) {
                if (dmUsers[u]) {
                    var dmData = dmUsers[u][0];
                    if (u in _lastDMIdPerUser && u in _newDMsPerUser) {
                        if (dmData.id !== _lastDMIdPerUser[u]) {
                            _newDMsPerUser[u] += dmData.id - _lastDMIdPerUser[u];
                            _newDMsUpdated = true;
                        }
                    } else {
                        _newDMsPerUser[u] = dmData.id + 1;
                        _newDMsUpdated = true;
                    }
                    _lastDMIdPerUser[u] = dmData.id;
                }
            }
            if (_newDMsUpdated) {
                saveDMsToStorage();
                $.MAL.updateNewDMsUI(getNewDMsCount());
                $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
            }
        }, null,
        function(req, ret) {console.warn('ajax error:' + ret);}, null
    );
}

function getNewDMsCount() {
    var newDMs = 0;

    for (var user in _newDMsPerUser) {
        if (user[0] !== '*' && _newDMsPerUser[user])
            newDMs += _newDMsPerUser[user];
    }

    return newDMs;
}

function getNewGroupDMsCount() {
    var newDMs = 0;

    for (var user in _newDMsPerUser) {
        if (user[0] === '*' && _newDMsPerUser[user])
            newDMs += _newDMsPerUser[user];
    }

    return newDMs;
}

function resetNewDMsCountForUser(user, lastId) {
    _newDMsPerUser[user] = 0;
    _lastDMIdPerUser[user] = lastId;

    saveDMsToStorage();
    $.MAL.updateNewDMsUI(getNewDMsCount());
    $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
}

function updateGroupList() {
    twisterRpc('listgroups', [],
        function(req, ret) {groupChatAliases = ret;}, null,
        function(req, ret) {console.warn('twisterd >= 0.9.30 required for listgroups');}, null
    );
}

function initDMsCount() {
    loadDMsFromStorage();
    $.MAL.updateNewDMsUI(getNewDMsCount());
    $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
    //quick hack to obtain list of group chat aliases
    updateGroupList();
    setInterval(updateGroupList, 60000);

    setTimeout(requestDMsCount, 200);
    //polling not needed: processNewPostsConfirmation will call requestDMsCount.
    //setInterval('requestDMsCount()', 5000);
}

function newmsgsChangedUser() {
    _knownMentions = {}
    _lastMentionTime = 0;
    _newMentions = 0;
}
<commit_msg>delete mistype<commit_after>// twister_newmsgs.js
// 2013 Miguel Freitas
//
// Periodically check for new mentions and private messages (DMs)
// Update UI counters in top bar. Load/save state to localStorage.

// --- mentions ---

var _knownMentions = {}
var _lastMentionTime = 0;
var _newMentions = 0;
var _lastLocalMentionId = -1;
var PURGE_OLD_MENTIONS_TIMEOUT = 3600 * 24 * 30; // one month
var _newMentionsUpdated = false;
var _newDMsUpdated = false;
var groupChatAliases = []

// process a mention received to check if it is really new
function processMention(user, mentionTime, data) {
    var key = user + ';' + mentionTime;
    var curTime = new Date().getTime() / 1000;
    if (mentionTime > curTime + 7200)  // 3600 * 2
        console.warn('ignoring mention from the future');
    else {
        if (!_knownMentions[key]) {
            // mention must be somewhat recent compared to last known one to be considered new
            if (mentionTime + 259200 > _lastMentionTime) {  // 3600 * 24 * 3
                _newMentions++;
                _newMentionsUpdated = true;
                _lastMentionTime = Math.max(mentionTime, _lastMentionTime);
                data.isNew = true;

                var reqId = defaultScreenName + '@mention';
                if (typeof _queryPendingPosts[reqId] !== 'object')
                    _queryPendingPosts[reqId] = [];
                _queryPendingPosts[reqId].push(data);
            }
            _knownMentions[key] = {mentionTime: mentionTime, data: data};
            purgeOldMentions();
            saveMentionsToStorage();
        }
    }
}

function purgeOldMentions() {
    for (var key in _knownMentions) {
        if (_knownMentions[key]) {
            if (!_knownMentions[key].mentionTime || !_knownMentions[key].data ||
                _knownMentions[key].mentionTime + PURGE_OLD_MENTIONS_TIMEOUT < _lastMentionTime) {
                delete _knownMentions[key];
            }
        }
    }
}

function saveMentionsToStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    ns.localStorage.set('knownMentions', _knownMentions);
    ns.localStorage.set('lastMentionTime', _lastMentionTime);
    ns.localStorage.set('newMentions', _newMentions);
    ns.localStorage.set('lastLocalMentionId', _lastLocalMentionId);
}

function loadMentionsFromStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    if (ns.localStorage.isSet('knownMentions'))
        _knownMentions = ns.localStorage.get('knownMentions');
    if (ns.localStorage.isSet('lastMentionTime'))
        _lastMentionTime = ns.localStorage.get('lastMentionTime');
    if (ns.localStorage.isSet('newMentions'))
        _newMentions = ns.localStorage.get('newMentions');
    if (ns.localStorage.isSet('lastLocalMentionId'))
        _lastLocalMentionId = ns.localStorage.get('lastLocalMentionId');
}

function requestMentionsCount() {
    // first: getmentions from torrents we follow
    twisterRpc('getmentions', [defaultScreenName, 100, {since_id: _lastLocalMentionId}],
        function(args, data) {
            if (data) {
                for (var i = 0; i < data.length; i++) {
                    _lastLocalMentionId = Math.max(_lastLocalMentionId, data[i].id);
                    var userpost = data[i].userpost;
                    processMention(userpost.n, userpost.time, data[i]);
                }
                $.MAL.updateNewMentionsUI(_newMentions);
            }
        }, null,
        function(req, ret) {console.warn('getmentions API requires twister-core > 0.9.27');}, null
    );
    // second: get mentions from dht (not-following)
    dhtget(defaultScreenName, 'mention', 'm',
            function(args, data) {
                if (data) {
                    for (var i = 0; i < data.length; i++) {
                        var userpost = data[i].userpost;
                        processMention(userpost.n, userpost.time, data[i]);
                    }
                    $.MAL.updateNewMentionsUI(_newMentions);
                }
            }, {},
            [10000, 2000, 3]  // use extended timeout parameters (requires twister_core >= 0.9.14)
    );

    if (_newMentionsUpdated) {
        _newMentionsUpdated = false;

        if (_newMentions) {
            $.MAL.soundNotifyMentions();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifMentions.val === 'enable')
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_mentions', _newMentions) + '.',
                    tag: 'twister_notification_new_mentions',
                    timeout: $.Options.showDesktopNotifMentionsTimer.val,
                    funcClick: function () {
                        var postboardSelector =
                            '.postboard-posts[data-request-id="' + defaultScreenName + '@mention"]';
                        if (!focusModalWithElement(postboardSelector,
                            function (req) {
                                var postboard = $(req.postboardSelector);
                                postboard.closest('.postboard').find('.postboard-news').hide();
                                displayQueryPending(postboard);
                                resetMentionsCount();
                            }, {postboardSelector: postboardSelector}
                        ))
                            $.MAL.showMentions(defaultScreenName);
                    }
                });
        }
    }

    // was moved here from requestDMsCount() because that is not ticking right
    // we would place it with other notifications into separate notification center
    if (_newDMsUpdated) {
        _newDMsUpdated = false;

        var newDMs = getNewDMsCount();
        if (newDMs) {
            $.MAL.soundNotifyDM();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable') {
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newDMs) + '.',
                    tag: 'twister_notification_new_DMs',
                    timeout: $.Options.showDesktopNotifDMsTimer.val,
                    funcClick: function () {$.MAL.showDMchat();}
                });
            }
        }
        var newDMs = getNewGroupDMsCount();
        if (newDMs) {
            $.MAL.soundNotifyDM();

            if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable') {
                $.MAL.showDesktopNotification({
                    body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newDMs) + '.',
                    tag: 'twister_notification_new_DMs',
                    timeout: $.Options.showDesktopNotifDMsTimer.val,
                    funcClick: function () {$.MAL.showDMchat({group: true});}
                });
            }
        }
    }
}

function resetMentionsCount() {
    _newMentions = 0;
    for (var key in _knownMentions) {
        if (_knownMentions[key] && _knownMentions[key].data)
            delete _knownMentions[key].data.isNew
    }
    saveMentionsToStorage();
    $.MAL.updateNewMentionsUI(_newMentions);
}

function initMentionsCount() {
    // polling mentions is a temporary solution
    loadMentionsFromStorage();
    $.MAL.updateNewMentionsUI(_newMentions);
    requestMentionsCount();
    setInterval(requestMentionsCount, 10000);
}

function getMentionsData() {
    mentions = []
    for (var key in _knownMentions) {
        if (_knownMentions[key] && _knownMentions[key].data) {
            mentions.push(_knownMentions[key].data);
        }
    }
    return mentions;
}

// --- direct messages ---

var _lastDMIdPerUser = {};
var _newDMsPerUser = {};

function saveDMsToStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    ns.localStorage.set('lastDMIdPerUser', _lastDMIdPerUser);
    ns.localStorage.set('newDMsPerUser', _newDMsPerUser);
}

function loadDMsFromStorage() {
    var ns = $.initNamespaceStorage(defaultScreenName);
    if (ns.localStorage.isSet('lastDMIdPerUser'))
        _lastDMIdPerUser = ns.localStorage.get('lastDMIdPerUser');
    if (ns.localStorage.isSet('newDMsPerUser'))
        _newDMsPerUser = ns.localStorage.get('newDMsPerUser');
}

function requestDMsCount() {
    var followList = [];
    for (var i = 0; i < followingUsers.length; i++)
        followList.push({username: followingUsers[i]});
    for (var i = 0; i < groupChatAliases.length; i++ )
        followList.push({username: groupChatAliases[i]});

    twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList],
        function(req, dmUsers) {
            for (var u in dmUsers) {
                if (dmUsers[u]) {
                    var dmData = dmUsers[u][0];
                    if (u in _lastDMIdPerUser && u in _newDMsPerUser) {
                        if (dmData.id !== _lastDMIdPerUser[u]) {
                            _newDMsPerUser[u] += dmData.id - _lastDMIdPerUser[u];
                            _newDMsUpdated = true;
                        }
                    } else {
                        _newDMsPerUser[u] = dmData.id + 1;
                        _newDMsUpdated = true;
                    }
                    _lastDMIdPerUser[u] = dmData.id;
                }
            }
            if (_newDMsUpdated) {
                saveDMsToStorage();
                $.MAL.updateNewDMsUI(getNewDMsCount());
                $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
            }
        }, null,
        function(req, ret) {console.warn('ajax error:' + ret);}, null
    );
}

function getNewDMsCount() {
    var newDMs = 0;

    for (var user in _newDMsPerUser) {
        if (user[0] !== '*' && _newDMsPerUser[user])
            newDMs += _newDMsPerUser[user];
    }

    return newDMs;
}

function getNewGroupDMsCount() {
    var newDMs = 0;

    for (var user in _newDMsPerUser) {
        if (user[0] === '*' && _newDMsPerUser[user])
            newDMs += _newDMsPerUser[user];
    }

    return newDMs;
}

function resetNewDMsCountForUser(user, lastId) {
    _newDMsPerUser[user] = 0;
    _lastDMIdPerUser[user] = lastId;

    saveDMsToStorage();
    $.MAL.updateNewDMsUI(getNewDMsCount());
    $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
}

function updateGroupList() {
    twisterRpc('listgroups', [],
        function(req, ret) {groupChatAliases = ret;}, null,
        function(req, ret) {console.warn('twisterd >= 0.9.30 required for listgroups');}, null
    );
}

function initDMsCount() {
    loadDMsFromStorage();
    $.MAL.updateNewDMsUI(getNewDMsCount());
    $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
    //quick hack to obtain list of group chat aliases
    updateGroupList();
    setInterval(updateGroupList, 60000);

    setTimeout(requestDMsCount, 200);
    //polling not needed: processNewPostsConfirmation will call requestDMsCount.
    //setInterval('requestDMsCount()', 5000);
}

function newmsgsChangedUser() {
    _knownMentions = {}
    _lastMentionTime = 0;
    _newMentions = 0;
}
<|endoftext|>


<commit_before>let container = null

Array
  .from(document.querySelectorAll('code.lang-javascript'))
  .forEach(element => {
    container = createContainer(element.parentNode)
    eval(element.textContent)
  })

function createElementFrom (string) {
  const div = document.createElement('div')
  div.innerHTML = string
  return div.firstChild
}

function createContainer (element) {
  const div = document.createElement('div')
  div.innerHTML = '

'
  return element.parentNode.insertBefore(div.firstChild, element.nextSibling)
}

function loadScript (url, callback) {
  const head = document.getElementsByTagName('head')[0]
  const script = document.createElement('script')
  script.type = 'text/javascript'
  script.src = url

  script.onreadystatechange = callback
  script.onload = callback

  head.appendChild(script)
}

function startLoop (callback) {
  const renderFrame = () => {
    window.requestAnimationFrame(renderFrame)
    callback()
  }
  renderFrame()
}

function three (init) {
  const currentContainer = container
  const THREE = window.THREE
  loadScript('https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js', () => {
    const width = currentContainer.offsetWidth
    const height = 300

    const scene = new THREE.Scene()

    const camera = new THREE.PerspectiveCamera(50, width / height, 1, 10000)
    camera.position.z = 500
    scene.add(camera)

    const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
    renderer.setSize(width, height)

    currentContainer.appendChild(renderer.domElement)

    const onWindowResize = () => {
      const width = currentContainer.offsetWidth
      const height = 300

      camera.aspect = width / height
      camera.updateProjectionMatrix()

      renderer.setSize(width, height)
      renderer.render(scene, camera)
    }

    window.addEventListener('resize', onWindowResize, false)

    const render = (updateFn) => {
      startLoop(() => {
        updateFn()
        renderer.render(scene, camera)
      })
    }

    const renderOnce = () => {
      renderer.render(scene, camera)
    }

    init({
      scene,
      renderer,
      camera,
      render,
      renderOnce
    })
  })
}

function canvas (renderFn) {
  const currentContainer = container
  const pixelRatio = window.devicePixelRatio
  const canvas = container.appendChild(document.createElement('canvas'))
  const context = canvas.getContext('2d')

  const resizeAndRender = () => {
    const width = currentContainer.offsetWidth
    const height = 300

    canvas.width = width * pixelRatio
    canvas.height = height * pixelRatio
    canvas.style.width = width + 'px'
    canvas.style.height = height + 'px'

    renderFn({
      context,
      width: canvas.width,
      height: canvas.height
    })
  }

  window.addEventListener('resize', resizeAndRender, false)

  resizeAndRender()
}

function display (...objects) {
  objects.forEach(o => {
    const prettyObject = JSON.stringify(o)
    const element = createElementFrom(`<pre><code>${prettyObject}</code><i class="fa fa-check-circle-o" aria-hidden="true"></i></pre>`)
    container.appendChild(element)
  })
}

window.three = three
window.canvas = canvas
window.display = display
<commit_msg>fix THREE not being available in calc-results script<commit_after>let container = null

Array
  .from(document.querySelectorAll('code.lang-javascript'))
  .forEach(element => {
    container = createContainer(element.parentNode)
    eval(element.textContent)
  })

function createElementFrom (string) {
  const div = document.createElement('div')
  div.innerHTML = string
  return div.firstChild
}

function createContainer (element) {
  const div = document.createElement('div')
  div.innerHTML = '

'
  return element.parentNode.insertBefore(div.firstChild, element.nextSibling)
}

function loadScript (url, callback) {
  const head = document.getElementsByTagName('head')[0]
  const script = document.createElement('script')
  script.type = 'text/javascript'
  script.src = url

  script.onreadystatechange = callback
  script.onload = callback

  head.appendChild(script)
}

function startLoop (callback) {
  const renderFrame = () => {
    window.requestAnimationFrame(renderFrame)
    callback()
  }
  renderFrame()
}

function three (init) {
  const currentContainer = container
  loadScript('https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js', () => {
    const width = currentContainer.offsetWidth
    const height = 300

    const scene = new THREE.Scene()

    const camera = new THREE.PerspectiveCamera(50, width / height, 1, 10000)
    camera.position.z = 500
    scene.add(camera)

    const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
    renderer.setSize(width, height)

    currentContainer.appendChild(renderer.domElement)

    const onWindowResize = () => {
      const width = currentContainer.offsetWidth
      const height = 300

      camera.aspect = width / height
      camera.updateProjectionMatrix()

      renderer.setSize(width, height)
      renderer.render(scene, camera)
    }

    window.addEventListener('resize', onWindowResize, false)

    const render = (updateFn) => {
      startLoop(() => {
        updateFn()
        renderer.render(scene, camera)
      })
    }

    const renderOnce = () => {
      renderer.render(scene, camera)
    }

    init({
      scene,
      renderer,
      camera,
      render,
      renderOnce
    })
  })
}

function canvas (renderFn) {
  const currentContainer = container
  const pixelRatio = window.devicePixelRatio
  const canvas = container.appendChild(document.createElement('canvas'))
  const context = canvas.getContext('2d')

  const resizeAndRender = () => {
    const width = currentContainer.offsetWidth
    const height = 300

    canvas.width = width * pixelRatio
    canvas.height = height * pixelRatio
    canvas.style.width = width + 'px'
    canvas.style.height = height + 'px'

    renderFn({
      context,
      width: canvas.width,
      height: canvas.height
    })
  }

  window.addEventListener('resize', resizeAndRender, false)

  resizeAndRender()
}

function display (...objects) {
  objects.forEach(o => {
    const prettyObject = JSON.stringify(o)
    const element = createElementFrom(`<pre><code>${prettyObject}</code><i class="fa fa-check-circle-o" aria-hidden="true"></i></pre>`)
    container.appendChild(element)
  })
}

window.three = three
window.canvas = canvas
window.display = display
<|endoftext|>


<commit_before>var ui = {
  constructUiFromLoadout: function (loadout) {
    //title
    $('.title').append(
      $('
'+ loadout.title +'
')
    );
  }
}
<commit_msg>Sending now works but the anti spam timer still needs some work<commit_after>var ui = {
  buttonClicked: false,
  handleClick: function (signal) {
    if (!this.buttonClicked) {
      net.sendSignal(signal)
      this.buttonClicked = true
      console.log('Started Timer');

      setTimeout(function () {

        this.buttonClicked = false;
        console.log('Ended Timer');
      }, 3000);

    }else if (this.buttonClicked) {
      console.log(this.buttonClicked);
      swal(
        'Hov Hov...',
        'Vent lige 20 sekunder med at ringe på igen!',
        'error'
      );
    }
  },
  constructUiFromLoadout: function (loadout) {
    var markup = loadout
    //title
    $('.title').append(
      $('
'+ markup.title +'
')
    );

    //Buttons
    for (var i = 0; i < markup.buttons.length; i+=2) {
      $('.btnTable').append(
        $('
<button id="'+markup.buttons[i].id+'" class="bttn-fill bttn-lg bttn-'+ markup.buttons[i].color +' doorbellbutton">'+ markup.buttons[i].name +'</button><button id="' + markup.buttons[i+1].id + '" class="bttn-fill bttn-lg bttn-'+ markup.buttons[i+1].color +' doorbellbutton">'+ markup.buttons[i+1].name +'</button>
')
      );
    }

    //Add click events
    markup.buttons.forEach(function (button) {
      $('#' + button.id).click(function () {
        ui.handleClick(button.signal)
      })
    })
  }
}
<|endoftext|>


<commit_before>function calculateDistanceHaversine(location1, location2, radius) {

	radius = radius || 6371e3;

	// convert latitude and longitude to radians first
    var lat1 = Math.PI * location1.latitude / 180;
    var lat2 = Math.PI * location2.latitude / 180;
    var d_phi = lat2 - lat1;
    var d_lambda = Math.PI * (location2.longitude - location1.longitude) / 180;

    var a = Math.sin(d_phi / 2) * Math.sin(d_phi / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(d_lambda / 2) * Math.sin(d_lambda / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    // console.log('|| {} - {} ||= {}'.format(location2, location1, earth_radius * c))

    return radius * c;
}

function breakSegments(data, byStatus, separationRadius, timeInterval) {

	separationRadius = separationRadius || [100, 10000]; // 10m, 10km
	timeInterval = timeInterval || [2 * 60 * 1000, 60 * 60 * 1000]; // 2 minutes, 1 hour
    byStatus = byStatus || false; // split by status as well?

	var segments = [];

	var currentSegment = [];
	var lastPosition = null;
	var n = data.length;
	for (var i = 0; i < n; i++) {

		// current position
		var currentPosition = data[i];

		// distance?
		if (lastPosition != null) {
			var distance = calculateDistanceHaversine(lastPosition.location, currentPosition.location);
			var interval = Math.abs(Date.parse(lastPosition.timestamp) - Date.parse(currentPosition.timestamp));
			var newStatus = false;
			if (byStatus && lastPosition.status != currentPosition.status) {
			    newStatus = true;
			    // will break segment, add current position first
                currentSegment.push(currentPosition);
            }
			if (newStatus ||
                distance > separationRadius[1] ||
                interval > timeInterval[1] ||
                (interval > timeInterval[0] && distance > separationRadius[0])) {
                // terminate current segment
                segments.push(currentSegment);
                currentSegment = [];
            }
		}

		// add position to segment
		currentSegment.push(currentPosition);

		// update lastPosition
		lastPosition = currentPosition;

	}

	// anything left?
	if (currentSegment.length > 0) {
        // terminate last segment
        segments.push(currentSegment);
    }

	return segments;

}

function createMarker(call_or_update, icon) {

    // default marker
    icon = icon || L.icon({
        iconUrl: '/static/icons/flag/blue.svg',
        iconSize: [15, 30],
    });

    var location = call_or_update.location;
    return L.marker(
        [location.latitude, location.longitude],
        {icon: icon});

}

function createSegmentLine(map, updates) {

	// Store data in an array
	var latlngs = [];
	updates.forEach(function(update) {

		// push location
		var loc = update.location;
		latlngs.push([loc.latitude, loc.longitude]);

    });

	// Add line to map
	console.log('Adding segment');
	return L.polyline(latlngs, {color: "red"});

}

// Interact with widget to add an ambulance route
function addAmbulanceRoute(map, data, byStatus) {

    byStatus = byStatus || false;

    // paginated?
    if ('results' in data) {
        data = data.results
    }

    // short return
    if (data.length == 0)
        return;

    // break segments
    var segments = breakSegments(data, byStatus);

    // loop on segments
    var n = segments.length;
    var last_segment = null;
	for (var i = 0; i < n; i++) {

	    // grab segment
        var segment = segments[i];

        // initial point
        var initialPoint = segment[0];

        if (i == 0) {

            // add starting marker
            console.log("Adding initial '" + initialPoint.status + "' marker");
            createMarker(segment[0])
                .addTo(map.map)
                .bindPopup('' + ambulance_status[initialPoint.status] + '')
                .on('mouseover',
                    function (e) {
                        // open popup bubble
                        this.openPopup().on('mouseout',
                            function (e) {
                                this.closePopup();
                            });
                    });

        } else if (byStatus) { // && i > 0

            var status = initialPoint.status;
            var last_status = last_segment[last_segment.length - 1].status;
            console.log('status = ' + status + ', last_status = ' + last_status);
            if (last_status != status) {
                // add status marker
                console.log("Adding '" + status + "' marker");
                createMarker(initialPoint)
                    .addTo(map.map)
                    .bindPopup('' + ambulance_status[status] + '')
                    .on('mouseover',
                        function (e) {
                            // open popup bubble
                            this.openPopup().on('mouseout',
                                function (e) {
                                    this.closePopup();
                                });
                        });
            }

        }

        // add segment to map
        createSegmentLine(map, segment)
            .addTo(map.map);

        // last segment?
        if (i == n - 1 && segment.length > 0) {
            // add ending marker
            var finalPoint = segment[segment.length - 1];
            console.log("Adding final '" + finalPoint.status + "' marker");
            createMarker(finalPoint)
                .addTo(map.map)
                .bindPopup('' + ambulance_status[finalPoint.status] + '')
                .on('mouseover',
                    function (e) {
                        // open popup bubble
                        this.openPopup().on('mouseout',
                            function (e) {
                                this.closePopup();
                            });
                    });
        }

        last_segment = segment;

    }

    // create route filter
    //createRouteFilter(segments);

    console.log('Centering map');
    map.center(data[0].location);

}

function createRouteFilter(map, segments) {

    // Add the checkbox on the top right corner for filtering.
    var container = L.DomUtil.create('div', 'filter-options bg-light');

    //Generate HTML code for checkboxes for each of the statuses.
    var filterHtml = "";

    filterHtml += '
';
    segments.forEach(function (segment, index) {

        var date = new Date(Date.parse(segment[0].timestamp));
        filterHtml += '
'
            + '<input class="chk" data-status="layer_' + index + '" type="checkbox" value="" checked >'
            + date.toLocaleString()
            + ''
            + '
';

    });
    filterHtml += "
";

    // Append html code to container
    container.innerHTML = filterHtml;

    // Add the checkboxes.
    var customControl = L.Control.extend({

        options: {
            position: 'topright'
        },

        onAdd: function (map) {
            return container;
        }

    });
    map.map.addControl(new customControl());

    // Add listener to remove status layer when filter checkbox is clicked
    $('.chk').change(function () {

        // Which layer?
        var layer = map.getLayerPane(this.getAttribute('data-status'));

        if (this.checked) {
            layer.style.display = 'block';
        } else {
            layer.style.display = 'none';
        }

    });

};
<commit_msg>Improving call_detail page.<commit_after>function calculateDistanceHaversine(location1, location2, radius) {

	radius = radius || 6371e3;

	// convert latitude and longitude to radians first
    var lat1 = Math.PI * location1.latitude / 180;
    var lat2 = Math.PI * location2.latitude / 180;
    var d_phi = lat2 - lat1;
    var d_lambda = Math.PI * (location2.longitude - location1.longitude) / 180;

    var a = Math.sin(d_phi / 2) * Math.sin(d_phi / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(d_lambda / 2) * Math.sin(d_lambda / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    // console.log('|| {} - {} ||= {}'.format(location2, location1, earth_radius * c))

    return radius * c;
}

function breakSegments(data, byStatus, separationRadius, timeInterval) {

	separationRadius = separationRadius || [100, 10000]; // 10m, 10km
	timeInterval = timeInterval || [2 * 60 * 1000, 60 * 60 * 1000]; // 2 minutes, 1 hour
    byStatus = byStatus || false; // split by status as well?

	var segments = [];

	var currentSegment = [];
	var lastPosition = null;
	var n = data.length;
	for (var i = 0; i < n; i++) {

		// current position
		var currentPosition = data[i];

		// distance?
		if (lastPosition != null) {
			var distance = calculateDistanceHaversine(lastPosition.location, currentPosition.location);
			var interval = Math.abs(Date.parse(lastPosition.timestamp) - Date.parse(currentPosition.timestamp));
			var newStatus = false;
			if (byStatus && lastPosition.status != currentPosition.status) {
			    newStatus = true;
			    // will break segment, add current position first
                var newCurrentPosition = Object.assign({}, currentPosition());
                newCurrentPosition.status = lastPosition.status;
                currentSegment.push(newCurrentPosition);
            }
			if (newStatus ||
                distance > separationRadius[1] ||
                interval > timeInterval[1] ||
                (interval > timeInterval[0] && distance > separationRadius[0])) {
                // terminate current segment
                segments.push(currentSegment);
                currentSegment = [];
            }
		}

		// add position to segment
		currentSegment.push(currentPosition);

		// update lastPosition
		lastPosition = currentPosition;

	}

	// anything left?
	if (currentSegment.length > 0) {
        // terminate last segment
        segments.push(currentSegment);
    }

	return segments;

}

function createMarker(call_or_update, icon) {

    // default marker
    icon = icon || L.icon({
        iconUrl: '/static/icons/flag/blue.svg',
        iconSize: [15, 30],
    });

    var location = call_or_update.location;
    return L.marker(
        [location.latitude, location.longitude],
        {icon: icon});

}

function createSegmentLine(map, updates) {

	// Store data in an array
	var latlngs = [];
	updates.forEach(function(update) {

		// push location
		var loc = update.location;
		latlngs.push([loc.latitude, loc.longitude]);

    });

	// Add line to map
	console.log('Adding segment');
	return L.polyline(latlngs, {color: "red"});

}

// Interact with widget to add an ambulance route
function addAmbulanceRoute(map, data, byStatus) {

    byStatus = byStatus || false;

    // paginated?
    if ('results' in data) {
        data = data.results
    }

    // short return
    if (data.length == 0)
        return;

    // break segments
    var segments = breakSegments(data, byStatus);

    // loop on segments
    var n = segments.length;
    var last_segment = null;
	for (var i = 0; i < n; i++) {

	    // grab segment
        var segment = segments[i];

        // initial point
        var initialPoint = segment[0];

        if (i == 0) {

            // add starting marker
            console.log("Adding initial '" + initialPoint.status + "' marker");
            createMarker(segment[0])
                .addTo(map.map)
                .bindPopup('' + ambulance_status[initialPoint.status] + '')
                .on('mouseover',
                    function (e) {
                        // open popup bubble
                        this.openPopup().on('mouseout',
                            function (e) {
                                this.closePopup();
                            });
                    });

        } else if (byStatus) { // && i > 0

            var status = initialPoint.status;
            var last_status = last_segment[last_segment.length - 1].status;
            console.log('status = ' + status + ', last_status = ' + last_status);
            if (last_status != status) {
                // add status marker
                console.log("Adding '" + status + "' marker");
                createMarker(initialPoint)
                    .addTo(map.map)
                    .bindPopup('' + ambulance_status[status] + '')
                    .on('mouseover',
                        function (e) {
                            // open popup bubble
                            this.openPopup().on('mouseout',
                                function (e) {
                                    this.closePopup();
                                });
                        });
            }

        }

        // add segment to map
        createSegmentLine(map, segment)
            .addTo(map.map);

        // last segment?
        if (i == n - 1 && segment.length > 0) {
            // add ending marker
            var finalPoint = segment[segment.length - 1];
            console.log("Adding final '" + finalPoint.status + "' marker");
            createMarker(finalPoint)
                .addTo(map.map)
                .bindPopup('' + ambulance_status[finalPoint.status] + '')
                .on('mouseover',
                    function (e) {
                        // open popup bubble
                        this.openPopup().on('mouseout',
                            function (e) {
                                this.closePopup();
                            });
                    });
        }

        last_segment = segment;

    }

    // create route filter
    //createRouteFilter(segments);

    console.log('Centering map');
    map.center(data[0].location);

}

function createRouteFilter(map, segments) {

    // Add the checkbox on the top right corner for filtering.
    var container = L.DomUtil.create('div', 'filter-options bg-light');

    //Generate HTML code for checkboxes for each of the statuses.
    var filterHtml = "";

    filterHtml += '
';
    segments.forEach(function (segment, index) {

        var date = new Date(Date.parse(segment[0].timestamp));
        filterHtml += '
'
            + '<input class="chk" data-status="layer_' + index + '" type="checkbox" value="" checked >'
            + date.toLocaleString()
            + ''
            + '
';

    });
    filterHtml += "
";

    // Append html code to container
    container.innerHTML = filterHtml;

    // Add the checkboxes.
    var customControl = L.Control.extend({

        options: {
            position: 'topright'
        },

        onAdd: function (map) {
            return container;
        }

    });
    map.map.addControl(new customControl());

    // Add listener to remove status layer when filter checkbox is clicked
    $('.chk').change(function () {

        // Which layer?
        var layer = map.getLayerPane(this.getAttribute('data-status'));

        if (this.checked) {
            layer.style.display = 'block';
        } else {
            layer.style.display = 'none';
        }

    });

};
<|endoftext|>


<commit_before>// Prevent errors in browsers without FireBug.
if (!window.console)
{
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
    window.console = {};
    for (var i = 0; i < names.length; ++i)
        window.console[names[i]] = function() {}
}

// Objects shown in the game for this level.
var gameObjects;

var gameObjectBeingMoved;

// Objects offered in the palette, they can be dragged on to the level to add a game object.
var paletteObjects;

var PALETTE_OBJECT_NAMES = [
                      "pyramid",
                      "spell",
                      "sphinx",
                      "hazard",
];

var background;
var canvas;
var ctx;

// Sprite type used for collision detection and drawing. Matches the size of an image.
function Sprite(name, firstLeft, firstTop) {

	this.name = name;

	this.image = new Image();
	this.image.src = name + ".png";
	this.image.onload = function() {
		draw();
	}

	this.move = function(newLeft, newTop) {
		this.left = newLeft;
		this.top = newTop;
		this.right = newLeft + this.image.width;
		this.bottom = newTop + this.image.height;
		draw();
	}
	this.move(firstLeft, firstTop);
}

function handleLoad() {

	canvas = document.getElementById('gameCanvas');
	ctx = canvas.getContext('2d');
	background = new Image();
	background.src = "background.png";
	background.onload = function() {
		draw();
	}

	gameObjects = new Array();
	gameObjects[0] = new Sprite("sphinx", canvas.width / 2, canvas.height / 2);
	gameObjects[1] = new Sprite("pyramid", canvas.width / 2, 0);

	paletteObjects = new Array();
	var paletteIndex = 0;
	for( var i in PALETTE_OBJECT_NAMES ) {
		console.log(PALETTE_OBJECT_NAMES[i]);
		console.log(paletteIndex);
		paletteObjects[paletteIndex] = new Sprite(
				PALETTE_OBJECT_NAMES[i], 15, 35 + 80 * paletteIndex);
		paletteIndex++;
	}

	draw();
}

function draw() {

	ctx.drawImage(background, 0, 0);

	//Draw game objects.
	for( var i in gameObjects ) {
		var gameObject = gameObjects[i];
		ctx.drawImage(gameObject.image, gameObject.left, gameObject.top);
	}

	//Draw palette objects.
	for( var i in paletteObjects ) {
		var paletteObject = paletteObjects[i];
		ctx.drawImage(paletteObject.image, paletteObject.left, paletteObject.top);
	}

	//Draw label for the palette area of the editor.
	ctx.font = "bold x-large sans-serif";
	ctx.fillStyle = "white";
	ctx.textAlign = "left";
	ctx.fillText("Palette", 5, 25);

	//Draw a vertical line separating it.
	ctx.moveTo(90, 0);
	ctx.lineTo(90, 512);
	ctx.lineWidth = 5;
	ctx.strokeStyle = "#000";
	ctx.stroke();

	//Draw label for the play area area of the editor.
	ctx.font = "bold x-large sans-serif";
	ctx.fillStyle = "white";
	ctx.textAlign = "left";
	ctx.fillText("Play Area", 100, 25);

}

function doesSpriteContain(sprite, x, y) {

	if ( null == sprite ) {
		return false;
	}
	return (sprite.left <= x &&
			sprite.right >= x &&
			sprite.bottom >= y &&
			sprite.top <= y)
}

function handleClick(e) {

	// If dragging something, clicking drops it.
	if ( null != gameObjectBeingMoved ) {
		gameObjectBeingMoved = null;
		return;
	}

	// Convert mouse coordinates into canvas coordinates.
	var mouseX = e.clientX - canvas.offsetLeft + window.pageXOffset;
	var mouseY = e.clientY - canvas.offsetTop + window.pageYOffset;

	// Otherwise, check if we are clicking to pick something up.
	for( var i in gameObjects ) {
		var gameObject = gameObjects[i];
		if ( doesSpriteContain(gameObject, mouseX, mouseY) ) {

			console.log("Object grabbed!");

			gameObjectBeingMoved = gameObject;
			gameObjectBeingMoved.move(mouseX, mouseY);
			break;
		}
	}

	for( var i in paletteObjects ) {
		var paletteObject = paletteObjects[i];
		if ( doesSpriteContain(paletteObject, mouseX, mouseY) ) {

			console.log("Palette clicked, making new game object!");

			gameObjectBeingMoved = new Sprite(paletteObject.name, paletteObject.left, paletteObject.top);
			gameObjectBeingMoved.move(mouseX, mouseY);

			gameObjects[gameObjects.length] = gameObjectBeingMoved;

			break;
		}
	}

}

function handleMouseOver() {
}

function handleMouseOut() {
}

function handleMouseMove(e) {
	// Convert mouse coordinates into canvas coordinates.
	var mouseX = e.clientX - canvas.offsetLeft + window.pageXOffset;
	var mouseY = e.clientY - canvas.offsetTop + window.pageYOffset;

	if ( null != gameObjectBeingMoved ) {
		console.log("Moving object!");
		gameObjectBeingMoved.move(mouseX, mouseY);
	} else {
		console.log("No grabbed object.");
	}
}
<commit_msg>Drag things by their center. Looks better.<commit_after>// Prevent errors in browsers without FireBug.
if (!window.console)
{
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
    window.console = {};
    for (var i = 0; i < names.length; ++i)
        window.console[names[i]] = function() {}
}

// Objects shown in the game for this level.
var gameObjects;

var gameObjectBeingMoved;

// Objects offered in the palette, they can be dragged on to the level to add a game object.
var paletteObjects;

var PALETTE_OBJECT_NAMES = [
                      "pyramid",
                      "spell",
                      "sphinx",
                      "hazard",
];

var background;
var canvas;
var ctx;

// Sprite type used for collision detection and drawing. Matches the size of an image.
function Sprite(name, firstLeft, firstTop) {

	this.name = name;

	this.image = new Image();
	this.image.src = name + ".png";
	this.image.onload = function() {
		draw();
	}

	this.move = function(newLeft, newTop) {
		this.left = newLeft;
		this.top = newTop;
		this.right = newLeft + this.image.width;
		this.bottom = newTop + this.image.height;
		draw();
	}

	this.moveCenter = function(newCenterX, newCenterY) {
		this.move(newCenterX - this.image.width / 2, newCenterY - this.image.height / 2);
		draw();
	}

	this.move(firstLeft, firstTop);
}

function handleLoad() {

	canvas = document.getElementById('gameCanvas');
	ctx = canvas.getContext('2d');
	background = new Image();
	background.src = "background.png";
	background.onload = function() {
		draw();
	}

	gameObjects = new Array();
	gameObjects[0] = new Sprite("sphinx", canvas.width / 2, canvas.height / 2);
	gameObjects[1] = new Sprite("pyramid", canvas.width / 2, 0);

	paletteObjects = new Array();
	var paletteIndex = 0;
	for( var i in PALETTE_OBJECT_NAMES ) {
		console.log(PALETTE_OBJECT_NAMES[i]);
		console.log(paletteIndex);
		paletteObjects[paletteIndex] = new Sprite(
				PALETTE_OBJECT_NAMES[i], 15, 35 + 80 * paletteIndex);
		paletteIndex++;
	}

	draw();
}

function draw() {

	ctx.drawImage(background, 0, 0);

	//Draw game objects.
	for( var i in gameObjects ) {
		var gameObject = gameObjects[i];
		ctx.drawImage(gameObject.image, gameObject.left, gameObject.top);
	}

	//Draw palette objects.
	for( var i in paletteObjects ) {
		var paletteObject = paletteObjects[i];
		ctx.drawImage(paletteObject.image, paletteObject.left, paletteObject.top);
	}

	//Draw label for the palette area of the editor.
	ctx.font = "bold x-large sans-serif";
	ctx.fillStyle = "white";
	ctx.textAlign = "left";
	ctx.fillText("Palette", 5, 25);

	//Draw a vertical line separating it.
	ctx.moveTo(90, 0);
	ctx.lineTo(90, 512);
	ctx.lineWidth = 5;
	ctx.strokeStyle = "#000";
	ctx.stroke();

	//Draw label for the play area area of the editor.
	ctx.font = "bold x-large sans-serif";
	ctx.fillStyle = "white";
	ctx.textAlign = "left";
	ctx.fillText("Play Area", 100, 25);

}

function doesSpriteContain(sprite, x, y) {

	if ( null == sprite ) {
		return false;
	}
	return (sprite.left <= x &&
			sprite.right >= x &&
			sprite.bottom >= y &&
			sprite.top <= y)
}

function handleClick(e) {

	// If dragging something, clicking drops it.
	if ( null != gameObjectBeingMoved ) {
		gameObjectBeingMoved = null;
		return;
	}

	// Convert mouse coordinates into canvas coordinates.
	var mouseX = e.clientX - canvas.offsetLeft + window.pageXOffset;
	var mouseY = e.clientY - canvas.offsetTop + window.pageYOffset;

	// Otherwise, check if we are clicking to pick something up.
	for( var i in gameObjects ) {
		var gameObject = gameObjects[i];
		if ( doesSpriteContain(gameObject, mouseX, mouseY) ) {

			console.log("Object grabbed!");

			gameObjectBeingMoved = gameObject;
			gameObjectBeingMoved.moveCenter(mouseX, mouseY);
			break;
		}
	}

	for( var i in paletteObjects ) {
		var paletteObject = paletteObjects[i];
		if ( doesSpriteContain(paletteObject, mouseX, mouseY) ) {

			console.log("Palette clicked, making new game object!");

			gameObjectBeingMoved = new Sprite(paletteObject.name, paletteObject.left, paletteObject.top);
			gameObjectBeingMoved.moveCenter(mouseX, mouseY);

			gameObjects[gameObjects.length] = gameObjectBeingMoved;

			break;
		}
	}

}

function handleMouseOver() {
}

function handleMouseOut() {
}

function handleMouseMove(e) {
	// Convert mouse coordinates into canvas coordinates.
	var mouseX = e.clientX - canvas.offsetLeft + window.pageXOffset;
	var mouseY = e.clientY - canvas.offsetTop + window.pageYOffset;

	if ( null != gameObjectBeingMoved ) {
		console.log("Moving object!");
		gameObjectBeingMoved.moveCenter(mouseX, mouseY);
	} else {
		console.log("No grabbed object.");
	}
}
<|endoftext|>


<commit_before>/* channelguide.js
 *
 * Shared functions used in channel guide
 */

function isMiro() {
    return (navigator.userAgent.indexOf('Miro') != -1 ||
	    (top.frames.length == 2 &&
	     top.frames[1].name == 'miro_guide_frame'));
}

function MiroVersion() {
    if (navigator.userAgent.indexOf('Miro') != -1) {
	return /Miro\/([\d.]+)/.exec(navigator.userAgent)[1];
    } else if (top.frames.length == 2 &&
	       top.frames[1].name == 'miro_guide_frame') {
	return "1.2";
    } else {
	return undefined;
    }
}

function getPreviousElement(elt) {
    while(elt.previousSibling) {
        elt = elt.previousSibling;
        if(elt.nodeType == 1) return elt;
    }
    return null;
}

function getNextElement(elt) {
    while(elt.nextSibling) {
        elt = elt.nextSibling;
        if(elt.nodeType == 1) return elt;
    }
    return null;
}

function showLoadIndicator(always) {
    if (always || navigator.userAgent.indexOf('Miro') != -1) {
	indicator = $("#load-indicator");
	indicator.animate({bottom: 0}, 'fast');
    }
}

function hideLoadIndicator() {
    indicator = $("#load-indicator").css('bottom', '-30px');
}

function makeXMLHttpRequest() {
    if (window.XMLHttpRequest) { // Mozilla, Safari, ...
        return new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE
        try {
            return new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {}
        try {
            return new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
    }
    return null;
}

function doAjaxCall(url, callback) {
    var request = makeXMLHttpRequest();
    if (!request) return false;
    request.onreadystatechange = function() {
         if (request.readyState == 4) {
            if (request.status == 200) {
                callback(request);
            }
         }
    };
    request.open('GET', url, true);
    request.send(null);
    return true;
}

function ajaxLink(url, id) {
    var elt = document.getElementById(id);
    if (!elt) return true;
    function callback(request) {
        elt.innerHTML = request.responseText;
    }
    if (!doAjaxCall(url, callback)) return true;
    return false;
}

/* Handle a subscription link.  We need to hit the channelguide URL, then make
 * the browser navigate to the subscribe link.  This is basically a hack
 * because some older version of democracy get confused because the
 * channelguide URL redirects te the subscribe_url.
 */
function handleSubscriptionLink(channel_guide_url, subscribe_url) {
    if (isMiro() && MiroVersion() >= "1.5") return true;
    request = makeXMLHttpRequest();
    if (!request) return true;
    request.subscribe_url = subscribe_url;
    request.onreadystatechange = function () {
        if (request.readyState == 2)
            _redirectToSubscription(request);
    }
    request.open('GET', channel_guide_url, true);
    request.send(null);
    return false;
}

function _redirectToSubscription(request) {
    window.location.href = request.subscribe_url;
}

function handleFormLink(url) {
    showLoadIndicator();
    window.location.href = url;
    return false;
}

$(document).ajaxStart(function() {
    showLoadIndicator(true);
}).ajaxStop(function() {
    hideLoadIndicator();
});

if (isMiro()) {
    document.write('<style type="text/css">.only-in-miro {  display: inherit !important;}.only-in-browser {  display: none !important;}</style>');
}
<commit_msg>be more careful showing/hiding load indicator<commit_after>/* channelguide.js
 *
 * Shared functions used in channel guide
 */

function isMiro() {
    return (navigator.userAgent.indexOf('Miro') != -1 ||
	    (top.frames.length == 2 &&
	     top.frames[1].name == 'miro_guide_frame'));
}

function MiroVersion() {
    if (navigator.userAgent.indexOf('Miro') != -1) {
	return /Miro\/([\d.]+)/.exec(navigator.userAgent)[1];
    } else if (top.frames.length == 2 &&
	       top.frames[1].name == 'miro_guide_frame') {
	return "1.2";
    } else {
	return undefined;
    }
}

function getPreviousElement(elt) {
    while(elt.previousSibling) {
        elt = elt.previousSibling;
        if(elt.nodeType == 1) return elt;
    }
    return null;
}

function getNextElement(elt) {
    while(elt.nextSibling) {
        elt = elt.nextSibling;
        if(elt.nodeType == 1) return elt;
    }
    return null;
}

function showLoadIndicator(always) {
    indicator = $("#load-indicator");
    if ((!indicator.queue().length) && always || navigator.userAgent.indexOf('Miro') != -1) {
	indicator.animate({bottom: 0}, 'fast');
    }
}

function hideLoadIndicator() {
    indicator = $("#load-indicator").stop().css('bottom', '-30px');
}

function makeXMLHttpRequest() {
    if (window.XMLHttpRequest) { // Mozilla, Safari, ...
        return new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE
        try {
            return new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {}
        try {
            return new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
    }
    return null;
}

function doAjaxCall(url, callback) {
    var request = makeXMLHttpRequest();
    if (!request) return false;
    request.onreadystatechange = function() {
         if (request.readyState == 4) {
            if (request.status == 200) {
                callback(request);
            }
         }
    };
    request.open('GET', url, true);
    request.send(null);
    return true;
}

function ajaxLink(url, id) {
    var elt = document.getElementById(id);
    if (!elt) return true;
    function callback(request) {
        elt.innerHTML = request.responseText;
    }
    if (!doAjaxCall(url, callback)) return true;
    return false;
}

/* Handle a subscription link.  We need to hit the channelguide URL, then make
 * the browser navigate to the subscribe link.  This is basically a hack
 * because some older version of democracy get confused because the
 * channelguide URL redirects te the subscribe_url.
 */
function handleSubscriptionLink(channel_guide_url, subscribe_url) {
    if (isMiro() && MiroVersion() >= "1.5") return true;
    request = makeXMLHttpRequest();
    if (!request) return true;
    request.subscribe_url = subscribe_url;
    request.onreadystatechange = function () {
        if (request.readyState == 2)
            _redirectToSubscription(request);
    }
    request.open('GET', channel_guide_url, true);
    request.send(null);
    return false;
}

function _redirectToSubscription(request) {
    window.location.href = request.subscribe_url;
}

function handleFormLink(url) {
    showLoadIndicator();
    window.location.href = url;
    return false;
}

$(document).ajaxStart(function() {
    showLoadIndicator(true);
}).ajaxStop(function() {
    hideLoadIndicator();
});

if (isMiro()) {
    document.write('<style type="text/css">.only-in-miro {  display: inherit !important;}.only-in-browser {  display: none !important;}</style>');
}
<|endoftext|>