<commit_before>/**
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {number} Radius in meter
 */
function getDebouncedRadius( bounds ) {
	// This corresponds to the smallest circle around the bounding rectangle, so some results will
	// be outside that visible rectangle
	var radius = Math.floor( bounds.getCenter().distanceTo( bounds.getSouthWest() ) );
	// Rounding to 2 significant digits means we loose +/-5% in the absolute worst case
	// eslint-disable-next-line no-bitwise
	return radius.toPrecision( 2 ) | 0;
}

/**
 * De-bounce point to a certain degree of accuracy depending on zoom factor.
 *
 * @private
 * @param {L.LatLng} point
 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
 * @return {L.LatLng}
 */
function getDebouncedPoint( point, zoom ) {
	// Higher numbers = less precision = larger grid size = better debounce
	var looseness = 22;
	// 4 decimal places correspond to ~11m, 3 to ~110m, and so on
	var decimalPlaces = Math.max(
		// Zoom changes with a factor of 2, lat/lng with a factor of 10 per decimal place
		4 - Math.floor( ( looseness - zoom ) * Math.LN2 / Math.LN10 ),
		0
	);
	return new L.LatLng(
		point.lat.toFixed( decimalPlaces ),
		point.lng.toFixed( decimalPlaces )
	);
}

/**
 * Building the search query. Includes calculations to debounce the input
 * from the bounding box.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
 * @return {string}
 */
function getSearchQuery( bounds, zoom ) {
	var radius = getDebouncedRadius( bounds ),
		center = getDebouncedPoint( bounds.getCenter(), zoom );
	radius = radius % 1000 ? radius + 'm' : Math.round( radius / 1000 ) + 'km';
	return 'nearcoord:' + radius + ',' + center.lat + ',' + center.lng;
}

/**
 * @private
 * @param {string} title
 * @param {string|undefined} description
 * @param {string|undefined} imageUrl
 * @return {string}
 */
function createPopupHtml( title, description, imageUrl ) {
	title = mw.Title.newFromText( title );

	var linkHtml = mw.html.element( 'a', {
			href: title.getUrl(),
			target: '_blank'
		}, title.getPrefixedText() ),
		titleHtml = mw.html.element( 'div', {
			class: 'marker-title'
		}, new mw.html.Raw( linkHtml ) ),
		contentHtml = '';

	if ( description ) {
		contentHtml += mw.html.element( 'span', {}, description );
	}

	if ( imageUrl ) {
		contentHtml += mw.html.element( 'img', { src: imageUrl } );
	}

	if ( contentHtml ) {
		return titleHtml + mw.html.element( 'div', {
			class: 'marker-description'
		}, new mw.html.Raw( contentHtml ) );
	}
	return titleHtml;
}

// FIXME: This grows the more the user moves/zooms the map; maybe drop old values?
var knownPoints;

function initializeKnownPoints( map ) {
	/* global Set */
	knownPoints = new Set();
	map.eachLayer( function ( layer ) {
		// Note: mapbox does simple checks like this in other places as well
		if ( layer.getLatLng ) {
			var latLng = layer.getLatLng();
			knownPoints.add( makeHash( [ latLng.lng, latLng.lat ] ) );
		}
	} );
}

/**
 * @param {Object} geoJSON
 * @returns {boolean}
 */
function filterDuplicatePoints( geoJSON ) {
	var hash = makeHash( geoJSON.geometry.coordinates ),
		known = knownPoints.has( hash );
	if ( !known ) {
		knownPoints.add( hash );
	}
	return !known;
}

/**
 * @param {number[]} coordinates Longitude, latitude
 * @return {string}
 */
function makeHash( coordinates ) {
	// Maximum base using 0–9 and a–z as digits
	var base = 36;
	// Choosen so that the base-36 representation of [0…360[ is never longer than 5 characters,
	// i.e. use the full range of what these 5 characters can represent. This corresponds to a
	// precision of ~0.000006° or ~0.7m.
	var precision = 167961;
	/* eslint-disable no-bitwise */
	return ( ( coordinates[ 0 ] * precision ) | 0 ).toString( base ) + ',' +
		( ( coordinates[ 1 ] * precision ) | 0 ).toString( base );
	/* eslint-enable no-bitwise */
}

/**
 * @param {Object} geoJSON
 * @param {L.LatLng} latlng
 * @return {L.Marker}
 */
function createNearbyMarker( geoJSON, latlng ) {
	return L.marker( latlng, {
		icon: L.mapbox.marker.icon( { 'marker-color': geoJSON.properties[ 'marker-color' ] || 'a2a9b1' } )
	} );
}

module.exports = {

	/**
	 * @param {L.Map} map
	 * @param {boolean} show
	 */
	toggleNearbyLayer: function ( map, show ) {
		if ( show ) {
			this.fetchAndPopulateNearbyLayer( map );
			map.on( {
				moveend: this.onMapMoveOrZoomEnd.bind( this, map ),
				zoomend: this.onMapMoveOrZoomEnd.bind( this, map )
			}, this );
		} else {
			clearTimeout( this.fetchNearbyTimeout );
			map.off( 'moveend zoomend' );
			if ( this.nearbyLayer ) {
				map.removeLayer( this.nearbyLayer );
			}
			delete this.nearbyLayer;
		}
	},

	/**
	 * @private
	 * @param {L.Map} map
	 */
	onMapMoveOrZoomEnd: function ( map ) {
		clearTimeout( this.fetchNearbyTimeout );
		this.fetchNearbyTimeout = setTimeout( this.fetchAndPopulateNearbyLayer.bind( this, map ), 500 );
	},

	/**
	 * @private
	 * @param {L.Map} map
	 */
	fetchAndPopulateNearbyLayer: function ( map ) {
		this.fetch( map.getBounds(), map.getZoom() ).then( this.populateNearbyLayer.bind( this, map ) );
	},

	/**
	 * @private
	 * @param {L.LatLngBounds} bounds
	 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
	 * @return {jQuery.Promise}
	 */
	fetch: function ( bounds, zoom ) {
		// The maximum thumbnail limit is currently 50
		var limit = 50;
		// TODO: Cache results if bounds remains unchanged
		return ( new mw.Api( {
			/* TODO: Temporary override for local testing; remove when not needed any more *
			ajax: {
				url: 'https://en.wikipedia.org/w/api.php',
				headers: {
					'User-Agent': 'Kartographer - the WMF Content Transform Team (https://www.mediawiki.org/wiki/Content_Transform_Team)'
				}
			}
			/**/
		} ) ).get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			prop: 'coordinates|pageprops|pageimages|description',
			// co… arguments belong to prop=coordinates
			colimit: 'max',
			generator: 'search',
			// gsr… arguments belong to generator=search
			gsrsearch: getSearchQuery( bounds, zoom ),
			gsrnamespace: 0,
			gsrlimit: limit,
			// pp… arguments belong to prop=pageprops
			ppprop: 'displaytitle',
			// pi… arguments belong to prop=pageimages
			piprop: 'thumbnail',
			pithumbsize: 300,
			pilimit: limit
		} );
	},

	/**
	 * @private
	 * @param {L.Map} map
	 * @param {Object} queryApiResponse
	 */
	populateNearbyLayer: function ( map, queryApiResponse ) {
		var geoJSON = this.convertGeosearchToGeoJSON( queryApiResponse );
		if ( !this.nearbyLayer ) {
			initializeKnownPoints( map );
			this.nearbyLayer = this.createNearbyLayer( geoJSON );
			map.addLayer( this.nearbyLayer );
		} else {
			this.nearbyLayer.addData( geoJSON );
		}
	},

	/**
	 * @private
	 * @param {Object} response Raw data returned by the geosearch API.
	 * @return {Object[]} A list of GeoJSON features, one for each page.
	 */
	convertGeosearchToGeoJSON: function ( response ) {
		var pages = response.query && response.query.pages || [];

		return pages.reduce( function ( result, page ) {
			var coordinates = page.coordinates && page.coordinates[ 0 ];

			if ( coordinates ) {
				var thumbnail = page.thumbnail;

				result.push( {
					type: 'Feature',
					geometry: { type: 'Point', coordinates: [ coordinates.lon, coordinates.lat ] },
					properties: {
						title: page.title,
						description: page.description,
						imageUrl: thumbnail ? thumbnail.source : undefined,
						'marker-color': '0000ff'
					}
				} );
			}

			return result;
		}, [] );
	},

	/**
	 * @private
	 * @param {Object[]} geoJSON
	 * @return {L.GeoJSON}
	 */
	createNearbyLayer: function ( geoJSON ) {
		return L.geoJSON( geoJSON, {
			filter: filterDuplicatePoints,
			pointToLayer: createNearbyMarker
		} ).bindPopup( function ( layer ) {
			return createPopupHtml(
				layer.feature.properties.title,
				layer.feature.properties.description,
				layer.feature.properties.imageUrl
			);
		}, { closeButton: false } );
	}

};
<commit_msg>Drop nearby data that's too far away from the current zoom level<commit_after>var currentZoom,
	nearbyLayers = {},
	knownPoints = {};

/**
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {number} Radius in meter
 */
function getDebouncedRadius( bounds ) {
	// This corresponds to the smallest circle around the bounding rectangle, so some results will
	// be outside that visible rectangle
	var radius = Math.floor( bounds.getCenter().distanceTo( bounds.getSouthWest() ) );
	// Rounding to 2 significant digits means we loose +/-5% in the absolute worst case
	// eslint-disable-next-line no-bitwise
	return radius.toPrecision( 2 ) | 0;
}

/**
 * De-bounce point to a certain degree of accuracy depending on zoom factor.
 *
 * @private
 * @param {L.LatLng} point
 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
 * @return {L.LatLng}
 */
function getDebouncedPoint( point, zoom ) {
	// Higher numbers = less precision = larger grid size = better debounce
	var looseness = 22;
	// 4 decimal places correspond to ~11m, 3 to ~110m, and so on
	var decimalPlaces = Math.max(
		// Zoom changes with a factor of 2, lat/lng with a factor of 10 per decimal place
		4 - Math.floor( ( looseness - zoom ) * Math.LN2 / Math.LN10 ),
		0
	);
	return new L.LatLng(
		point.lat.toFixed( decimalPlaces ),
		point.lng.toFixed( decimalPlaces )
	);
}

/**
 * Building the search query. Includes calculations to debounce the input
 * from the bounding box.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
 * @return {string}
 */
function getSearchQuery( bounds, zoom ) {
	var radius = getDebouncedRadius( bounds ),
		center = getDebouncedPoint( bounds.getCenter(), zoom );
	radius = radius % 1000 ? radius + 'm' : Math.round( radius / 1000 ) + 'km';
	return 'nearcoord:' + radius + ',' + center.lat + ',' + center.lng;
}

/**
 * @private
 * @param {string} title
 * @param {string|undefined} description
 * @param {string|undefined} imageUrl
 * @return {string}
 */
function createPopupHtml( title, description, imageUrl ) {
	title = mw.Title.newFromText( title );

	var linkHtml = mw.html.element( 'a', {
			href: title.getUrl(),
			target: '_blank'
		}, title.getPrefixedText() ),
		titleHtml = mw.html.element( 'div', {
			class: 'marker-title'
		}, new mw.html.Raw( linkHtml ) ),
		contentHtml = '';

	if ( description ) {
		contentHtml += mw.html.element( 'span', {}, description );
	}

	if ( imageUrl ) {
		contentHtml += mw.html.element( 'img', { src: imageUrl } );
	}

	if ( contentHtml ) {
		return titleHtml + mw.html.element( 'div', {
			class: 'marker-description'
		}, new mw.html.Raw( contentHtml ) );
	}
	return titleHtml;
}

function initializeKnownPoints( map ) {
	/* global Set */
	knownPoints.featureLayer = new Set();
	map.eachLayer( function ( layer ) {
		// Note: mapbox does simple checks like this in other places as well
		if ( layer.getLatLng ) {
			var latLng = layer.getLatLng();
			knownPoints.featureLayer.add( makeHash( [ latLng.lng, latLng.lat ] ) );
		}
	} );
}

/**
 * @param {Object} geoJSON
 * @returns {boolean}
 */
function filterDuplicatePoints( geoJSON ) {
	var hash = makeHash( geoJSON.geometry.coordinates );
	for ( var i in knownPoints ) {
		if ( knownPoints[ i ].has( hash ) ) {
			return false;
		}
	}
	knownPoints[ currentZoom ].add( hash );
	return true;
}

/**
 * @param {number[]} coordinates Longitude, latitude
 * @return {string}
 */
function makeHash( coordinates ) {
	// Maximum base using 0–9 and a–z as digits
	var base = 36;
	// Choosen so that the base-36 representation of [0…360[ is never longer than 5 characters,
	// i.e. use the full range of what these 5 characters can represent. This corresponds to a
	// precision of ~0.000006° or ~0.7m.
	var precision = 167961;
	/* eslint-disable no-bitwise */
	return ( ( coordinates[ 0 ] * precision ) | 0 ).toString( base ) + ',' +
		( ( coordinates[ 1 ] * precision ) | 0 ).toString( base );
	/* eslint-enable no-bitwise */
}

/**
 * @param {Object} geoJSON
 * @param {L.LatLng} latlng
 * @return {L.Marker}
 */
function createNearbyMarker( geoJSON, latlng ) {
	return L.marker( latlng, {
		icon: L.mapbox.marker.icon( { 'marker-color': geoJSON.properties[ 'marker-color' ] || 'a2a9b1' } )
	} );
}

module.exports = {

	/**
	 * @param {L.Map} map
	 * @param {boolean} show
	 */
	toggleNearbyLayer: function ( map, show ) {
		if ( show ) {
			initializeKnownPoints( map );
			this.fetchAndPopulateNearbyLayer( map );
			map.on( {
				moveend: this.onMapMoveOrZoomEnd.bind( this, map ),
				zoomend: this.onMapMoveOrZoomEnd.bind( this, map )
			} );
		} else {
			clearTimeout( this.fetchNearbyTimeout );
			map.off( 'moveend zoomend' );
			for ( var i in nearbyLayers ) {
				map.removeLayer( nearbyLayers[ i ] );
				delete nearbyLayers[ i ];
				delete knownPoints[ i ];
			}
		}
	},

	/**
	 * @private
	 * @param {L.Map} map
	 */
	onMapMoveOrZoomEnd: function ( map ) {
		clearTimeout( this.fetchNearbyTimeout );
		this.fetchNearbyTimeout = setTimeout( this.fetchAndPopulateNearbyLayer.bind( this, map ), 500 );
	},

	/**
	 * @private
	 * @param {L.Map} map
	 */
	fetchAndPopulateNearbyLayer: function ( map ) {
		this.fetch( map.getBounds(), map.getZoom() ).then( this.populateNearbyLayer.bind( this, map ) );
	},

	/**
	 * @private
	 * @param {L.LatLngBounds} bounds
	 * @param {number} zoom Typically ranging from 0 (entire world) to 19 (nearest)
	 * @return {jQuery.Promise}
	 */
	fetch: function ( bounds, zoom ) {
		// The maximum thumbnail limit is currently 50
		var limit = 50;
		// TODO: Cache results if bounds remains unchanged
		return ( new mw.Api( {
			/* TODO: Temporary override for local testing; remove when not needed any more *
			ajax: {
				url: 'https://en.wikipedia.org/w/api.php',
				headers: {
					'User-Agent': 'Kartographer - the WMF Content Transform Team (https://www.mediawiki.org/wiki/Content_Transform_Team)'
				}
			}
			/**/
		} ) ).get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			prop: 'coordinates|pageprops|pageimages|description',
			// co… arguments belong to prop=coordinates
			colimit: 'max',
			generator: 'search',
			// gsr… arguments belong to generator=search
			gsrsearch: getSearchQuery( bounds, zoom ),
			gsrnamespace: 0,
			gsrlimit: limit,
			// pp… arguments belong to prop=pageprops
			ppprop: 'displaytitle',
			// pi… arguments belong to prop=pageimages
			piprop: 'thumbnail',
			pithumbsize: 300,
			pilimit: limit
		} );
	},

	/**
	 * @private
	 * @param {L.Map} map
	 * @param {Object} queryApiResponse
	 */
	populateNearbyLayer: function ( map, queryApiResponse ) {
		var zoom = map.getZoom();
		var geoJSON = this.convertGeosearchToGeoJSON( queryApiResponse );

		// Drop data from zoom levels that are too far away from the current zoom level
		var keepDataZoomLimit = 3;
		for ( var i in nearbyLayers ) {
			if ( Math.abs( zoom - i ) > keepDataZoomLimit ) {
				map.removeLayer( nearbyLayers[ i ] );
				delete nearbyLayers[ i ];
				delete knownPoints[ i ];
			}
		}

		// TODO: Is there a better way to pass this information to the filter callback?
		currentZoom = zoom;
		if ( !nearbyLayers[ zoom ] ) {
			knownPoints[ zoom ] = new Set();
			nearbyLayers[ zoom ] = this.createNearbyLayer( geoJSON );
			map.addLayer( nearbyLayers[ zoom ] );
		} else {
			nearbyLayers[ zoom ].addData( geoJSON );
		}
	},

	/**
	 * @private
	 * @param {Object} response Raw data returned by the geosearch API.
	 * @return {Object[]} A list of GeoJSON features, one for each page.
	 */
	convertGeosearchToGeoJSON: function ( response ) {
		var pages = response.query && response.query.pages || [];

		return pages.reduce( function ( result, page ) {
			var coordinates = page.coordinates && page.coordinates[ 0 ];

			if ( coordinates ) {
				var thumbnail = page.thumbnail;

				result.push( {
					type: 'Feature',
					geometry: { type: 'Point', coordinates: [ coordinates.lon, coordinates.lat ] },
					properties: {
						title: page.title,
						description: page.description,
						imageUrl: thumbnail ? thumbnail.source : undefined,
						'marker-color': '0000ff'
					}
				} );
			}

			return result;
		}, [] );
	},

	/**
	 * @private
	 * @param {Object[]} geoJSON
	 * @return {L.GeoJSON}
	 */
	createNearbyLayer: function ( geoJSON ) {
		return L.geoJSON( geoJSON, {
			filter: filterDuplicatePoints,
			pointToLayer: createNearbyMarker
		} ).bindPopup( function ( layer ) {
			return createPopupHtml(
				layer.feature.properties.title,
				layer.feature.properties.description,
				layer.feature.properties.imageUrl
			);
		}, { closeButton: false } );
	}

};
<|endoftext|>


<commit_before>/*Copyright 2019 Bang & Olufsen A/S
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

// HIFIBERRY SOFTWARE UPDATE FOR BEOCREATE

var exec = require("child_process").exec;
var spawn = require("child_process").spawn;
var fs = require("fs");

var debug = beo.debug;
var version = require("./package.json").version;

var settings = {
	autoUpdate: false,
	autoCheck: false
};

beo.bus.on('general', function(event) {

	if (event.header == "startup") {

	}

	if (event.header == "activatedExtension") {
		if (event.content == "software-update") {
			checkForUpdate();
		}

		if (event.content == "system-settings") {
			if (newVersion) {
				beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
			} else {
				beo.sendToUI("software-update", {header: "badge"});
			}
		}
	}
});

beo.bus.on('software-update', function(event) {

	if (event.header == "install") {
		installUpdate();
	}

});

var lastChecked = 0;
var newVersion = null;
var releaseNotes = "";

function checkForUpdate(forceCheck) {
	checkTime = new Date().getTime();
	if (checkTime - lastChecked > 300000 || forceCheck) {
		exec("/opt/hifiberry/bin/update --check", function(error, stdout, stderr) {
			lastChecked = checkTime;
			updateLines = stdout.trim().split("\n");
			newVersion = updateLines[0];
			if (newVersion) {
				if (debug) console.log("Software update is available – release "+newVersion+".");
				updateLines.splice(0, 1);
				releaseNotes = updateLines.join("\n").trim();
				beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
			} else {
				newVersion = null;
				if (debug) console.log("Product appears to be up to date.");
				beo.sendToUI("software-update", {header: "upToDate"});
			}
		});
	} else {
		if (debug) console.log("Checked for update less than 5 minutes ago, sending cached info.");
		if (newVersion) {
			beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
		} else {
			beo.sendToUI("software-update", {header: "upToDate"});
		}
	}
}

var updateInProgress = false;
var updatePhase = 0;
var previousProgress = -5;
function installUpdate() {
	if (!updateInProgress) {
		updateInProgress = true;
		if (beo.developerMode) {
			if (debug) console.log("Starting software update simulation.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--simulate"]);
		} else {
			if (debug) console.log("Starting software update.");
			updateProcess = spawn("/opt/hifiberry/bin/update");
		}
		//updateProcess = spawn("curl", ["https://www.hifiberry.com/images/updater-20191030-pi3.tar.gz", "-o", "updater.tar.gz", "--progress-bar"], {cwd: "/data"});

		updateProcess.stdout.on('data', function (data) {
			//console.log('stdout: ' + data.toString());
			data = data.toString();
			switch (updatePhase) {
				case 0: // Download starting.
					if (data.indexOf("downloading http") != -1) {
						updatePhase = 1;
						if (debug) console.log("Downloading update...");
					}
					break;
				case 1:
					if (data.indexOf("Could not download updater") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updateError", content: {reason: "downloadError"}});
						console.error("Update download was unsuccessful.");
					}
					if (data.indexOf("unmounting") != -1) {
						updatePhase = 2;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 50, phase: "extractingFirmware"}});
						if (debug) console.log("Extracting firmware...");
					}
					break;
				case 2:
					if (data.indexOf("mounting") != -1) {
						updatePhase = 3;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 70, phase: "resizing"}});
						if (debug) console.log("Resizing file system...");
					}
					break;
				case 3:
					if (data.indexOf("extracting new kernel") != -1) {
						updatePhase = 4;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 75, phase: "extractingKernel"}});
						if (debug) console.log("Extracting new kernel...");
					}
					break;
				case 4:
					if (data.indexOf("migrating") != -1) {
						updatePhase = 5;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 85, phase: "copyingFiles"}});
						if (debug) console.log("Copying files from current installation to new installation...");
					}
					break;
				case 5:
					if (data.indexOf("switching root file system") != -1) {
						updatePhase = 6;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 95, phase: "finalising"}});
						if (debug) console.log("Switching root file system...");
					}
					if (data.indexOf("not switching to new version") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "doneSimulation"}});
						if (debug) console.log("Update simulation complete.");
					}
					break;
				case 6:
					if (data.indexOf("removing") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "done"}});
						if (debug) console.log("Removing updater and restarting...");
					}
					break;
			}
		});

		updateProcess.stderr.on('data', function (data) {
			data = data.toString();
			if (updatePhase == 1) { // Updates from curl.
				progressIndex = data.indexOf("%");
				if (progressIndex != -1) {
					percentage = parseFloat(data.substr(progressIndex-5, 5));
					if (percentage - previousProgress >= 5) {
						beo.sendToUI("software-update", {header: "updating", content: {progress: Math.round(percentage/2), phase: "download"}});
						previousProgress = percentage;
					}
				}
			}

		});

		updateProcess.on('exit', function (code) {
			updateInProgress = false;
			console.log('Software updater process exited (code '+code.toString()+").");
		});
	}
}

module.exports = {
	version: version
};

<commit_msg>Automatically check for software updates once a day<commit_after>/*Copyright 2019 Bang & Olufsen A/S
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

// HIFIBERRY SOFTWARE UPDATE FOR BEOCREATE

var exec = require("child_process").exec;
var spawn = require("child_process").spawn;
var fs = require("fs");

var debug = beo.debug;
var version = require("./package.json").version;

var settings = {
	autoUpdate: false,
	autoCheck: true
};

beo.bus.on('general', function(event) {

	if (event.header == "startup") {

		if (settings.autoCheck) {
			startAutoCheckTimeout();
		}

	}

	if (event.header == "activatedExtension") {
		if (event.content == "software-update") {
			checkForUpdate();
		}

		if (event.content == "general-settings") {
			if (newVersion) {
				beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
			} else {
				beo.sendToUI("software-update", {header: "badge"});
			}
		}
	}

	if (event.header == "connected") {
		if (newVersion) {
			beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
		}
	}
});

beo.bus.on('software-update', function(event) {

	if (event.header == "install") {
		installUpdate();
	}

});

var lastChecked = 0;
var newVersion = null;
var releaseNotes = "";

function checkForUpdate(forceCheck) {
	checkTime = new Date().getTime();
	if (checkTime - lastChecked > 300000 || forceCheck) {
		exec("/opt/hifiberry/bin/update --check", function(error, stdout, stderr) {
			lastChecked = checkTime;
			updateLines = stdout.trim().split("\n");
			newVersion = updateLines[0];
			if (newVersion) {
				if (debug) console.log("Software update is available – release "+newVersion+".");
				updateLines.splice(0, 1);
				releaseNotes = updateLines.join("\n").trim();
				beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
			} else {
				newVersion = null;
				releaseNotes = "";
				if (debug) console.log("Product appears to be up to date.");
				beo.sendToUI("software-update", {header: "upToDate"});
			}
		});
	} else {
		if (debug) console.log("Checked for update less than 5 minutes ago, sending cached info.");
		if (newVersion) {
			beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
		} else {
			beo.sendToUI("software-update", {header: "upToDate"});
		}
	}
	if (settings.autoCheck) startAutoCheckTimeout();
}

var updateInProgress = false;
var updatePhase = 0;
var previousProgress = -5;
function installUpdate() {
	if (!updateInProgress) {
		updateInProgress = true;
		if (beo.developerMode) {
			if (debug) console.log("Starting software update simulation.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--simulate"]);
		} else {
			if (debug) console.log("Starting software update.");
			updateProcess = spawn("/opt/hifiberry/bin/update");
		}
		//updateProcess = spawn("curl", ["https://www.hifiberry.com/images/updater-20191030-pi3.tar.gz", "-o", "updater.tar.gz", "--progress-bar"], {cwd: "/data"});

		updateProcess.stdout.on('data', function (data) {
			//console.log('stdout: ' + data.toString());
			data = data.toString();
			switch (updatePhase) {
				case 0: // Download starting.
					if (data.indexOf("downloading http") != -1) {
						updatePhase = 1;
						if (debug) console.log("Downloading update...");
					}
					break;
				case 1:
					if (data.indexOf("Could not download updater") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updateError", content: {reason: "downloadError"}});
						console.error("Update download was unsuccessful.");
					}
					if (data.indexOf("unmounting") != -1) {
						updatePhase = 2;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 50, phase: "extractingFirmware"}});
						if (debug) console.log("Extracting firmware...");
					}
					break;
				case 2:
					if (data.indexOf("mounting") != -1) {
						updatePhase = 3;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 70, phase: "resizing"}});
						if (debug) console.log("Resizing file system...");
					}
					break;
				case 3:
					if (data.indexOf("extracting new kernel") != -1) {
						updatePhase = 4;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 75, phase: "extractingKernel"}});
						if (debug) console.log("Extracting new kernel...");
					}
					break;
				case 4:
					if (data.indexOf("migrating") != -1) {
						updatePhase = 5;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 85, phase: "copyingFiles"}});
						if (debug) console.log("Copying files from current installation to new installation...");
					}
					break;
				case 5:
					if (data.indexOf("switching root file system") != -1) {
						updatePhase = 6;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 95, phase: "finalising"}});
						if (debug) console.log("Switching root file system...");
					}
					if (data.indexOf("not switching to new version") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "doneSimulation"}});
						if (debug) console.log("Update simulation complete.");
					}
					break;
				case 6:
					if (data.indexOf("removing") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "done"}});
						if (debug) console.log("Removing updater and restarting...");
					}
					break;
			}
		});

		updateProcess.stderr.on('data', function (data) {
			data = data.toString();
			if (updatePhase == 1) { // Updates from curl.
				progressIndex = data.indexOf("%");
				if (progressIndex != -1) {
					percentage = parseFloat(data.substr(progressIndex-5, 5));
					if (percentage - previousProgress >= 5) {
						beo.sendToUI("software-update", {header: "updating", content: {progress: Math.round(percentage/2), phase: "download"}});
						previousProgress = percentage;
					}
				}
			}

		});

		updateProcess.on('exit', function (code) {
			updateInProgress = false;
			console.log('Software updater process exited (code '+code.toString()+").");
		});
	}
}

var autoCheckTimeout;
function startAutoCheckTimeout() {
	clearTimeout(autoCheckTimeout);
	autoCheckTimeout = setTimeout(function() {
		checkForUpdate();
	}, 86400000) // Check once per day.
}

module.exports = {
	version: version
};

<|endoftext|>


<commit_before>define(["Handlebars"], function (Handlebars) {

	Handlebars.registerHelper("result_display", function(value) {
		// thanks to John Gruber for this regexp http://daringfireball.net/2010/07/improved_regex_for_matching_urls
		// also to "Searls" for his port to JS https://gist.github.com/1033143
		var urlRegexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?������]))/ig;

		if ($.isPlainObject(value))
			return JSON.stringify(value);
		else if (value == null)
			return "(null)";
		else if (value === false)
			return "false";
		else if (typeof value === "string" && value.match(urlRegexp) && Handlebars.Utils.escapeExpression(value) == value)
			return new Handlebars.SafeString(value.replace(urlRegexp, "$1"));
		else
			return value;
	});

	// returns nothing
});<commit_msg>Adding back in reference to jQuery<commit_after>define(["jQuery","Handlebars"], function ($,Handlebars) {

	Handlebars.registerHelper("result_display", function(value) {
		// thanks to John Gruber for this regexp http://daringfireball.net/2010/07/improved_regex_for_matching_urls
		// also to "Searls" for his port to JS https://gist.github.com/1033143
		var urlRegexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?������]))/ig;

		if ($.isPlainObject(value))
			return JSON.stringify(value);
		else if (value == null)
			return "(null)";
		else if (value === false)
			return "false";
		else if (typeof value === "string" && value.match(urlRegexp) && Handlebars.Utils.escapeExpression(value) == value)
			return new Handlebars.SafeString(value.replace(urlRegexp, "$1"));
		else
			return value;
	});

	// returns nothing
});<|endoftext|>


<commit_before>/**
* map tab shown in cartodb admin
*/

cdb.admin.MapTab = cdb.core.View.extend({

  events: {
    'click .share a': 'shareMap'
  },

  className: 'map',
  animation_time: 300,

  initialize: function() {
    this.template = this.getTemplate('table/views/maptab');
    this.map = this.model;
    this.map_enabled = false;
    this.featureHovered = null;
    this.infowindowModel = this.options.infowindow;

    //this.add_related_model(this.options.dataLayer);
    this.add_related_model(this.map);
    this.add_related_model(this.options.table);
    this.add_related_model(this.infowindowModel);

    var self = this;

    cdb.god.bind("hidePanel", function(ev) {
      self._moveShareButton("hide");
    });
    cdb.god.bind("showPanel", function(ev) {
      self._moveShareButton("show");
    });
    cdb.god.bind("narrowPanel", function(ev) {
      self._moveShareButton("narrow");
    });

    this.map.bind('change:provider', this.switchMapType, this);
    this.options.table.bind('change:privacy', this._setPrivacy, this);

    _.bindAll(this, 'showNoGeoRefWarning');
  },

  clearMap: function() {
    this.mapView.clean();
    this.zoom.clean();
    this.infowindow.clean();

    delete this.mapView;
    delete this.zoom;
    delete this.infowindow;

    this.map_enabled = false;
  },

  _setPrivacy: function() {
    if (this.options.table.get('privacy').toLowerCase() != "public") {
      this.$el.find('div.share a').addClass('disabled');
    } else {
      this.$el.find('div.share a').removeClass('disabled');
    }
  },

  /**
  * this function is used when the map library is changed. Each map library
  * works in different way and need to recreate all the components again
  */
  switchMapType: function() {

    if (this.map_enabled) {
      this.clearMap();
      this.render();
      this.enableMap();
    }
  },

  /**
  * map can't be loaded from the beggining, it needs the DOM to be loaded
  * so we wait until is actually shown to create the mapview and show it
  */
  enableMap: function() {
    this.$('.tipsy').remove();
    var self = this;

    if (!this.map_enabled) {
      var div = this.$('#map')
      , base_maps = this.$('.base_maps');

      if (!this.baseLayerChooser) {

        this.baseLayerChooser = new cdb.admin.BaseMapChooser({
          model: this.map,
          baseLayers: this.options.baseLayers
        });

      }

      base_maps.append(this.baseLayerChooser.render().el);

      var mapViewClass = cdb.geo.LeafletMapView;
      if(this.map.get('provider') === 'googlemaps') {
        mapViewClass = cdb.geo.GoogleMapsMapView;
      }

      this.mapView = new mapViewClass({
        el: div,
        map: this.map
      });

      this.clickTimeout = null;
      this._bindMissingClickEvents();

      this.map_enabled = true;

      this.mapView.bind('newLayerView', self._bindDataLayer, self);

      // bind data layer if it is already added to the map
      var dataLayer = this.map.get('dataLayer');
      if(dataLayer) {
        var dl = this.mapView.getLayerByCid(self.map.get('dataLayer').cid);
        this._bindDataLayer(dl);
      }

      var zoomControl = new cdb.geo.ui.Zoom({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/zoom_control")
      });
      this.$el.append(zoomControl.render().$el);
      this.zoom = zoomControl;

      // infowindow
      self.infowindow = new cdb.admin.MapInfowindow({
        model: self.infowindowModel,
        //template: cdb.templates.getTemplate('table/views/infowindow'),
        mapView: self.mapView,
        table: self.options.table
      });
      self.mapView.$el.append(self.infowindow.el);

      // editing geometry
      this.geometryEditor = new cdb.admin.GeometryEditor({
        model: this.options.table
      });
      this.geometryEditor.mapView = this.mapView;
      self.mapView.$el.append(self.geometryEditor.render().el);
      this.geometryEditor.hide();

      this.geometryEditor.bind('editStart', this.hideDataLayer, this);
      this.geometryEditor.bind('editDiscard', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.updateDataLayerView, this);
      self.infowindow.bind('editGeom', this.geometryEditor.editGeom, this.geometryEditor);
      self.infowindow.bind('removeGeom', this.updateDataLayerView, this);

      this.options.table.bind('change:dataSource', function() {
        self.geometryEditor.discard();
      });

      // HACK
      // wait a little bit to give time to the mapview
      // to estabilize
      setTimeout(function() {
        self.mapView.setAutoSaveBounds();
      }, 1000);

      this.checkGeoRef();

    }
  },

  checkGeoRef: function() {
    var geoColumns = this.options.table.geomColumnTypes();
    if(!geoColumns || !geoColumns.length || geoColumns.length == 0) {
      this.trigger('noGeoRef');
      this.showNoGeoRefWarning();
    } else {
      this.trigger('geoRef');
    }
  },

  /**
  * this function binds click and dblclick events
  * in order to not raise click when user does a dblclick
  *
  * it raises a missingClick when the user clicks on the map
  * but not over a feature or ui component
  */
  _bindMissingClickEvents: function() {
    var self = this;
    this.mapView.bind('click', function(e) {
      if(self.clickTimeout === null) {
        self.clickTimeout = setTimeout(function() {
          self.clickTimeout = null;
          if(!self.featureHovered) {
            self.trigger('missingClick');
          }
        }, 150);
      }
      //google maps does not send an event
      if(!self.featureHovered && e.preventDefault) {
        e.preventDefault();
        e.stopPropagation();
      }
    });

    this.mapView.bind('dblclick', function() {
      if(self.clickTimeout !== null) {
        clearTimeout(self.clickTimeout);
        self.clickTimeout = null;
      }
    });
  },

  /**
  * when the layer view is created this method is called
  * to attach all the click events
  */
  _bindDataLayer: function(layer) {
    var self = this;

    var layerType = layer.model.get('type');

    if (layerType === 'CartoDB') { // unbind previos stuff

      if (self.layerDataView) {
        self.layerDataView.unbind(null, null, this);
      }
      var dataLayer = self.map.get('dataLayer');

      if (dataLayer) {

        var cid = dataLayer.cid;

        self.layerDataView = self.mapView.getLayerByCid(cid);

        if (self.layerDataView) {
          self.layerDataView.bind('featureClick', self.featureClick, self);
          self.layerDataView.bind('featureOut',   self.featureOut,   self);
          self.layerDataView.bind('featureOver',  self.featureOver,  self);
        }

      }

    } else if (layerType === 'Plain') {

      // var color = layer.model.get("color");
      // console.log(color, this.baseLayerChooser);
      // this.baseLayerChooser.backgroundMapColorView.setColor(color);

    }

  },

  featureOver: function(e, latlon, pxPos, data) {
    this.mapView.setCursor('pointer');
    this.featureHovered = data;
  },

  featureOut: function() {
    this.mapView.setCursor('auto');
    this.featureHovered = null;
  },

  featureClick: function(e, latlon, pxPos, data) {
    if(data.cartodb_id) {
      this.infowindow
      .setLatLng(latlon)
      .setFeatureInfo(data.cartodb_id)
      .showInfowindow();
    } else {
      cdb.log.error("can't show infowindow, no cartodb_id on data");
    }
  },

  _moveShareButton: function(type) {
    var right = 0;
    if (type == "show") {
      right = 592;
    } else if (type == "hide") {
      right = 57;
    } else {
      right = 442;
    }

    this.$el.find("div.share")
    .animate({
      right: right
    }, this.animation_time);
  },

  render: function() {
    this.$el.html('');
    this.$el.append(this.template());
    if(!this.model.isNew()) {
      this.checkGeoRef();
    }
    return this;
  },

  shareMap: function(e) {
    e.preventDefault();

    var privacy = this.options.table.get('privacy').toLowerCase()
      , dlg;

    // If the table is not public, we'll show a warning dialog
    if (privacy != 'public') {
      dlg = new cdb.admin.NoShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    } else {
      dlg = new cdb.admin.ShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    }

    dlg.appendToBody().open();

    return false;
  },

  addGeometry: function() {
    // row is saved by geometry editor if it is needed
    this.geometryEditor.createGeom(this.options.table.data().newRow(), 'polygon');
  },

  showDataLayer: function() {
    var dataLayer = this.map.get('dataLayer');
    this.layerDataView.show();
  },

  hideDataLayer: function() {
    var dataLayer = this.map.get('dataLayer');
    this.layerDataView.hide();
  },

  /**
  * reload tiles
  */
  updateDataLayerView: function() {
    this.layerDataView.reload();
  },
  /**
  * Paints a dialog with a warning when the user hasn't any georeferenced row
  * @method showNoGeorefWarning
  */
  showNoGeoRefWarning: function() {
    if(!this.noGeoRefDialog) {
      this.noGeoRefDialog = new cdb.admin.NoGeoRefDataDialog();
      this.retrigger('georeference', this.noGeoRefDialog);
      this.retrigger('manual', this.noGeoRefDialog);
      this.noGeoRefDialog.render().$el.appendTo(this.$el);
    }
    // if the dialog already has been shown, we don't show it again

  }

});

<commit_msg>removed unnused code<commit_after>/**
* map tab shown in cartodb admin
*/

cdb.admin.MapTab = cdb.core.View.extend({

  events: {
    'click .share a': 'shareMap'
  },

  className: 'map',
  animation_time: 300,

  initialize: function() {
    this.template = this.getTemplate('table/views/maptab');
    this.map = this.model;
    this.map_enabled = false;
    this.featureHovered = null;
    this.infowindowModel = this.options.infowindow;

    //this.add_related_model(this.options.dataLayer);
    this.add_related_model(this.map);
    this.add_related_model(this.options.table);
    this.add_related_model(this.infowindowModel);

    var self = this;

    cdb.god.bind("hidePanel", function(ev) {
      self._moveShareButton("hide");
    });
    cdb.god.bind("showPanel", function(ev) {
      self._moveShareButton("show");
    });
    cdb.god.bind("narrowPanel", function(ev) {
      self._moveShareButton("narrow");
    });

    this.map.bind('change:provider', this.switchMapType, this);
    this.options.table.bind('change:privacy', this._setPrivacy, this);

    _.bindAll(this, 'showNoGeoRefWarning');
  },

  clearMap: function() {
    this.mapView.clean();
    this.zoom.clean();
    this.infowindow.clean();

    delete this.mapView;
    delete this.zoom;
    delete this.infowindow;

    this.map_enabled = false;
  },

  _setPrivacy: function() {
    if (this.options.table.get('privacy').toLowerCase() != "public") {
      this.$el.find('div.share a').addClass('disabled');
    } else {
      this.$el.find('div.share a').removeClass('disabled');
    }
  },

  /**
  * this function is used when the map library is changed. Each map library
  * works in different way and need to recreate all the components again
  */
  switchMapType: function() {

    if (this.map_enabled) {
      this.clearMap();
      this.render();
      this.enableMap();
    }
  },

  /**
  * map can't be loaded from the beggining, it needs the DOM to be loaded
  * so we wait until is actually shown to create the mapview and show it
  */
  enableMap: function() {
    this.$('.tipsy').remove();
    var self = this;

    if (!this.map_enabled) {
      var div = this.$('#map')
      , base_maps = this.$('.base_maps');

      if (!this.baseLayerChooser) {

        this.baseLayerChooser = new cdb.admin.BaseMapChooser({
          model: this.map,
          baseLayers: this.options.baseLayers
        });

      }

      base_maps.append(this.baseLayerChooser.render().el);

      var mapViewClass = cdb.geo.LeafletMapView;
      if(this.map.get('provider') === 'googlemaps') {
        mapViewClass = cdb.geo.GoogleMapsMapView;
      }

      this.mapView = new mapViewClass({
        el: div,
        map: this.map
      });

      this.clickTimeout = null;
      this._bindMissingClickEvents();

      this.map_enabled = true;

      this.mapView.bind('newLayerView', self._bindDataLayer, self);

      // bind data layer if it is already added to the map
      var dataLayer = this.map.get('dataLayer');
      if(dataLayer) {
        var dl = this.mapView.getLayerByCid(self.map.get('dataLayer').cid);
        this._bindDataLayer(dl);
      }

      var zoomControl = new cdb.geo.ui.Zoom({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/zoom_control")
      });
      this.$el.append(zoomControl.render().$el);
      this.zoom = zoomControl;

      // infowindow
      self.infowindow = new cdb.admin.MapInfowindow({
        model: self.infowindowModel,
        //template: cdb.templates.getTemplate('table/views/infowindow'),
        mapView: self.mapView,
        table: self.options.table
      });
      self.mapView.$el.append(self.infowindow.el);

      // editing geometry
      this.geometryEditor = new cdb.admin.GeometryEditor({
        model: this.options.table
      });
      this.geometryEditor.mapView = this.mapView;
      self.mapView.$el.append(self.geometryEditor.render().el);
      this.geometryEditor.hide();

      this.geometryEditor.bind('editStart', this.hideDataLayer, this);
      this.geometryEditor.bind('editDiscard', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.updateDataLayerView, this);
      self.infowindow.bind('editGeom', this.geometryEditor.editGeom, this.geometryEditor);
      self.infowindow.bind('removeGeom', this.updateDataLayerView, this);

      this.options.table.bind('change:dataSource', function() {
        self.geometryEditor.discard();
      });

      // HACK
      // wait a little bit to give time to the mapview
      // to estabilize
      setTimeout(function() {
        self.mapView.setAutoSaveBounds();
      }, 1000);

      this.checkGeoRef();

    }
  },

  checkGeoRef: function() {
    var geoColumns = this.options.table.geomColumnTypes();
    if(!geoColumns || !geoColumns.length || geoColumns.length == 0) {
      this.trigger('noGeoRef');
      this.showNoGeoRefWarning();
    } else {
      this.trigger('geoRef');
    }
  },

  /**
  * this function binds click and dblclick events
  * in order to not raise click when user does a dblclick
  *
  * it raises a missingClick when the user clicks on the map
  * but not over a feature or ui component
  */
  _bindMissingClickEvents: function() {
    var self = this;
    this.mapView.bind('click', function(e) {
      if(self.clickTimeout === null) {
        self.clickTimeout = setTimeout(function() {
          self.clickTimeout = null;
          if(!self.featureHovered) {
            self.trigger('missingClick');
          }
        }, 150);
      }
      //google maps does not send an event
      if(!self.featureHovered && e.preventDefault) {
        e.preventDefault();
        e.stopPropagation();
      }
    });

    this.mapView.bind('dblclick', function() {
      if(self.clickTimeout !== null) {
        clearTimeout(self.clickTimeout);
        self.clickTimeout = null;
      }
    });
  },

  /**
  * when the layer view is created this method is called
  * to attach all the click events
  */
  _bindDataLayer: function(layer) {
    var self = this;

    var layerType = layer.model.get('type');

    if (layerType === 'CartoDB') { // unbind previos stuff

      if (self.layerDataView) {
        self.layerDataView.unbind(null, null, this);
      }
      var dataLayer = self.map.get('dataLayer');

      if (dataLayer) {

        var cid = dataLayer.cid;

        self.layerDataView = self.mapView.getLayerByCid(cid);

        if (self.layerDataView) {
          self.layerDataView.bind('featureClick', self.featureClick, self);
          self.layerDataView.bind('featureOut',   self.featureOut,   self);
          self.layerDataView.bind('featureOver',  self.featureOver,  self);
        }

      }

    }

  },

  featureOver: function(e, latlon, pxPos, data) {
    this.mapView.setCursor('pointer');
    this.featureHovered = data;
  },

  featureOut: function() {
    this.mapView.setCursor('auto');
    this.featureHovered = null;
  },

  featureClick: function(e, latlon, pxPos, data) {
    if(data.cartodb_id) {
      this.infowindow
      .setLatLng(latlon)
      .setFeatureInfo(data.cartodb_id)
      .showInfowindow();
    } else {
      cdb.log.error("can't show infowindow, no cartodb_id on data");
    }
  },

  _moveShareButton: function(type) {
    var right = 0;
    if (type == "show") {
      right = 592;
    } else if (type == "hide") {
      right = 57;
    } else {
      right = 442;
    }

    this.$el.find("div.share")
    .animate({
      right: right
    }, this.animation_time);
  },

  render: function() {
    this.$el.html('');
    this.$el.append(this.template());
    if(!this.model.isNew()) {
      this.checkGeoRef();
    }
    return this;
  },

  shareMap: function(e) {
    e.preventDefault();

    var privacy = this.options.table.get('privacy').toLowerCase()
      , dlg;

    // If the table is not public, we'll show a warning dialog
    if (privacy != 'public') {
      dlg = new cdb.admin.NoShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    } else {
      dlg = new cdb.admin.ShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    }

    dlg.appendToBody().open();

    return false;
  },

  addGeometry: function() {
    // row is saved by geometry editor if it is needed
    this.geometryEditor.createGeom(this.options.table.data().newRow(), 'polygon');
  },

  showDataLayer: function() {
    var dataLayer = this.map.get('dataLayer');
    this.layerDataView.show();
  },

  hideDataLayer: function() {
    var dataLayer = this.map.get('dataLayer');
    this.layerDataView.hide();
  },

  /**
  * reload tiles
  */
  updateDataLayerView: function() {
    this.layerDataView.reload();
  },
  /**
  * Paints a dialog with a warning when the user hasn't any georeferenced row
  * @method showNoGeorefWarning
  */
  showNoGeoRefWarning: function() {
    if(!this.noGeoRefDialog) {
      this.noGeoRefDialog = new cdb.admin.NoGeoRefDataDialog();
      this.retrigger('georeference', this.noGeoRefDialog);
      this.retrigger('manual', this.noGeoRefDialog);
      this.noGeoRefDialog.render().$el.appendTo(this.$el);
    }
    // if the dialog already has been shown, we don't show it again

  }

});

<|endoftext|>


<commit_before>/*
Class: AnimationTransition

Animates transition between two elements

Parameters:
	duration (integer || array[integers] || array[arrays[integers]]): duration of animation or of each animation step or for each item of each step
	stylesBefore (array[map of style properties and values]): styles to apply to elements before animation starts
	stylesTransition (array[stylemap || array[stylemap]]): styles to animate elements to
	stylesAfter (array[stylemap]): styles to apply to elements after animation

-----instantiation
		//--
		__.animateImage = new __AnimationTransition({
			stylesBefore: [
				{}
				,{left: __.bannerlist.width(), display: 'block'}
			]
			,stylesTransition: [
				{left: -(__.bannerlist.width())}
				,{left: 0}
			]
			,stylesAfter: [
				{}
				,{}
			]
		})
		__.animateLabel = new __AnimationTransition({
			doMultistep: true
			,duration: [this.boot.duration * 1/3, this.boot.duration *2/3]
			,stylesBefore: [
				{}
				,{bottom:  -(elmLabel.outerHeight()), display: 'block'}
			]
			,stylesTransition: [
				[
					{bottom: __.bannerlist.height()}
					,{}
				]
				,[
					{}
					,function(argElement){
						return {bottom: argElement.attr(this.boot.attrDataBottom)}
					}
				]
			]
			,stylesAfter: [
				{display: 'none'}
				,{}
			]
			,boot: {attrDataBottom: 'data-bottom'}
		})
*/
/* global __, jQuery, setTimeout */
define(['jquery', 'tmclasses/tmclasses', 'tmlib/AnimationQueue', 'tmlib/core/isArray'], function(__jQuery, __tmclasses, __AnimationQueue, __isArray){
	var __AnimationTransition = __tmclasses.create({
		init: function(){
			this.__parent(arguments);

			//--derived attributes
			if(typeof this.doMultistep === 'undefined'){
				this.doMultistep = (this.stylesTransition && this.stylesTransition.length && __isArray(this.stylesTransition[0]));
			}
			this.queue = new __AnimationQueue();

			//--do something
			if(this.onInit){
				this.onInit.call(this);
			}
		}
		,properties: {
			animateStep: function(_args, _stepKey){
				var _elms = _args.elements || this.elms;
				var _countElms = _elms.length;
				var _countElements = 0;
				var _iElms;
				var _this = this;
				var _countItemsCompleted = 0;
				var _dqCallback = function(){
					++_countItemsCompleted;
					if(_countItemsCompleted >= _countElements){
						_this.queue.dequeue();
					}
				};
				var _stepOpts;
				var _stepAnimationPieces = [];

				//--build step animation pieces and count all elements instead of just elements in array so that calls to _callbackDQ will happen the proper number of times
				for(_iElms = 0; _iElms < _countElms; ++_iElms){
					var _duration;
					var _styles;
					var _elm = _elms[_iElms];

					if(!this.stylesTransition){
						_styles = null;
					}else if(typeof _stepKey != 'undefined'){
						_styles = this.stylesTransition[_stepKey][_iElms] || null;
						if(__isArray(this.duration)){
							_duration = this.duration[_stepKey];
							if(__isArray(_duration)){
								_duration = this.duration[_stepKey][_iElms];
							}
						}else{
							_duration = this.duration;
						}
					}else{
						_styles = _this.stylesTransition[_iElms] || null;
						_duration = this.duration;
						if(__isArray(_duration)){
							_duration = this.duration[_iElms];
						}
					}
					if(_styles){
						//--count all elements, since each 'elm' could contain multiple elements
						_countElements += _elm.length;
						if(typeof _styles === 'function'){
							_styles = _styles.call(this, _elm, _args);
						}
						_stepAnimationPieces.push({
							duration: _duration
							,elm: _elm
							,styles: _styles
						});

						// _addStepAnimationPiece(_elm, _styles, _duration, _dqCallback);
					}
				}

				//--run animations
				for(var _iPieces = 0; _iPieces < _stepAnimationPieces.length; ++_iPieces){
					_stepOpts = _stepAnimationPieces[_iPieces];
					_stepOpts.elm.animate(_stepOpts.styles, _stepOpts.duration, _dqCallback);
				}
			}
			,doMultistep: undefined
			,duration: 500
			,elms: undefined
			,onAfter: function(_args){
				var _this = this;
				//-# in timeout since post animation values were overwriting onAfter values
				setTimeout(function(){
					var _elms = _args.elements;
					var _styles;
					for(var _iElms in _elms){
						if(_elms.hasOwnProperty(_iElms)){
							_styles = (_this.stylesAfter && _this.stylesAfter[_iElms]) ? _this.stylesAfter[_iElms] : null;
							if(_styles){
								if(typeof _styles === 'function'){
									_styles = _styles.call(this, _elms[_iElms], _args);
								}
								if(_styles && !jQuery.isEmptyObject(_styles)){
									_elms[_iElms].css(_styles);
								}
							}
						}
					}
					_this.pub('after', _args);
					_this.queue.dequeue();
				}, 0);
			}
			,onBefore: function(_args){
				var _elms = _args.elements;
				var _styles;
				for(var _iElms in _elms){
					if(_elms.hasOwnProperty(_iElms)){
						_styles = (this.stylesBefore && this.stylesBefore[_iElms]) ? this.stylesBefore[_iElms] : null;
						if(_styles){
							if(typeof _styles === 'function'){
								_styles = _styles.call(this, _elms[_iElms], _args);
							}
							if(_styles && !jQuery.isEmptyObject(_styles)){
								_elms[_iElms].css(_styles);
							}
						}
					}
				}
				this.pub('before', _args);
				this.queue.dequeue();
			}
			,onInit: undefined
			,stylesBefore: null
			,stylesTransition: null
			,stylesAfter: null
			,transitionForElements: function(_args){
				if(__isArray(_args)){
					_args = {elements: _args};
				}
				var _this = this;
				if(this.onBefore){
					this.queue.queue({callback: function(){
						_this.onBefore.call(_this, _args);
					}});
				}
				if(this.animateStep && this.stylesTransition){
					if(this.doMultistep){
						var _createCallback = function(_this, _keyStep){
							return function(){
								_this.animateStep.call(_this, _args, _keyStep);
							};
						};
						var _keyStep = 0;
						var _stylesLength = this.stylesTransition.length;
						for(; _keyStep < _stylesLength; ++_keyStep){
							this.queue.queue({callback: _createCallback(this, _keyStep)});
						}
					}else{
						this.queue.queue({callback: function(){
							_this.animateStep.call(_this, _args);
						}});
					}
				}
				if(this.onAfter){
					this.queue.queue({callback: function(){
						_this.onAfter.call(_this, _args);
					}});
				}
				this.queue.dequeue();
			}
		}
	});
	return __AnimationTransition;
});
<commit_msg>AnimationTransition: added ability to pass 'after' option to transitionForElements()<commit_after>/*
Class: AnimationTransition

Animates transition between two elements

Parameters:
	duration (integer || array[integers] || array[arrays[integers]]): duration of animation or of each animation step or for each item of each step
	stylesBefore (array[map of style properties and values]): styles to apply to elements before animation starts
	stylesTransition (array[stylemap || array[stylemap]]): styles to animate elements to
	stylesAfter (array[stylemap]): styles to apply to elements after animation

-----instantiation
		//--
		__.animateImage = new __AnimationTransition({
			stylesBefore: [
				{}
				,{left: __.bannerlist.width(), display: 'block'}
			]
			,stylesTransition: [
				{left: -(__.bannerlist.width())}
				,{left: 0}
			]
			,stylesAfter: [
				{}
				,{}
			]
		})
		__.animateLabel = new __AnimationTransition({
			doMultistep: true
			,duration: [this.boot.duration * 1/3, this.boot.duration *2/3]
			,stylesBefore: [
				{}
				,{bottom:  -(elmLabel.outerHeight()), display: 'block'}
			]
			,stylesTransition: [
				[
					{bottom: __.bannerlist.height()}
					,{}
				]
				,[
					{}
					,function(argElement){
						return {bottom: argElement.attr(this.boot.attrDataBottom)}
					}
				]
			]
			,stylesAfter: [
				{display: 'none'}
				,{}
			]
			,boot: {attrDataBottom: 'data-bottom'}
		})
*/
/* global __, jQuery, setTimeout */
define(['jquery', 'tmclasses/tmclasses', 'tmlib/AnimationQueue', 'tmlib/core/isArray'], function(__jQuery, __tmclasses, __AnimationQueue, __isArray){
	var __AnimationTransition = __tmclasses.create({
		init: function(){
			this.__parent(arguments);

			//--derived attributes
			if(typeof this.doMultistep === 'undefined'){
				this.doMultistep = (this.stylesTransition && this.stylesTransition.length && __isArray(this.stylesTransition[0]));
			}
			this.queue = new __AnimationQueue();

			//--do something
			if(this.onInit){
				this.onInit.call(this);
			}
		}
		,properties: {
			animateStep: function(_args, _stepKey){
				var _elms = _args.elements || this.elms;
				var _countElms = _elms.length;
				var _countElements = 0;
				var _iElms;
				var _this = this;
				var _countItemsCompleted = 0;
				var _dqCallback = function(){
					++_countItemsCompleted;
					if(_countItemsCompleted >= _countElements){
						_this.queue.dequeue();
					}
				};
				var _stepOpts;
				var _stepAnimationPieces = [];

				//--build step animation pieces and count all elements instead of just elements in array so that calls to _callbackDQ will happen the proper number of times
				for(_iElms = 0; _iElms < _countElms; ++_iElms){
					var _duration;
					var _styles;
					var _elm = _elms[_iElms];

					if(!this.stylesTransition){
						_styles = null;
					}else if(typeof _stepKey != 'undefined'){
						_styles = this.stylesTransition[_stepKey][_iElms] || null;
						if(__isArray(this.duration)){
							_duration = this.duration[_stepKey];
							if(__isArray(_duration)){
								_duration = this.duration[_stepKey][_iElms];
							}
						}else{
							_duration = this.duration;
						}
					}else{
						_styles = _this.stylesTransition[_iElms] || null;
						_duration = this.duration;
						if(__isArray(_duration)){
							_duration = this.duration[_iElms];
						}
					}
					if(_styles){
						//--count all elements, since each 'elm' could contain multiple elements
						_countElements += _elm.length;
						if(typeof _styles === 'function'){
							_styles = _styles.call(this, _elm, _args);
						}
						_stepAnimationPieces.push({
							duration: _duration
							,elm: _elm
							,styles: _styles
						});

						// _addStepAnimationPiece(_elm, _styles, _duration, _dqCallback);
					}
				}

				//--run animations
				for(var _iPieces = 0; _iPieces < _stepAnimationPieces.length; ++_iPieces){
					_stepOpts = _stepAnimationPieces[_iPieces];
					_stepOpts.elm.animate(_stepOpts.styles, _stepOpts.duration, _dqCallback);
				}
			}
			,doMultistep: undefined
			,duration: 500
			,elms: undefined
			,onAfter: function(_args){
				var _this = this;
				//-# in timeout since post animation values were overwriting onAfter values
				setTimeout(function(){
					var _elms = _args.elements;
					var _styles;
					for(var _iElms in _elms){
						if(_elms.hasOwnProperty(_iElms)){
							_styles = (_this.stylesAfter && _this.stylesAfter[_iElms]) ? _this.stylesAfter[_iElms] : null;
							if(_styles){
								if(typeof _styles === 'function'){
									_styles = _styles.call(this, _elms[_iElms], _args);
								}
								if(_styles && !jQuery.isEmptyObject(_styles)){
									_elms[_iElms].css(_styles);
								}
							}
						}
					}
					_this.pub('after', _args);
					_this.queue.dequeue();
				}, 0);
			}
			,onBefore: function(_args){
				var _elms = _args.elements;
				var _styles;
				for(var _iElms in _elms){
					if(_elms.hasOwnProperty(_iElms)){
						_styles = (this.stylesBefore && this.stylesBefore[_iElms]) ? this.stylesBefore[_iElms] : null;
						if(_styles){
							if(typeof _styles === 'function'){
								_styles = _styles.call(this, _elms[_iElms], _args);
							}
							if(_styles && !jQuery.isEmptyObject(_styles)){
								_elms[_iElms].css(_styles);
							}
						}
					}
				}
				this.pub('before', _args);
				this.queue.dequeue();
			}
			,onInit: undefined
			,stylesBefore: null
			,stylesTransition: null
			,stylesAfter: null
			,transitionForElements: function(_args){
				if(__isArray(_args)){
					_args = {elements: _args};
				}
				var _this = this;
				if(this.onBefore){
					this.queue.queue({callback: function(){
						_this.onBefore.call(_this, _args);
					}});
				}
				if(this.animateStep && this.stylesTransition){
					if(this.doMultistep){
						var _createCallback = function(_this, _keyStep){
							return function(){
								_this.animateStep.call(_this, _args, _keyStep);
							};
						};
						var _keyStep = 0;
						var _stylesLength = this.stylesTransition.length;
						for(; _keyStep < _stylesLength; ++_keyStep){
							this.queue.queue({callback: _createCallback(this, _keyStep)});
						}
					}else{
						this.queue.queue({callback: function(){
							_this.animateStep.call(_this, _args);
						}});
					}
				}
				if(this.onAfter){
					this.queue.queue({callback: function(){
						_this.onAfter.call(_this, _args);
					}});
				}
				if(_args.after){
					this.queue.queue({callback: function(){
						_args.after.call(_this, _args);
					}});
				}
				this.queue.dequeue();
			}
		}
	});
	return __AnimationTransition;
});
<|endoftext|>


<commit_before>wax = wax || {};
wax.mm = wax.mm || {};

wax.mm.interaction = function() {
    var dirty = false,
        _grid,
        map,
        clearingEvents = ['zoomed', 'panned', 'centered',
            'extentset', 'resized', 'drawn'];

    function grid() {
        if (!dirty && _grid !== undefined && _grid.length) {
            return _grid;
        } else {
            var tiles, notempty;
            for (var i = 0; i < map.getLayers().length; i++) {
                tiles = map.getLayerAt(i).tiles;
                for (var p in tiles) {
                    notempty = true;
                    break;
                }
                if (notempty) {
                    var zoomLayer = map.getLayerAt(i)
                        .levels[Math.round(map.getZoom())];
                   break;
               }
            }
            _grid = (function(t) {
                var o = [];
                for (var key in t) {
                    if (t[key].parentNode === zoomLayer) {
                        var offset = wax.u.offset(t[key]);
                        o.push([
                            offset.top,
                            offset.left,
                            t[key]
                        ]);
                    }
                }
                return o;
            })(tiles);
            return _grid;
        }
    }

    function setdirty() { dirty = true; }

    function attach(x) {
        if (!arguments.length) return map;
        map = x;
        for (var i = 0; i < clearingEvents.length; i++) {
            map.addCallback(clearingEvents[i], setdirty);
        }
    }

    function detach(x) {
        for (var i = 0; i < clearingEvents.length; i++) {
            map.removeCallback(clearingEvents[i], setdirty);
        }
    }

    return wax.interaction()
        .attach(attach)
        .detach(detach)
        .parent(function() {
          return map.parent;
        })
        .grid(grid);
};
<commit_msg>Nicer way to check for empty layers, from @tmcw<commit_after>wax = wax || {};
wax.mm = wax.mm || {};

wax.mm.interaction = function() {
    var dirty = false,
        _grid,
        map,
        clearingEvents = ['zoomed', 'panned', 'centered',
            'extentset', 'resized', 'drawn'];

    function grid() {
        if (!dirty && _grid !== undefined && _grid.length) {
            return _grid;
        } else {
            var tiles;
            for (var i = 0; i < map.getLayers().length; i++) {
                var zoomLayer = map.getLayerAt(i).levels[Math.round(map.zoom())];
                if (zoomLayer !== undefined) {
                    tiles = map.getLayerAt(i).tileElementsInLevel(zoomLayer);
                    if (tiles.length) break;
                }
            }
            _grid = (function(t) {
                var o = [];
                for (var key in t) {
                    if (t[key].parentNode === zoomLayer) {
                        var offset = wax.u.offset(t[key]);
                        o.push([
                            offset.top,
                            offset.left,
                            t[key]
                        ]);
                    }
                }
                return o;
            })(tiles);
            return _grid;
        }
    }

    function setdirty() { dirty = true; }

    function attach(x) {
        if (!arguments.length) return map;
        map = x;
        for (var i = 0; i < clearingEvents.length; i++) {
            map.addCallback(clearingEvents[i], setdirty);
        }
    }

    function detach(x) {
        for (var i = 0; i < clearingEvents.length; i++) {
            map.removeCallback(clearingEvents[i], setdirty);
        }
    }

    return wax.interaction()
        .attach(attach)
        .detach(detach)
        .parent(function() {
          return map.parent;
        })
        .grid(grid);
};
<|endoftext|>


<commit_before>angular
  .module('emissionByCountry')
  .controller('GraphController', ['$scope', '$location', 'smoothScrollService', 'CountriesByName', GraphController]);

function GraphController($scope, $location, smoothScrollService, CountriesByName) {
  var self = this;

  // self.tagline = 'graph has loaded';
  self.selectedCountry = 'unselected from controller';
  self.dbData = [];
  self.matchingDataFromDBWorld = [{name: 'World Average', propertyValue: 15}];
  self.matchingDataFromDB = [];
  self.maxValueForEachProperty = [
    {"propertyName":"name","propertyValue": "Qatar"},
    {"propertyName":"perCapitaCO2Emission","propertyValue":39.7362666248},
    {"propertyName":"longTermCO2Emssion","propertyValue":2920405.8287741},
    {"propertyName":"carbonEmission","propertyValue":795750.907022915},
    {"propertyName":"atmosphericSurfaceWarming","propertyValue":4.3806087432},
    {"propertyName":"seaSurfaceWarming","propertyValue":3.0883291639},
    {"propertyName":"seaSurfaceDeoxygenation","propertyValue":-12.81328057},
    {"propertyName":"seaSurfacePH","propertyValue":-0.3942547869},
    {"propertyName":"decreaseMaxCatchPotential","propertyValue":-14894069.73},
    {"propertyName":"speciesTurnover","propertyValue":25.43261495},
    {"propertyName":"decreaseInBodySize","propertyValue":-31.86972656}
  ];

  CountriesByName.get().then(function (res) {
    self.dbData = res;
  });

  self.updateSelectedCountry = function(newCountry){
    self.selectedCountry = newCountry;
    $scope.$apply();
    searchForCountryInDatabase();

    $location.hash('selectedCountryInfo');
    smoothScrollService.scrollTo('selectedCountryInfo');
  };

  self.d3OnClick = function(item){
    console.log('d3OnClick Triggered');
    // TODO-chantelle: replace with bubble functionality
  };

  function searchForCountryInDatabase() {
    for(var i = 0; i < self.dbData.length; i++){
      if(self.selectedCountry === self.dbData[i].name){

        var propertyNames = Object.keys(self.dbData[i]);
        self.matchingDataFromDB = [];

        // a = 1 avoids name of country being stored in the selected country object being
        // passed to the d3Directive
        for(var a = 1; a < propertyNames.length; a++) {
          self.matchingDataFromDB.push({
            propertyName: propertyNames[a],
            actualValue: self.dbData[i][propertyNames[a]],

            // if propertyValue is negative, return the positive scaled version of the value
            propertyValue: (self.dbData[i][propertyNames[a]] * 100 / self.maxValueForEachProperty[a].propertyValue)
          });
        }

        console.log(self.matchingDataFromDB);
      }
    }
    $scope.$apply();
    if (angular.equals(self.matchingDataFromDB, {})){
      console.log('Country Not Found in Database');
    }
  } // searchForCountryInDatabase
}<commit_msg>updated dipslay text<commit_after>angular
  .module('emissionByCountry')
  .controller('GraphController', ['$scope', '$location', 'smoothScrollService', 'CountriesByName', GraphController]);

function GraphController($scope, $location, smoothScrollService, CountriesByName) {
  var self = this;

  // self.tagline = 'graph has loaded';
  self.selectedCountry = 'Select a country on the map!';
  self.dbData = [];
  self.matchingDataFromDBWorld = [{name: 'World Average', propertyValue: 15}];
  self.matchingDataFromDB = [];
  self.maxValueForEachProperty = [
    {"propertyName":"name","propertyValue": "Qatar"},
    {"propertyName":"perCapitaCO2Emission","propertyValue":39.7362666248},
    {"propertyName":"longTermCO2Emssion","propertyValue":2920405.8287741},
    {"propertyName":"carbonEmission","propertyValue":795750.907022915},
    {"propertyName":"atmosphericSurfaceWarming","propertyValue":4.3806087432},
    {"propertyName":"seaSurfaceWarming","propertyValue":3.0883291639},
    {"propertyName":"seaSurfaceDeoxygenation","propertyValue":-12.81328057},
    {"propertyName":"seaSurfacePH","propertyValue":-0.3942547869},
    {"propertyName":"decreaseMaxCatchPotential","propertyValue":-14894069.73},
    {"propertyName":"speciesTurnover","propertyValue":25.43261495},
    {"propertyName":"decreaseInBodySize","propertyValue":-31.86972656}
  ];

  CountriesByName.get().then(function (res) {
    self.dbData = res;
  });

  self.updateSelectedCountry = function(newCountry){
    self.selectedCountry = newCountry;
    $scope.$apply();
    searchForCountryInDatabase();

    $location.hash('selectedCountryInfo');
    smoothScrollService.scrollTo('selectedCountryInfo');
  };

  self.d3OnClick = function(item){
    console.log('d3OnClick Triggered');
    // TODO-chantelle: replace with bubble functionality
  };

  function searchForCountryInDatabase() {
    for(var i = 0; i < self.dbData.length; i++){
      if(self.selectedCountry === self.dbData[i].name){

        var propertyNames = Object.keys(self.dbData[i]);
        self.matchingDataFromDB = [];

        // a = 1 avoids name of country being stored in the selected country object being
        // passed to the d3Directive
        for(var a = 1; a < propertyNames.length; a++) {
          self.matchingDataFromDB.push({
            propertyName: propertyNames[a],
            actualValue: self.dbData[i][propertyNames[a]],

            // if propertyValue is negative, return the positive scaled version of the value
            propertyValue: (self.dbData[i][propertyNames[a]] * 100 / self.maxValueForEachProperty[a].propertyValue)
          });
        }

        console.log(self.matchingDataFromDB);
      }
    }
    $scope.$apply();
    if (angular.equals(self.matchingDataFromDB, {})){
      console.log('Country Not Found in Database');
    }
  } // searchForCountryInDatabase
}<|endoftext|>


<commit_before>// Karma configuration
// Generated on Wed Feb 22 2017 10:11:41 GMT+0100 (Paris, Madrid)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      //'www/lib/ionic/js/angular/angular.js',
      //'www/test/test.spec.js',
      'www/js/calculator.html'
      //'www/js/*.js'
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Firefox'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: true,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}
<commit_msg>Update my.conf.js<commit_after>// Karma configuration
// Generated on Wed Feb 22 2017 10:11:41 GMT+0100 (Paris, Madrid)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      //'www/lib/ionic/js/angular/angular.js',
      //'www/test/test.spec.js',
      'www/js/calculator.html'
      //'www/js/*.js'
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['PhantomJS'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: true,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}
<|endoftext|>


<commit_before>(function($){
	// Parametre par defaut
	var defauts={
		"coherenceClass" : null
	};

	$.fn.coherenceResolutions=function(methodOrOptions){
		var methodArgs=arguments;

		var allReturnMethod=[];
		this.each(function(){
			var coherenceResolutions=$(this).data('coherenceResolutions');
			if (typeof coherenceResolutions === "undefined") {
				// On initialise la classe
				var options=methodOrOptions;

				coherenceResolutions=$.coherenceResolutions($(this),options);
			}else{
				// On applique une method public sur la classe
				var method=methodOrOptions;

				var returnMethod=coherenceResolutions.doPublicMethod(method,methodArgs);
				if(returnMethod!=null){
					allReturnMethod.push(returnMethod);
				}
			}

			$(this).data('coherenceResolutions',coherenceResolutions);
		});
		if(allReturnMethod.length > 0) return allReturnMethod;
		return this;
	};

	$.coherenceResolutions=function(that,methodOrOptions){
		that.coherence=null;

		that.coherenceCheck=[];

		var initialize = function(){
			that.empty();

			if(that.parametres.coherenceClass!=null){
				that.coherence=that.parametres.coherenceClass;
			}

			addListener();

			that.addClass("listIncoherence");

			that._spinner=$("<span>",{"class":"glyphicon glyphicon-refresh glyphicon-refresh-animate"}).hide();
			that._listeResolutions=$("<div>");

			that._button=$("<div>",{"style":"padding : 25px;"}).append(
				$("<button>",{'type':"button", 'class':"btn btn-default"})
					.text("Valider les propositions selectionné")
				    .on("click",function(){
				    	showModalValidation();
				    })
			);

			that._modalValidation=$("<div>").modalResolutions({"coherenceClass" : that.coherence});

			that._modalValidation.on('valider',function(evt,reponses){
				valider(reponses);
			});

			that.append([
			    that._spinner,
			    that._button,
			    that._listeResolutions,
			    that._modalValidation
			]);

			loadListePropositions();

			return that;
		};

		var addListener=function(){
			that.parametres.coherenceClass.socket.removeAllListeners('get-all-incoherence-resolutions');
			that.parametres.coherenceClass.socket.on('get-all-incoherence-resolutions',getAllIncoherence);
		}

		var getAllIncoherence=function(coherenceName,outil,target,allIncoherences,responses){
			// On ne prend en compte l'evenement que si on sur cette coherence
			if(coherenceName==that.parametres.coherenceClass.coherence){
				addListe(allIncoherences);

				work();
			}
		}

		var showModalValidation=function(){
			that.coherenceCheck=[];
			var checkedIncoherences=that._listeResolutions.find('.validationCheckbox:checked');

			if(checkedIncoherences.length > 0){
				checkedIncoherences.each(function(){
					that.coherenceCheck.push($(this).attr('elemid'));
				});

				that._modalValidation.modalResolutions('show');
			}else{
				console.log("ToDo : message d'erreur si aucun check");
			}

		}
		var sleep=function(milliseconds) {
			  var start = new Date().getTime();
			  for (var i = 0; i < 1e7; i++) {
			    if ((new Date().getTime() - start) > milliseconds){
			      break;
			    }
			  }
			}

		var valider=function(reponses){
			wait();

			var nbToValidate=0;
			var nbValidate=0;
			$.each(that.coherenceCheck,function(index,elemid){
				nbToValidate++;
				that.coherence.valider(elemid,reponses,function(){
					nbValidate++;

					if(nbToValidate==nbValidate){
						loadListePropositions();

						that.trigger('refresh');
					}
				});
			});
		}

		var loadListePropositions=function(){
			that._listeResolutions.empty();

			wait();

			// Ajout d'un timeout pour afficher le wait (sinon blqouer par les requetes ajax)
			setTimeout(function(){
				if(that.coherence!=null){
					var allIncoherence=that.coherence.loadAllIncoherence();
				}
			},100);
		}

		var addListe=function(incoherences){
			var listeIncoherence=$("<ul>");

			function SortByLabel(a, b){
				var aName = a.label.toLowerCase();
				var bName = b.label.toLowerCase();
				return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
			}

			incoherences.sort(SortByLabel);

			$.each(incoherences,function(index,incoherence){
				var li=$("<li>",{"class":"list-group-item"});
				li.append($("<input>",{'class' : "validationCheckbox", 'type' : 'checkbox',"elemId" : incoherence.id}));

				li.append($("<span>").text(incoherence.label));

				listeIncoherence.append(li);
			});
			that._listeResolutions.empty();
			that._listeResolutions.append(listeIncoherence);
		}

		var setStatus=function(status){
			switch(status){
				case "wait" : 	that._spinner.show();
								that._button.hide();
								that._listeResolutions.hide();
								break;
				case "work" : 	that._spinner.hide();
								that._button.show();
								that._listeResolutions.show();
				    			break;
				default : 	that._spinner.hide();
							that._button.show();
							that._listeResolutions.show();
    		    			break;
			}
		}

		var wait=function(){
			setStatus("wait");

			return that;
		}

		var work=function(){
			setStatus("work");

			return that;
		}

		var methods={};

		that.doPublicMethod=function(method,args){
			if ( methods[method] ) {
				return methods[ method ].apply( that, Array.prototype.slice.call( args, 1 ));
			} else{
				$.error( 'Method ' +  method + ' does not exist on jQuery.coherenceResolutions' );
			}
		}

		// Prise en compte des options utilisateurs
		$.extend(that.parametres={},defauts,methodOrOptions);
		return initialize();

    };
})(jQuery);<commit_msg>delete unused<commit_after>(function($){
	// Parametre par defaut
	var defauts={
		"coherenceClass" : null
	};

	$.fn.coherenceResolutions=function(methodOrOptions){
		var methodArgs=arguments;

		var allReturnMethod=[];
		this.each(function(){
			var coherenceResolutions=$(this).data('coherenceResolutions');
			if (typeof coherenceResolutions === "undefined") {
				// On initialise la classe
				var options=methodOrOptions;

				coherenceResolutions=$.coherenceResolutions($(this),options);
			}else{
				// On applique une method public sur la classe
				var method=methodOrOptions;

				var returnMethod=coherenceResolutions.doPublicMethod(method,methodArgs);
				if(returnMethod!=null){
					allReturnMethod.push(returnMethod);
				}
			}

			$(this).data('coherenceResolutions',coherenceResolutions);
		});
		if(allReturnMethod.length > 0) return allReturnMethod;
		return this;
	};

	$.coherenceResolutions=function(that,methodOrOptions){
		that.coherence=null;

		that.coherenceCheck=[];

		var initialize = function(){
			that.empty();

			if(that.parametres.coherenceClass!=null){
				that.coherence=that.parametres.coherenceClass;
			}

			addListener();

			that.addClass("listIncoherence");

			that._spinner=$("<span>",{"class":"glyphicon glyphicon-refresh glyphicon-refresh-animate"}).hide();
			that._listeResolutions=$("<div>");

			that._button=$("<div>",{"style":"padding : 25px;"}).append(
				$("<button>",{'type':"button", 'class':"btn btn-default"})
					.text("Valider les propositions selectionné")
				    .on("click",function(){
				    	showModalValidation();
				    })
			);

			that._modalValidation=$("<div>").modalResolutions({"coherenceClass" : that.coherence});

			that._modalValidation.on('valider',function(evt,reponses){
				valider(reponses);
			});

			that.append([
			    that._spinner,
			    that._button,
			    that._listeResolutions,
			    that._modalValidation
			]);

			loadListePropositions();

			return that;
		};

		var addListener=function(){
			that.parametres.coherenceClass.socket.removeAllListeners('get-all-incoherence-resolutions');
			that.parametres.coherenceClass.socket.on('get-all-incoherence-resolutions',getAllIncoherence);
		}

		var getAllIncoherence=function(coherenceName,outil,target,allIncoherences,responses){
			// On ne prend en compte l'evenement que si on sur cette coherence
			if(coherenceName==that.parametres.coherenceClass.coherence){
				addListe(allIncoherences);

				work();
			}
		}

		var showModalValidation=function(){
			that.coherenceCheck=[];
			var checkedIncoherences=that._listeResolutions.find('.validationCheckbox:checked');

			if(checkedIncoherences.length > 0){
				checkedIncoherences.each(function(){
					that.coherenceCheck.push($(this).attr('elemid'));
				});

				that._modalValidation.modalResolutions('show');
			}else{
				console.log("ToDo : message d'erreur si aucun check");
			}
		}

		var valider=function(reponses){
			wait();

			var nbToValidate=0;
			var nbValidate=0;
			$.each(that.coherenceCheck,function(index,elemid){
				nbToValidate++;
				that.coherence.valider(elemid,reponses,function(){
					nbValidate++;

					if(nbToValidate==nbValidate){
						loadListePropositions();

						that.trigger('refresh');
					}
				});
			});
		}

		var loadListePropositions=function(){
			that._listeResolutions.empty();

			wait();

			// Ajout d'un timeout pour afficher le wait (sinon blqouer par les requetes ajax)
			setTimeout(function(){
				if(that.coherence!=null){
					var allIncoherence=that.coherence.loadAllIncoherence();
				}
			},100);
		}

		var addListe=function(incoherences){
			var listeIncoherence=$("<ul>");

			function SortByLabel(a, b){
				var aName = a.label.toLowerCase();
				var bName = b.label.toLowerCase();
				return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
			}

			incoherences.sort(SortByLabel);

			$.each(incoherences,function(index,incoherence){
				var li=$("<li>",{"class":"list-group-item"});
				li.append($("<input>",{'class' : "validationCheckbox", 'type' : 'checkbox',"elemId" : incoherence.id}));

				li.append($("<span>").text(incoherence.label));

				listeIncoherence.append(li);
			});
			that._listeResolutions.empty();
			that._listeResolutions.append(listeIncoherence);
		}

		var setStatus=function(status){
			switch(status){
				case "wait" : 	that._spinner.show();
								that._button.hide();
								that._listeResolutions.hide();
								break;
				case "work" : 	that._spinner.hide();
								that._button.show();
								that._listeResolutions.show();
				    			break;
				default : 	that._spinner.hide();
							that._button.show();
							that._listeResolutions.show();
    		    			break;
			}
		}

		var wait=function(){
			setStatus("wait");

			return that;
		}

		var work=function(){
			setStatus("work");

			return that;
		}

		var methods={};

		that.doPublicMethod=function(method,args){
			if ( methods[method] ) {
				return methods[ method ].apply( that, Array.prototype.slice.call( args, 1 ));
			} else{
				$.error( 'Method ' +  method + ' does not exist on jQuery.coherenceResolutions' );
			}
		}

		// Prise en compte des options utilisateurs
		$.extend(that.parametres={},defauts,methodOrOptions);
		return initialize();

    };
})(jQuery);<|endoftext|>


<commit_before>Seed({
		name : "Mold.Tools.Test.Unit",
		dna : "action",
		include : [
			"Mold.Tools.Test.TDD",
			"Mold.Tools.Test.ConsoleReporter"
		]
	},
	function(){

		var simpleUnit = new Mold.Tools.Test.TDD();
		simpleUnit.addReporter(new Mold.Tools.Test.ConsoleReporter());
		simpleUnit.test(function(){
			describe("My First Test", function(){

				var test = "";
				before("before testing", function(done){
					test = "to was rein"
					done();
				})

				after("TESCHT NACHHER", function(done){
					setTimeout(function(){
						done();
					}, 500)
				})

				describe("My sub Test", function(){
					it("sub sub Test something", function(){
						var hasn;
						expect(hasn).toBeDefined();
					})
				});

				it("Test aync test", function(done){
					console.log("mach es anycron");
					done();
				})

				it("noch ein Test lala", function(){
					console.log("mach was anderes");
				})
			});
		});
		simpleUnit.run();

		Mold.addPostProcessor("unittest", function(createdSeed, rawSeed){
			if(rawSeed.test){
				if(typeof rawSeed.test === "function"){

				}
			}
		});

		//if not nodejs run default tests
		if(!Mold.isNodeJS){
			Mold.load({
				name : "Mold.Lib.*"
			}).bind(function(){
				//console.log("Package loaded!")
			});
		}

	}
);<commit_msg>before each method added<commit_after>Seed({
		name : "Mold.Tools.Test.Unit",
		dna : "action",
		include : [
			"Mold.Tools.Test.TDD",
			"Mold.Tools.Test.ConsoleReporter"
		]
	},
	function(){

		var simpleUnit = new Mold.Tools.Test.TDD();
		simpleUnit.addReporter(new Mold.Tools.Test.ConsoleReporter());
		simpleUnit.test(function(){
			describe("My First Test", function(){
				this.peter = "hans"
				var test = "";

				beforeEach("before each testing", function(done){
					test = "to was rein"
					console.log("before each", this.peter)
					done();
				})

				after("TESCHT NACHHER", function(done){
					setTimeout(function(){
						done();
					}, 500)
				})

				describe("My sub Test", function(){
					this.hans = "peter";

					it("sub sub Test something", function(){
						var hasn;
						expect(hasn).toBeDefined();
					})
				});

				it("Test aync test", function(done){
					console.log("mach es anycron");
					done();
				})

				it("noch ein Test lala", function(){
					console.log("mach was anderes");
				})
			});
		});
		simpleUnit.run();

		Mold.addPostProcessor("unittest", function(createdSeed, rawSeed){
			if(rawSeed.test){
				if(typeof rawSeed.test === "function"){

				}
			}
		});

		//if not nodejs run default tests
		if(!Mold.isNodeJS){
			Mold.load({
				name : "Mold.Lib.*"
			}).bind(function(){
				//console.log("Package loaded!")
			});
		}

	}
);<|endoftext|>


<commit_before>import Modal from 'flarum/components/Modal';
import Group from 'flarum/models/Group';
import Button from 'flarum/components/Button';
import saveConfig from 'flarum/utils/saveConfig';
import SelectDropdown from 'flarum/components/SelectDropdown';
import GroupBadge from 'flarum/components/GroupBadge';

export default class DefaultGroupSettingsModal extends Modal {
  constructor(...args) {
    super(...args);

    const user = app.session.user;

    this.defaultGroup = app.config['hyn.default_group.group'] || Group.MEMBER_ID;
    this.group = app.store.getById('groups', this.defaultGroup);

    this.groups = app.store.all('groups')
        .filter(group => [Group.GUEST_ID].indexOf(group.id()) === -1);
  }

  className() {
    return 'DefaultGroupSettingsModal Modal--small';
  }

  title() {
    return 'Default group Settings';
  }

  content() {
    return (

            Default group

              {this.groups.map(group =>
                this.radio(group)
              )}

    );
  }

  radio(option) {
    let className = 'Checkbox ' + (option.id() == this.defaultGroup ? 'on' : 'off');
    if (this.loading) className += ' loading';
    return (

        <input type="radio"
          value={option.id()}
          checked={option.id() == this.defaultGroup}
          onchange={m.withAttr('value'), this.save.bind(this)}
          />
        {GroupBadge.component({group: option})} {option.namePlural()}

    );
  }

  save(e, groupId) {
    this.loading = true;
    saveConfig({
      'hyn.default_group.group': e.target.value
    }).then(
      () => this.hide()
        //,
      //() => m.redraw()
    );
  }
}
<commit_msg>undefined on app config<commit_after>import Modal from 'flarum/components/Modal';
import Group from 'flarum/models/Group';
import Button from 'flarum/components/Button';
import saveConfig from 'flarum/utils/saveConfig';
import SelectDropdown from 'flarum/components/SelectDropdown';
import GroupBadge from 'flarum/components/GroupBadge';

export default class DefaultGroupSettingsModal extends Modal {
  constructor(...args) {
    super(...args);

    const user = app.session.user;

    this.defaultGroup = this.setting('hyn.default_group.group') || Group.MEMBER_ID;
    this.group = app.store.getById('groups', this.defaultGroup);

    this.groups = app.store.all('groups')
        .filter(group => [Group.GUEST_ID].indexOf(group.id()) === -1);
  }

  className() {
    return 'DefaultGroupSettingsModal Modal--small';
  }

  title() {
    return 'Default group Settings';
  }

  content() {
    return (

            Default group

              {this.groups.map(group =>
                this.radio(group)
              )}

    );
  }

  radio(option) {
    let className = 'Checkbox ' + (option.id() == this.defaultGroup ? 'on' : 'off');
    if (this.loading) className += ' loading';
    return (

        <input type="radio"
          value={option.id()}
          checked={option.id() == this.defaultGroup}
          onchange={m.withAttr('value'), this.save.bind(this)}
          />
        {GroupBadge.component({group: option})} {option.namePlural()}

    );
  }

  save(e, groupId) {
    this.loading = true;
    saveConfig({
      'hyn.default_group.group': e.target.value
    }).then(
      () => this.hide()
        //,
      //() => m.redraw()
    );
  }
}
<|endoftext|>