<commit_before>// TODO: resolve how loading will work
var Framework = Mast;
////////////////////////////////////////////////////////////////////////////

// TODO: disable debug mode
Framework.debug = true;

/**
 * This is the starting point to your Mast application. It runs multiple proccess that allow a
 * Mast application to run. You should grab templates and components with something like
 * Require.js before calling Framework.raise().
 *
 * @param  {Object}   options
 * @param  {Function} cb
 *
 * options example:
 * 	options = {
 * 		data: {
 * 			authenticated: false
 * 		},
 * 		templates: {
 *		 componentName: templateHTML
 *	  },
 *	  components: {
 *		 componentName: ComponentDefinition
 *	  }
 * 	}
 */
Framework.raise = function (options, cb) {

	// If only one arg is present, use options as callback if possible
	// If options are not defined, use an empty object
	if (!cb && _.isFunction(options)) {
		cb = options;
	}
	if (!_.isObject(options)) {
		options = {};
	}

	// If log is specified, use it, otherwise use the console
	if (options.log) {
		Framework.log = options.log;
		Framework.warn = options.log.warn;
		Framework.error = options.log.error;
		Framework.debug = Framework.debug ? options.log.debug : function () {};
	}
	else {
		Framework.log = Framework.isProduction ? function () {} : console.log.bind(console);
		Framework.warn = Framework.isProduction ? function () {} : console.warn.bind(console);
		Framework.error = Framework.isProduction ? function () {} : console.error.bind(console);
		Framework.debug = Framework.debug ? console.debug.bind(console) : function () {};
	}

	// Merge data into Framework.data
	_.extend(Framework.data, options.data || {});

	// Merge specified templates with Framework.templates
	_.extend(Framework.templates, options.templates || {});

	// If Mast.define() was used, build the list of components
	Framework._buildComponentDefinitions();

	// Merge specified components w/ Framework.components
	_.extend(Framework.components, options.components || {});

	// Back up each component definition before transforming it into a live prototype
	Framework.componentDefs = _.clone(Framework.components);

	// Wait until Framework core is ready
	Framework.ready(function () {

		// Collect any <script> tag templates on the page
		// and absorb them into Framework.templates
		_.extend(Framework.templates, collectTemplates());

		// Build actual prototypes for the components
		// (need the templates at this point to make this work)
		buildComponentPrototypes();

		// TODO: v2.5
		// ################################
		// Load any missing templates named in our components asynchronously

		// Determine dependencies by parsing HTML for regions

		// Find a flat list of all the top-level region components
		// (i.e. <region>s which are not inside other <region>s)
		// ################################

		// TODO: v2.3
		// ################################
		// For all regions, absorb implicit templates, identifying discovered regions as subcomponents

		// If no implicit template found, and either:
		// (1) no component with the same name is found, and no template with the same name is found
		// or (2) a component w/ the same name is found, but no template is specified
		// Then throw an error and ignore that region.

		// How will this work?

		// Of the stuff initially in the DOM, and starting with each top-level region,
		// descend to the leaf branches in the current tree, absorbing the implicit contents
		// from each of its child regions, until we reach a leaf.
		// ################################

		// TODO: v2.3
		// ################################
		// Only grab top-level regions, since implicit DOM parsing is enabled
		// ################################

		// Grab initial regions from DOM
		collectRegions();

		// Do the initial routing sequence
		// Look at the #fragment url and fire the global route event
		Framework.history.start();

		// TODO: v2.1
		// ################################
		// Fire the register event on all async endpoint collections/models
		//
		// This will let things like socket adapters make the initial connection
		// ################################

		// ################################
		// Trigger callback
		if (cb) cb();

	});

	// Iterate through component definitions, merge them with templates, and build actual prototypes
	function buildComponentPrototypes() {
		_.each(Framework.components, function (componentDef, componentKey) {

			// If component id is not explicitly set, use the componentKey
			if (!componentDef.id) componentDef.id = componentKey;

			// Validate that the component definition doesn't have any forbidden properties
			Framework.Component.prototype._validateDefinition(componentDef);

			// TODO: Validate that the component isn't named anything naughty
			// (can't think of anything that would cause problems at the moment)

			// Search templates
			var template = Framework.templates[componentDef.id];

			// If no match for component was found in templates, issue a warning
			// (unless this is the global app component, in which case it's fine)
			// if (!template && componentDef.id !== 'App') {
			if (!template) {
				return Framework.warn(componentDef.id + ' :: No template found for component!');
			}

			// Save reference to template in component prototype
			Framework.debug(componentDef.id + ' :: Pairing component with template...');
			componentDef.template = template;

			// Translate right-hand shorthand for top-level keys and events object
			componentDef = objMap(componentDef, translateShorthand);
			if (componentDef.events) {
				componentDef.events = objMap(componentDef.events, translateShorthand);
			}

			// Go ahead and turn the definition into a real component prototype
			var componentPrototype = Framework.Component.extend(componentDef);

			// Discover subscriptions
			componentPrototype.prototype.subscriptions = {};

			// Iterate through each property on this component prototype
			_.each(componentPrototype.prototype, function (handler, key) {
				if (key.match(/^(%|#|~)/)) {
					if (!_.isString(handler)) {
            throw new Error(componentDef.id + ':: Invalid listener for subscription: ' + key +'. Define your callback with an anonymous function instead of a string.');
          }
					if (!_.isFunction(handler)) {
						throw new Error(componentDef.id + ':: Invalid listener for subscription: ' + key);
					}
					componentPrototype.prototype.subscriptions[key] = handler;
				}
			});

			// Save prototype in global set for tracking
			Framework.components[componentDef.id] = componentPrototype;
		});
	}

	// Load any script tags on the page with type="text/template"
	function collectTemplates() {
		var templates = {};

		$('script[type="text/template"]').each(function (i, el) {
			var id = Mast.Util.el2id(el, true);
			templates[id] = $(el).html();

			// Strip whitespace leftover from script tags
			templates[id] = templates[id].replace(/^\s+/,'');
			templates[id] = templates[id].replace(/\s+$/,'');

			// Remove from DOM
			$(el).remove();
		});

		return templates;
	}

	// Collect any regions with the default component set from the DOM
	function collectRegions () {
		var $defaultRegions = $('region[default]');

		// Now instantiate the appropriate default component in each
		$defaultRegions.each(function(i,el) {

			// Pull id from region
			var regionId = Mast.Util.el2id(el);

			// Build region
			var region = new Framework.Region({
				id: regionId,
				$el: $(el),

				parent: { id: '*' }

				// Use app component as parent if one was specified
				// parent: Framework.components.App ? new Framework.components.App() : { id: '*' }
			});

			// Extract default component id
			var componentId = $(el).attr('default');
			region.append(componentId);
		});
	}

	// Translate right-hand-side shorthand from things like #about_me and %mainMenu:open
	// to functions that perform the proper behaviors
	function translateShorthand (value, key) {

		// If this is an important, Framework-specific data key, and a function was specified,
		// run it to get the value
		if (_.isFunction(value) && (key === 'collection' || key === 'model')) {
			return value();
		}

		// Ignore other non-strings
		if (!_.isString(value)) return value;

		var matches;

		// Method to redirect user to a client-side URL
		if ( value.match(/^#/) ) {
			return function changeUrlFragment () {
				window.location.hash = value;
			};
		}
		// Method to trigger global event
		else if ( value.match(/^%/) ) {
			return function triggerEvent () {
				Mast.trigger(value);
			};
		}
		// Method do add the specified class
		else if ( (matches = value.match(/^\+\s\.*(.+)/)) && matches[1] ) {
			return function addClass () {
				this.$el.addClass(matches[1]);
			};
		}
		// Method to remove the specified class
		else if ( (matches = value.match(/^\-\s*\.(.+)/)) && matches[1] ) {
			return function removeClass () {
				this.$el.removeClass(matches[1]);
			};
		}

		return value;
	}

	// Map an object's values, but return a valid object
	function objMap(obj, transformFn) {
		return _.object(_.keys(obj), _.map(obj, transformFn));
	}

};
<commit_msg>fixed negation error on checking for string handler<commit_after>// TODO: resolve how loading will work
var Framework = Mast;
////////////////////////////////////////////////////////////////////////////

// TODO: disable debug mode
Framework.debug = true;

/**
 * This is the starting point to your Mast application. It runs multiple proccess that allow a
 * Mast application to run. You should grab templates and components with something like
 * Require.js before calling Framework.raise().
 *
 * @param  {Object}   options
 * @param  {Function} cb
 *
 * options example:
 * 	options = {
 * 		data: {
 * 			authenticated: false
 * 		},
 * 		templates: {
 *		 componentName: templateHTML
 *	  },
 *	  components: {
 *		 componentName: ComponentDefinition
 *	  }
 * 	}
 */
Framework.raise = function (options, cb) {

	// If only one arg is present, use options as callback if possible
	// If options are not defined, use an empty object
	if (!cb && _.isFunction(options)) {
		cb = options;
	}
	if (!_.isObject(options)) {
		options = {};
	}

	// If log is specified, use it, otherwise use the console
	if (options.log) {
		Framework.log = options.log;
		Framework.warn = options.log.warn;
		Framework.error = options.log.error;
		Framework.debug = Framework.debug ? options.log.debug : function () {};
	}
	else {
		Framework.log = Framework.isProduction ? function () {} : console.log.bind(console);
		Framework.warn = Framework.isProduction ? function () {} : console.warn.bind(console);
		Framework.error = Framework.isProduction ? function () {} : console.error.bind(console);
		Framework.debug = Framework.debug ? console.debug.bind(console) : function () {};
	}

	// Merge data into Framework.data
	_.extend(Framework.data, options.data || {});

	// Merge specified templates with Framework.templates
	_.extend(Framework.templates, options.templates || {});

	// If Mast.define() was used, build the list of components
	Framework._buildComponentDefinitions();

	// Merge specified components w/ Framework.components
	_.extend(Framework.components, options.components || {});

	// Back up each component definition before transforming it into a live prototype
	Framework.componentDefs = _.clone(Framework.components);

	// Wait until Framework core is ready
	Framework.ready(function () {

		// Collect any <script> tag templates on the page
		// and absorb them into Framework.templates
		_.extend(Framework.templates, collectTemplates());

		// Build actual prototypes for the components
		// (need the templates at this point to make this work)
		buildComponentPrototypes();

		// TODO: v2.5
		// ################################
		// Load any missing templates named in our components asynchronously

		// Determine dependencies by parsing HTML for regions

		// Find a flat list of all the top-level region components
		// (i.e. <region>s which are not inside other <region>s)
		// ################################

		// TODO: v2.3
		// ################################
		// For all regions, absorb implicit templates, identifying discovered regions as subcomponents

		// If no implicit template found, and either:
		// (1) no component with the same name is found, and no template with the same name is found
		// or (2) a component w/ the same name is found, but no template is specified
		// Then throw an error and ignore that region.

		// How will this work?

		// Of the stuff initially in the DOM, and starting with each top-level region,
		// descend to the leaf branches in the current tree, absorbing the implicit contents
		// from each of its child regions, until we reach a leaf.
		// ################################

		// TODO: v2.3
		// ################################
		// Only grab top-level regions, since implicit DOM parsing is enabled
		// ################################

		// Grab initial regions from DOM
		collectRegions();

		// Do the initial routing sequence
		// Look at the #fragment url and fire the global route event
		Framework.history.start();

		// TODO: v2.1
		// ################################
		// Fire the register event on all async endpoint collections/models
		//
		// This will let things like socket adapters make the initial connection
		// ################################

		// ################################
		// Trigger callback
		if (cb) cb();

	});

	// Iterate through component definitions, merge them with templates, and build actual prototypes
	function buildComponentPrototypes() {
		_.each(Framework.components, function (componentDef, componentKey) {

			// If component id is not explicitly set, use the componentKey
			if (!componentDef.id) componentDef.id = componentKey;

			// Validate that the component definition doesn't have any forbidden properties
			Framework.Component.prototype._validateDefinition(componentDef);

			// TODO: Validate that the component isn't named anything naughty
			// (can't think of anything that would cause problems at the moment)

			// Search templates
			var template = Framework.templates[componentDef.id];

			// If no match for component was found in templates, issue a warning
			// (unless this is the global app component, in which case it's fine)
			// if (!template && componentDef.id !== 'App') {
			if (!template) {
				return Framework.warn(componentDef.id + ' :: No template found for component!');
			}

			// Save reference to template in component prototype
			Framework.debug(componentDef.id + ' :: Pairing component with template...');
			componentDef.template = template;

			// Translate right-hand shorthand for top-level keys and events object
			componentDef = objMap(componentDef, translateShorthand);
			if (componentDef.events) {
				componentDef.events = objMap(componentDef.events, translateShorthand);
			}

			// Go ahead and turn the definition into a real component prototype
			var componentPrototype = Framework.Component.extend(componentDef);

			// Discover subscriptions
			componentPrototype.prototype.subscriptions = {};

			// Iterate through each property on this component prototype
			_.each(componentPrototype.prototype, function (handler, key) {
				if (key.match(/^(%|#|~)/)) {
					if (_.isString(handler)) {
            throw new Error(componentDef.id + ':: Invalid listener for subscription: ' + key +'. Define your callback with an anonymous function instead of a string.');
          }
					if (!_.isFunction(handler)) {
						throw new Error(componentDef.id + ':: Invalid listener for subscription: ' + key);
					}
					componentPrototype.prototype.subscriptions[key] = handler;
				}
			});

			// Save prototype in global set for tracking
			Framework.components[componentDef.id] = componentPrototype;
		});
	}

	// Load any script tags on the page with type="text/template"
	function collectTemplates() {
		var templates = {};

		$('script[type="text/template"]').each(function (i, el) {
			var id = Mast.Util.el2id(el, true);
			templates[id] = $(el).html();

			// Strip whitespace leftover from script tags
			templates[id] = templates[id].replace(/^\s+/,'');
			templates[id] = templates[id].replace(/\s+$/,'');

			// Remove from DOM
			$(el).remove();
		});

		return templates;
	}

	// Collect any regions with the default component set from the DOM
	function collectRegions () {
		var $defaultRegions = $('region[default]');

		// Now instantiate the appropriate default component in each
		$defaultRegions.each(function(i,el) {

			// Pull id from region
			var regionId = Mast.Util.el2id(el);

			// Build region
			var region = new Framework.Region({
				id: regionId,
				$el: $(el),

				parent: { id: '*' }

				// Use app component as parent if one was specified
				// parent: Framework.components.App ? new Framework.components.App() : { id: '*' }
			});

			// Extract default component id
			var componentId = $(el).attr('default');
			region.append(componentId);
		});
	}

	// Translate right-hand-side shorthand from things like #about_me and %mainMenu:open
	// to functions that perform the proper behaviors
	function translateShorthand (value, key) {

		// If this is an important, Framework-specific data key, and a function was specified,
		// run it to get the value
		if (_.isFunction(value) && (key === 'collection' || key === 'model')) {
			return value();
		}

		// Ignore other non-strings
		if (!_.isString(value)) return value;

		var matches;

		// Method to redirect user to a client-side URL
		if ( value.match(/^#/) ) {
			return function changeUrlFragment () {
				window.location.hash = value;
			};
		}
		// Method to trigger global event
		else if ( value.match(/^%/) ) {
			return function triggerEvent () {
				Mast.trigger(value);
			};
		}
		// Method do add the specified class
		else if ( (matches = value.match(/^\+\s\.*(.+)/)) && matches[1] ) {
			return function addClass () {
				this.$el.addClass(matches[1]);
			};
		}
		// Method to remove the specified class
		else if ( (matches = value.match(/^\-\s*\.(.+)/)) && matches[1] ) {
			return function removeClass () {
				this.$el.removeClass(matches[1]);
			};
		}

		return value;
	}

	// Map an object's values, but return a valid object
	function objMap(obj, transformFn) {
		return _.object(_.keys(obj), _.map(obj, transformFn));
	}

};
<|endoftext|>


<commit_before>"use strict"
let pad = number => number <= 99 ? ("0"+number).slice(-2) : number;

var $loginStatus = $(".login-status");
var $lastRequestLabel = $(".last-request");
var $fullScanLabel = $(".full-scan");

var $selectExclude = $("#exclude-pokemon");
var excludedPokemon = [];

var map;
var coverCircle;
var searchLocation = {};
var newLocationMarker;
var currentLocationMarker;

try {
    excludedPokemon = JSON.parse(localStorage.excludedPokemon);
    console.log(excludedPokemon);
} catch (e) {}

function getFromStorage(keyName, default_value) {
    var res = localStorage.getItem(keyName);
    if(res){
        return res === "true";
    } else {
        return default_value;
    }
}

document.getElementById('pokemon-checkbox').checked = getFromStorage("displayPokemons", "true");
document.getElementById('gyms-checkbox').checked = getFromStorage("displayGyms", "true");
document.getElementById('coverage-checkbox').checked = getFromStorage("displayCoverage", "true");

$.getJSON("static/locales/pokemon.en.json").done(function(data) {
    var pokeList = [];

    $.each(data, function(key, value) {
        pokeList.push( { id: key, text: value } );
    });

    $selectExclude.select2({
        placeholder: "Type to exclude Pokemon",
        data: pokeList
    });
    $selectExclude.val(excludedPokemon).trigger("change");
});

// exclude multi-select listener
$selectExclude.on("change", function (e) {
    excludedPokemon = $selectExclude.val().map(Number);
    localStorage.excludedPokemon = JSON.stringify(excludedPokemon);
    clearStaleMarkers();
});

// change-location button listener
$('#map').on('click', '#new-loc-btn', function () {
     newLocationMarker.setMap(null);

    $.post("set-location",
           {'lat': newLocationMarker.getPosition().lat(),
            'lng':  newLocationMarker.getPosition().lng()},
        function( data ) {
            updateMap();
        }, "json");
});

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: center_lat, lng: center_lng},
        zoom: 13,
        mapTypeControl: false,
        streetViewControl: false,
        disableAutoPan: true
    });

    updateMap();

    currentLocationMarker = new google.maps.Marker({
        position: {lat: center_lat, lng: center_lng},
        map: map,
        animation: google.maps.Animation.DROP
    });

    // on click listener for
    google.maps.event.addListener(map, 'click', function(event) {
        if (newLocationMarker) {
            newLocationMarker.setMap(null);
       }
        newLocationMarker = new google.maps.Marker({
            position: event.latLng,
            map: map
        });
        newLocationMarker.infoWindow = new google.maps.InfoWindow({
            content: "<button id=\"new-loc-btn\">Set new Location</button>",
            disableAutoPan: true
        });
        newLocationMarker.infoWindow.open(map, newLocationMarker);
        google.maps.event.addListener(newLocationMarker.infoWindow,'closeclick',function(){
            newLocationMarker.setMap(null); //removes the marker
        });
    });
};

function pokemonLabel(name, id, disappear_time, latitude, longitude) {
    var disappear_date = new Date(disappear_time);

    var label = `

            ${name}
             -

                #${id}

            Disappears at ${pad(disappear_date.getHours())}:${pad(disappear_date.getMinutes())}:${pad(disappear_date.getSeconds())}
            (00m00s)

            Get Directions

`;
    return label;
};

function gymLabel(team_name, team_id, gym_points) {
    var gym_color = [ "0, 0, 0, .4", "74, 138, 202, .6", "240, 68, 58, .6", "254, 217, 40, .6" ];
    var str;
    if (team_name == 0) {
        str = `

                ${team_name}

`;
    } else {
        str = `

Gym owned by:

                Team ${team_name}

Prestige: ${gym_points}

`;
    }

    return str;
}

var map_pokemons = {}; // dict containing all pokemons on the map.
var map_gyms = {};
var gym_types = [ "Uncontested", "Mystic", "Valor", "Instinct" ];

function setupPokemonMarker(item) {
    var myIcon = new google.maps.MarkerImage('static/icons/'+item.pokemon_id+'.png', null, null, null, new google.maps.Size(30,30));

    var marker = new google.maps.Marker({
        position: {lat: item.latitude, lng: item.longitude},
        map: map,
        icon: myIcon
    });

    marker.infoWindow = new google.maps.InfoWindow({
        content: pokemonLabel(item.pokemon_name, item.pokemon_id, item.disappear_time, item.latitude, item.longitude),
        disableAutoPan: true
    });

    addListeners(marker);
    return marker;
}

function setupGymMarker(item) {
    var marker = new google.maps.Marker({
        position: {lat: item.latitude, lng: item.longitude},
        map: map,
        icon: 'static/forts/'+gym_types[item.team_id]+'.png'
    });

    marker.infoWindow = new google.maps.InfoWindow({
        content: gymLabel(gym_types[item.team_id], item.team_id, item.gym_points),
        disableAutoPan: true
    });

    addListeners(marker);
    return marker;
}

function addListeners(marker){
    marker.addListener('click', function() {
        marker.infoWindow.open(map, marker);
        updateLabelDiffTime();
        marker.persist = true;
    });

    google.maps.event.addListener(marker.infoWindow,'closeclick',function(){
        marker.persist = null;
    });

    var isMobile = (/iphone|ipod|android|ie|blackberry|fennec/).test(navigator.userAgent.toLowerCase());

    if (!isMobile){
        marker.addListener('mouseover', function() {
            marker.infoWindow.open(map, marker);
            updateLabelDiffTime();
        });

        marker.addListener('mouseout', function() {
            if (!marker.persist) {
                marker.infoWindow.close();
            }
        });
    }
    return marker
}

function clearStaleMarkers(){
    $.each(map_pokemons, function(key, value) {

        if (map_pokemons[key]['disappear_time'] < new Date().getTime() ||
                excludedPokemon.indexOf(map_pokemons[key]['pokemon_id']) >= 0) {
            map_pokemons[key].marker.setMap(null);
            console.log("removing marker with key "+key);
            delete map_pokemons[key];
        }
    });
}

function updateMap() {
    $.ajax({
        url: "map-data",
        type: 'GET',
        data: {'pokemon': localStorage.displayPokemons,
               'pokestops': document.getElementById('pokestops-checkbox').checked,
               'pokestops-lured': document.getElementById('pokestops-lured-checkbox').checked,
               'gyms': localStorage.displayGyms},
        dataType: "json"
    }).done(function(result){
        statusLabels(result["server_status"]);

         if (JSON.stringify(result['search_area']) !== JSON.stringify(searchLocation)) {
            searchLocation = result['search_area'];

            if (currentLocationMarker) currentLocationMarker.setMap(null);
            currentLocationMarker = new google.maps.Marker({
                position: searchLocation,
                map: map,
                animation: google.maps.Animation.DROP
            });

            if (coverCircle) {
                coverCircle.setCenter(searchLocation);
                coverCircle.setRadius(searchLocation['radius']);
            } else {
                coverCircle = new google.maps.Circle({
                    strokeColor: '#FF0000',
                    strokeOpacity: 0.6,
                    strokeWeight: 1,
                    fillColor: '#FF0000',
                    fillOpacity: 0.08,
                    clickable: false,
                    map: map,
                    center: searchLocation,
                    radius: searchLocation['radius']
                });
                coverCircle.setVisible(document.getElementById('coverage-checkbox').checked);
            }
        }

       $.each(result.pokemons, function(i, item){
            if (!document.getElementById('pokemon-checkbox').checked) {
                return false; // in case the checkbox was unchecked in the meantime.
            }

            if (!(item.encounter_id in map_pokemons) &&
                    excludedPokemon.indexOf(item.pokemon_id) < 0) {
                // add marker to map and item to dict
                if (item.marker) item.marker.setMap(null);
                item.marker = setupPokemonMarker(item);
                map_pokemons[item.encounter_id] = item;
            }
        });

        $.each(result.gyms, function(i, item){
            if (!document.getElementById('gyms-checkbox').checked) {
                return false; // in case the checkbox was unchecked in the meantime.
            }

            if (item.gym_id in map_gyms) {
                // if team has changed, create new marker (new icon)
                if (map_gyms[item.gym_id].team_id != item.team_id) {
                    map_gyms[item.gym_id].marker.setMap(null);
                    map_gyms[item.gym_id].marker = setupGymMarker(item);
                } else { // if it hasn't changed generate new label only (in case prestige has changed)
                    map_gyms[item.gym_id].marker.infoWindow = new google.maps.InfoWindow({
                        content: gymLabel(gym_types[item.team_id], item.team_id, item.gym_points),
                        disableAutoPan: true
                    });
                }
            }
            else { // add marker to map and item to dict
                if (item.marker) item.marker.setMap(null);
                item.marker = setupGymMarker(item);
                map_gyms[item.gym_id] = item;
            }
        });

        clearStaleMarkers();
    });
}

window.setInterval(updateMap, 10000);

$('#gyms-checkbox').change(function() {
    localStorage.displayGyms = this.checked;
    if(this.checked) {
        updateMap();
    } else {
        $.each(map_gyms, function(key, value) {
            map_gyms[key].marker.setMap(null);
        });
        map_gyms = {}
    }
});

$('#pokemon-checkbox').change(function() {
    localStorage.displayPokemons = this.checked;
    if(this.checked) {
        updateMap();
    } else {
        $.each(map_pokemons, function(key, value) {
            map_pokemons[key].marker.setMap(null);
        });
        map_pokemons = {}
    }
});

$('#coverage-checkbox').change(function() {
    localStorage.displayCoverage = this.checked;
    coverCircle.setVisible(this.checked);
});

var coverCircles = [];
/*
function displayCoverage() {
    if (currentLocationMarker) currentLocationMarker.setMap(null);

    $.each(coverCircles, function(i, circle) {
        circle.setMap(null);
    });

    $.getJSON("cover", {format: "json"}).done(function(data) {
        currentLocationMarker = new google.maps.Marker({
            position: {lat: data['center']['lat'], lng: data['center']['lng']},
            map: map,
            animation: google.maps.Animation.DROP
        });

        $.each(data['cover'], function(i, point) {
            coverCircles.push(new google.maps.Circle({
                strokeColor: '#FF0000',
                strokeOpacity: 0.6,
                strokeWeight: 1,
                fillColor: '#FF0000',
                fillOpacity: 0.08,
                clickable: false,
                map: map,
                center: point,
                radius: 100
            }));
        });
    });
}
*/

function statusLabels(status) {
    if (status['login_time'] == 0) {
        $loginStatus.html('Login failed');
        $loginStatus.removeClass('label-success label-warning');
        $loginStatus.addClass('label-danger');
    } else {
        $loginStatus.html('Logged in');
        $loginStatus.removeClass('label-danger label-warning');
        $loginStatus.addClass('label-success');
    }

    var difference = status['last-successful-request'];

    if (difference == 'none') {

    } else if (difference == 'sleep') {
        $lastRequestLabel.removeClass('label-danger label-warning');
        $lastRequestLabel.addClass('label-success');
        $lastRequestLabel.html("Sleeping");
    } else {
        var timestring = formatTimeDiff(difference);
        if (difference <= 2) {
            $lastRequestLabel.removeClass('label-danger label-warning');
            $lastRequestLabel.addClass('label-success');
        } if (difference > 2 && difference <= 30) {
            $lastRequestLabel.removeClass('label-success label-danger');
            $lastRequestLabel.addClass('label-warning');
        } if (difference > 30) {
            $lastRequestLabel.removeClass('label-success label-warning');
            $lastRequestLabel.addClass('label-danger');
        }
        $lastRequestLabel.html("Last scan: "+timestring+ " ago");
    }

    var timeSinceScan = status['complete-scan-time'];
    if (timeSinceScan)
        $fullScanLabel.html("Last full scan in "+ formatTimeDiff(timeSinceScan))

}

function formatTimeDiff(difference) {
    var hours = Math.floor(difference / 3600);
    var minutes = Math.floor(difference % 3600 / 60);
    var seconds = Math.floor(difference % 3600 % 60);
    var milli = Math.floor((difference % 3600 % 60 - seconds)*100);

    var timestring = "";
    if(hours > 0) timestring += hours + "h";
    if(minutes > 0) timestring += pad(minutes) + "m";
    if (hours == 0) {
        timestring += pad(seconds);

        if (hours > 0 || minutes > 0) {
            timestring += "s"
        } else {
             timestring += "." + pad(milli) + "s"
        }
    }
    return timestring;
}

var updateLabelDiffTime = function() {
    $('.label-countdown').each(function (index, element) {
        var disappearsAt = new Date(parseInt(element.getAttribute("disappears-at")));
        var now = new Date();

        var difference = Math.abs(disappearsAt - now);
        var hours = Math.floor(difference / 36e5);
        var minutes = Math.floor((difference - (hours * 36e5)) / 6e4);
        var seconds = Math.floor((difference - (hours * 36e5) - (minutes * 6e4)) / 1e3);

        var timestring = "";
        if(disappearsAt < now){
            timestring = "(expired)";
        }
        else {
            timestring = "(";
            if(hours > 0)
                timestring = hours + "h";

            timestring += ("0" + minutes).slice(-2) + "m";
            timestring += ("0" + seconds).slice(-2) + "s";
            timestring += ")";
        }

        $(element).text(timestring)
    });
};

window.setInterval(updateLabelDiffTime, 1000);
<commit_msg>converted ES6 js<commit_after>"use strict"

var $loginStatus = $(".login-status");
var $lastRequestLabel = $(".last-request");
var $fullScanLabel = $(".full-scan");

var $selectExclude = $("#exclude-pokemon");
var excludedPokemon = [];

var map;
var coverCircle;
var searchLocation = {};
var newLocationMarker;
var currentLocationMarker;

try {
    excludedPokemon = JSON.parse(localStorage.excludedPokemon);
    console.log(excludedPokemon);
} catch (e) {}

function getFromStorage(keyName, default_value) {
    var res = localStorage.getItem(keyName);
    if(res){
        return res === "true";
    } else {
        return default_value;
    }
}

function pad(num, size) {
    var s = num+"";
    if (s.length < 2) s = "0" + s;
    return s;
}

document.getElementById('pokemon-checkbox').checked = getFromStorage("displayPokemons", "true");
document.getElementById('gyms-checkbox').checked = getFromStorage("displayGyms", "true");
document.getElementById('coverage-checkbox').checked = getFromStorage("displayCoverage", "true");

$.getJSON("static/locales/pokemon.en.json").done(function(data) {
    var pokeList = [];

    $.each(data, function(key, value) {
        pokeList.push( { id: key, text: value } );
    });

    $selectExclude.select2({
        placeholder: "Type to exclude Pokemon",
        data: pokeList
    });
    $selectExclude.val(excludedPokemon).trigger("change");
});

// exclude multi-select listener
$selectExclude.on("change", function (e) {
    excludedPokemon = $selectExclude.val().map(Number);
    localStorage.excludedPokemon = JSON.stringify(excludedPokemon);
    clearStaleMarkers();
});

// change-location button listener
$('#map').on('click', '#new-loc-btn', function () {
     newLocationMarker.setMap(null);

    $.post("set-location",
           {'lat': newLocationMarker.getPosition().lat(),
            'lng':  newLocationMarker.getPosition().lng()},
        function( data ) {
            updateMap();
        }, "json");
});

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: center_lat, lng: center_lng},
        zoom: 13,
        mapTypeControl: false,
        streetViewControl: false,
        disableAutoPan: true
    });

    updateMap();

    currentLocationMarker = new google.maps.Marker({
        position: {lat: center_lat, lng: center_lng},
        map: map,
        animation: google.maps.Animation.DROP
    });

    // on click listener for
    google.maps.event.addListener(map, 'click', function(event) {
        if (newLocationMarker) {
            newLocationMarker.setMap(null);
       }
        newLocationMarker = new google.maps.Marker({
            position: event.latLng,
            map: map
        });
        newLocationMarker.infoWindow = new google.maps.InfoWindow({
            content: "<button id=\"new-loc-btn\">Set new Location</button>",
            disableAutoPan: true
        });
        newLocationMarker.infoWindow.open(map, newLocationMarker);
        google.maps.event.addListener(newLocationMarker.infoWindow,'closeclick',function(){
            newLocationMarker.setMap(null); //removes the marker
        });
    });
};

function pokemonLabel(name, id, disappear_time, latitude, longitude) {
    var disappear_date = new Date(disappear_time);

    var label = "
\
            " +name+ "\
             - \
            \
                #"+id+"\
            \

\

\
            Disappears at " +pad(disappear_date.getHours())+ ":"+pad(disappear_date.getMinutes())+":"+pad(disappear_date.getSeconds())+"\
            (00m00s)
\

\
            Get Directions\

";
    return label;
};

function gymLabel(team_name, team_id, gym_points) {
    var gym_color = [ "0, 0, 0, .4", "74, 138, 202, .6", "240, 68, 58, .6", "254, 217, 40, .6" ];
    var str;
    if (team_name == 0) {
        str = "
\

\
                " + team_name + "
\

\

";
    } else {
        str = "
\

Gym owned by:
\

\
                Team " + team_name + "
\
                 \

\

Prestige: " + gym_points + "
\

";
    }

    return str;
}

var map_pokemons = {}; // dict containing all pokemons on the map.
var map_gyms = {};
var gym_types = [ "Uncontested", "Mystic", "Valor", "Instinct" ];

function setupPokemonMarker(item) {
    var myIcon = new google.maps.MarkerImage('static/icons/'+item.pokemon_id+'.png', null, null, null, new google.maps.Size(30,30));

    var marker = new google.maps.Marker({
        position: {lat: item.latitude, lng: item.longitude},
        map: map,
        icon: myIcon
    });

    marker.infoWindow = new google.maps.InfoWindow({
        content: pokemonLabel(item.pokemon_name, item.pokemon_id, item.disappear_time, item.latitude, item.longitude),
        disableAutoPan: true
    });

    addListeners(marker);
    return marker;
}

function setupGymMarker(item) {
    var marker = new google.maps.Marker({
        position: {lat: item.latitude, lng: item.longitude},
        map: map,
        icon: 'static/forts/'+gym_types[item.team_id]+'.png'
    });

    marker.infoWindow = new google.maps.InfoWindow({
        content: gymLabel(gym_types[item.team_id], item.team_id, item.gym_points),
        disableAutoPan: true
    });

    addListeners(marker);
    return marker;
}

function addListeners(marker){
    marker.addListener('click', function() {
        marker.infoWindow.open(map, marker);
        updateLabelDiffTime();
        marker.persist = true;
    });

    google.maps.event.addListener(marker.infoWindow,'closeclick',function(){
        marker.persist = null;
    });

    var isMobile = (/iphone|ipod|android|ie|blackberry|fennec/).test(navigator.userAgent.toLowerCase());

    if (!isMobile){
        marker.addListener('mouseover', function() {
            marker.infoWindow.open(map, marker);
            updateLabelDiffTime();
        });

        marker.addListener('mouseout', function() {
            if (!marker.persist) {
                marker.infoWindow.close();
            }
        });
    }
    return marker
}

function clearStaleMarkers(){
    $.each(map_pokemons, function(key, value) {

        if (map_pokemons[key]['disappear_time'] < new Date().getTime() ||
                excludedPokemon.indexOf(map_pokemons[key]['pokemon_id']) >= 0) {
            map_pokemons[key].marker.setMap(null);
            console.log("removing marker with key "+key);
            delete map_pokemons[key];
        }
    });
}

function updateMap() {
    $.ajax({
        url: "map-data",
        type: 'GET',
        data: {'pokemon': localStorage.displayPokemons,
               'pokestops': document.getElementById('pokestops-checkbox').checked,
               'pokestops-lured': document.getElementById('pokestops-lured-checkbox').checked,
               'gyms': localStorage.displayGyms},
        dataType: "json"
    }).done(function(result){
        statusLabels(result["server_status"]);

         if (JSON.stringify(result['search_area']) !== JSON.stringify(searchLocation)) {
            searchLocation = result['search_area'];

            if (currentLocationMarker) currentLocationMarker.setMap(null);
            currentLocationMarker = new google.maps.Marker({
                position: searchLocation,
                map: map,
                animation: google.maps.Animation.DROP
            });

            if (coverCircle) {
                coverCircle.setCenter(searchLocation);
                coverCircle.setRadius(searchLocation['radius']);
            } else {
                coverCircle = new google.maps.Circle({
                    strokeColor: '#FF0000',
                    strokeOpacity: 0.6,
                    strokeWeight: 1,
                    fillColor: '#FF0000',
                    fillOpacity: 0.08,
                    clickable: false,
                    map: map,
                    center: searchLocation,
                    radius: searchLocation['radius']
                });
                coverCircle.setVisible(document.getElementById('coverage-checkbox').checked);
            }
        }

       $.each(result.pokemons, function(i, item){
            if (!document.getElementById('pokemon-checkbox').checked) {
                return false; // in case the checkbox was unchecked in the meantime.
            }

            if (!(item.encounter_id in map_pokemons) &&
                    excludedPokemon.indexOf(item.pokemon_id) < 0) {
                // add marker to map and item to dict
                if (item.marker) item.marker.setMap(null);
                item.marker = setupPokemonMarker(item);
                map_pokemons[item.encounter_id] = item;
            }
        });

        $.each(result.gyms, function(i, item){
            if (!document.getElementById('gyms-checkbox').checked) {
                return false; // in case the checkbox was unchecked in the meantime.
            }

            if (item.gym_id in map_gyms) {
                // if team has changed, create new marker (new icon)
                if (map_gyms[item.gym_id].team_id != item.team_id) {
                    map_gyms[item.gym_id].marker.setMap(null);
                    map_gyms[item.gym_id].marker = setupGymMarker(item);
                } else { // if it hasn't changed generate new label only (in case prestige has changed)
                    map_gyms[item.gym_id].marker.infoWindow = new google.maps.InfoWindow({
                        content: gymLabel(gym_types[item.team_id], item.team_id, item.gym_points),
                        disableAutoPan: true
                    });
                }
            }
            else { // add marker to map and item to dict
                if (item.marker) item.marker.setMap(null);
                item.marker = setupGymMarker(item);
                map_gyms[item.gym_id] = item;
            }
        });

        clearStaleMarkers();
    });
}

window.setInterval(updateMap, 10000);

$('#gyms-checkbox').change(function() {
    localStorage.displayGyms = this.checked;
    if(this.checked) {
        updateMap();
    } else {
        $.each(map_gyms, function(key, value) {
            map_gyms[key].marker.setMap(null);
        });
        map_gyms = {}
    }
});

$('#pokemon-checkbox').change(function() {
    localStorage.displayPokemons = this.checked;
    if(this.checked) {
        updateMap();
    } else {
        $.each(map_pokemons, function(key, value) {
            map_pokemons[key].marker.setMap(null);
        });
        map_pokemons = {}
    }
});

$('#coverage-checkbox').change(function() {
    localStorage.displayCoverage = this.checked;
    coverCircle.setVisible(this.checked);
});

var coverCircles = [];
/*
function displayCoverage() {
    if (currentLocationMarker) currentLocationMarker.setMap(null);

    $.each(coverCircles, function(i, circle) {
        circle.setMap(null);
    });

    $.getJSON("cover", {format: "json"}).done(function(data) {
        currentLocationMarker = new google.maps.Marker({
            position: {lat: data['center']['lat'], lng: data['center']['lng']},
            map: map,
            animation: google.maps.Animation.DROP
        });

        $.each(data['cover'], function(i, point) {
            coverCircles.push(new google.maps.Circle({
                strokeColor: '#FF0000',
                strokeOpacity: 0.6,
                strokeWeight: 1,
                fillColor: '#FF0000',
                fillOpacity: 0.08,
                clickable: false,
                map: map,
                center: point,
                radius: 100
            }));
        });
    });
}
*/

function statusLabels(status) {
    if (status['login_time'] == 0) {
        $loginStatus.html('Login failed');
        $loginStatus.removeClass('label-success label-warning');
        $loginStatus.addClass('label-danger');
    } else {
        $loginStatus.html('Logged in');
        $loginStatus.removeClass('label-danger label-warning');
        $loginStatus.addClass('label-success');
    }

    var difference = status['last-successful-request'];

    if (difference == 'na') {

    } else if (difference == 'sleep') {
        $lastRequestLabel.removeClass('label-danger label-warning');
        $lastRequestLabel.addClass('label-success');
        $lastRequestLabel.html("Sleeping");
    } else {
        var timestring = formatTimeDiff(difference);
        if (difference <= 2) {
            $lastRequestLabel.removeClass('label-danger label-warning');
            $lastRequestLabel.addClass('label-success');
        } if (difference > 2 && difference <= 30) {
            $lastRequestLabel.removeClass('label-success label-danger');
            $lastRequestLabel.addClass('label-warning');
        } if (difference > 30) {
            $lastRequestLabel.removeClass('label-success label-warning');
            $lastRequestLabel.addClass('label-danger');
        }
        $lastRequestLabel.html("Last scan: "+timestring+ " ago");
    }

    var timeSinceScan = status['complete-scan-time'];
    if (timeSinceScan)
        $fullScanLabel.html("Last full scan in "+ formatTimeDiff(timeSinceScan))

}

function formatTimeDiff(difference) {
    var hours = Math.floor(difference / 3600);
    var minutes = Math.floor(difference % 3600 / 60);
    var seconds = Math.floor(difference % 3600 % 60);
    var milli = Math.floor((difference % 3600 % 60 - seconds)*100);

    var timestring = "";
    if(hours > 0) timestring += hours + "h";
    if(minutes > 0) timestring += pad(minutes) + "m";
    if (hours == 0) {
        timestring += pad(seconds);

        if (hours > 0 || minutes > 0) {
            timestring += "s"
        } else {
             timestring += "." + pad(milli) + "s"
        }
    }
    return timestring;
}

var updateLabelDiffTime = function() {
    $('.label-countdown').each(function (index, element) {
        var disappearsAt = new Date(parseInt(element.getAttribute("disappears-at")));
        var now = new Date();

        var difference = Math.abs(disappearsAt - now);
        var hours = Math.floor(difference / 36e5);
        var minutes = Math.floor((difference - (hours * 36e5)) / 6e4);
        var seconds = Math.floor((difference - (hours * 36e5) - (minutes * 6e4)) / 1e3);

        var timestring = "";
        if(disappearsAt < now){
            timestring = "(expired)";
        }
        else {
            timestring = "(";
            if(hours > 0)
                timestring = hours + "h";

            timestring += ("0" + minutes).slice(-2) + "m";
            timestring += ("0" + seconds).slice(-2) + "s";
            timestring += ")";
        }

        $(element).text(timestring)
    });
};

window.setInterval(updateLabelDiffTime, 1000);
<|endoftext|>


<commit_before>const Resource = require('../api-resource').Resource;
const logging = require('logging').getLogger(__filename);

class FacilityUserResource extends Resource {
  static resourceName() {
    return 'facilityuser';
  }

  getCurrentFacility() {
    const promise = new Promise((resolve, reject) => {
      this.client({ path: this.currentFacilityUrl() }).then((response) => {
        resolve(response.entity);
      }, (response) => {
        logging.error('An error occurred', response);
      });
    },
    (reject) => {
      reject(reject);
    });
    return promise;
  }

  login(payload) {
    const promise = new Promise((resolve, reject) => {
      this.client({ path: this.loginUrl(), entity: payload, method: 'POST',
      headers: { 'Content-Type': 'application/json' } }).then((response) => {
        console.log('login called in facilityResource.js');
        resolve(response.entity);
      }, (response) => {
        logging.error('An error occurred', response);
      });
    },
    (reject) => {
      reject(reject);
    });
    return promise;
  }

  logout() {
    const promise = new Promise((resolve, reject) => {
      this.client({ path: this.logoutUrl(), method: 'POST' }).then((response) => {
        resolve(response.entity);
      }, (response) => {
        logging.error('An error occurred', response);
      });
    },
    (reject) => {
      reject(reject);
    });
    return promise;
  }

  get currentFacilityUrl() {
    return this.urls[`currentfacility_list`];
  }

  get loginUrl() {
    return this.urls[`login`];
  }

  get logoutUrl() {
    return this.urls[`logout`];
  }

}

module.exports = FacilityUserResource;
<commit_msg>removes old login/logout code from facilityUser.js<commit_after>const Resource = require('../api-resource').Resource;
const logging = require('logging').getLogger(__filename);

class FacilityUserResource extends Resource {
  static resourceName() {
    return 'facilityuser';
  }

  getCurrentFacility() {
    const promise = new Promise((resolve, reject) => {
      this.client({ path: this.currentFacilityUrl() }).then((response) => {
        resolve(response.entity);
      }, (response) => {
        logging.error('An error occurred', response);
      });
    },
    (reject) => {
      reject(reject);
    });
    return promise;
  }

  get currentFacilityUrl() {
    return this.urls[`currentfacility_list`];
  }

}

module.exports = FacilityUserResource;
<|endoftext|>


<commit_before>function initMap() {
	//Die Karte wird in der index-html in das Div mit der id "mymap" gezeichnet
	map = new L.Map("map");

	var min = 11; //minimale Zoomstufe
	var max = 16; //maximale Zoomstufe

	myTiles = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; //hier wird angegeben von wo die Tiles für die Karte geladen werden sollen
	osmCopyright = "Map data © 2012 OpenStreetMap contributors"; //Copyrigth, das unten rechts erscheint
	myLayer = new L.TileLayer(myTiles, { minZoom:min, maxZoom: max, attribution: osmCopyright, zIndex:0, reuseTiles:true } ); //Nun wird mit diesen tiles eine ebene erstellt (hier gibt es nur eine Ebene, es sind aber auch mehrere Ebenen möglich)
	map.addLayer( myLayer ); //Füge die Ebene der Karte hinzu

	var center = new L.LatLng(52.484766470498954, 13.363323211669922); //Fokus der Karte
	map.setView(center, min);

	var fernsehturm = L.icon({
        iconUrl: '/static/img/Fernsehturm_grau_26px3.png',
        iconSize:     [27, 125], // size of the icon
        iconAnchor:   [8,111] // point of the icon which will correspond to marker's location
	});

	var fernsehturmMarker = L.marker([52.520841,13.409405],{icon: fernsehturm}).addTo(map);

	var funkturm = L.icon({
        iconUrl: '/static/img/Funkturm_36px_neu.png',
        iconSize:     [36, 110], // size of the icon
        iconAnchor:   [16,108] // point of the icon which will correspond to marker's location
	});
	var funkturmMarker = L.marker([52.5050681,13.278211400000032],{icon: funkturm}).addTo(map);

	var greenIcon = L.icon({
        iconUrl: '/static/img/marker_green.png',
        iconSize:     [21, 32], // size of the icon width,height
        iconAnchor:   [14, 45], // point of the icon which will correspond to marker's location
        popupAnchor:  [4, -20] // point from which the popup should open relative to the iconAnchor
	});

    var redIcon = L.icon({
        iconUrl: '/static/img/marker_red.png',
        iconSize:     [21, 32], // size of the icon width,height
        iconAnchor:   [14, 45], // point of the icon which will correspond to marker's location
        popupAnchor:  [4, -20] // point from which the popup should open relative to the iconAnchor
	});

    $.each(bbps, function(key,bbp){
        var lon = bbp.fields.lon;
        var lat = bbp.fields.lat;
        var t = bbp.fields.vorhaben;
        var link = bbp.fields.link;
        var marker = L.marker([lat,lon],{icon: redIcon}).addTo(map);

        popuptext = "' + t + "";
        marker.bindPopup(popuptext);

    });

    var html = '
';
    $.each(projects, function(key,project){
        var lon = project.fields.lon;
        var lat = project.fields.lat;
        var t = project.fields.titel;
        html += '
' + t + '
';
        var marker = L.marker([lat,lon],{icon: greenIcon}).addTo(map);
        marker.bindPopup(t);
    });
    html += '
';
    $('#sidebar-content').append(html);

}

function moveOutSidebar(){
    $('#sidebar-button').unbind('click');
    $('#sidebar').removeClass('sidebar-shadow');
    $('#sidebar').animate(
        {left: '-=300'},
        'fast',
        function(){});
    $('#sidebar-button').animate(
        {left: '-=300'},
        'fast',
        function(){
            $('#sidebar-button').click(moveInSidebar);
            $('i', '#sidebar-button').remove();
            $('#sidebar-button').append('');
	    $('#sidebar').removeClass('sidebar-shadow');
        }
    );
}

function moveInSidebar(){
    $('#sidebar-button').unbind('click');
    $('#sidebar').addClass('sidebar-shadow');
    $('#sidebar').animate(
        {left: '+=300'},
        'fast',
        function(){});
    $('#sidebar-button').animate(
        {left: '+=300'},
        'fast',
        function(){
            $('#sidebar-button').click(moveOutSidebar);
            $('i', '#sidebar-button').remove();
            $('#sidebar-button').append('');
        }
    );
}

$(document).ready(function() {
    setTimeout('initMap()',100);
    $('#sidebar-button').click(moveInSidebar);
});<commit_msg>mit OSM Bright<commit_after>function initMap() {
	//Die Karte wird in der index-html in das Div mit der id "mymap" gezeichnet
	map = new L.Map("map");

	var min = 11; //minimale Zoomstufe
	var max = 16; //maximale Zoomstufe

	myTiles = "http://tiles.jochenklar.de/bright/{z}/{x}/{y}.png";
	//myTiles = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; //hier wird angegeben von wo die Tiles für die Karte geladen werden sollen
	osmCopyright = "Map data © 2012 OpenStreetMap contributors"; //Copyrigth, das unten rechts erscheint
	myLayer = new L.TileLayer(myTiles, { minZoom:min, maxZoom: max, attribution: osmCopyright, zIndex:0, reuseTiles:true } ); //Nun wird mit diesen tiles eine ebene erstellt (hier gibt es nur eine Ebene, es sind aber auch mehrere Ebenen möglich)
	map.addLayer( myLayer ); //Füge die Ebene der Karte hinzu

	var center = new L.LatLng(52.484766470498954, 13.363323211669922); //Fokus der Karte
	map.setView(center, min);

	var fernsehturm = L.icon({
        iconUrl: '/static/img/Fernsehturm_grau_26px3.png',
        iconSize:     [27, 125], // size of the icon
        iconAnchor:   [8,111] // point of the icon which will correspond to marker's location
	});

	var fernsehturmMarker = L.marker([52.520841,13.409405],{icon: fernsehturm}).addTo(map);

	var funkturm = L.icon({
        iconUrl: '/static/img/Funkturm_36px_neu.png',
        iconSize:     [36, 110], // size of the icon
        iconAnchor:   [16,108] // point of the icon which will correspond to marker's location
	});
	var funkturmMarker = L.marker([52.5050681,13.278211400000032],{icon: funkturm}).addTo(map);

	var greenIcon = L.icon({
        iconUrl: '/static/img/marker_green.png',
        iconSize:     [21, 32], // size of the icon width,height
        iconAnchor:   [14, 45], // point of the icon which will correspond to marker's location
        popupAnchor:  [4, -20] // point from which the popup should open relative to the iconAnchor
	});

    var redIcon = L.icon({
        iconUrl: '/static/img/marker_red.png',
        iconSize:     [21, 32], // size of the icon width,height
        iconAnchor:   [14, 45], // point of the icon which will correspond to marker's location
        popupAnchor:  [4, -20] // point from which the popup should open relative to the iconAnchor
	});

	var html = '
';
    $.each(bbps, function(key,bbp){
        var lon = bbp.fields.lon;
        var lat = bbp.fields.lat;
        var t = bbp.fields.vorhaben;
        var link = bbp.fields.link;
        html += '
' + t + '
';
        var marker = L.marker([lat,lon],{icon: redIcon}).addTo(map);

        popuptext = "' + t + "";
        marker.bindPopup(popuptext);

    });

    $.each(projects, function(key,project){
        var lon = project.fields.lon;
        var lat = project.fields.lat;
        var t = project.fields.titel;
        html += '
' + t + '
';
        var marker = L.marker([lat,lon],{icon: greenIcon}).addTo(map);
        marker.bindPopup(t);
    });
    html += '
';
    $('#sidebar-content').append(html);

}

function moveOutSidebar(){
    $('#sidebar-button').unbind('click');
    $('#sidebar').removeClass('sidebar-shadow');
    $('#sidebar').animate(
        {left: '-=300'},
        'fast',
        function(){});
    $('#sidebar-button').animate(
        {left: '-=300'},
        'fast',
        function(){
            $('#sidebar-button').click(moveInSidebar);
            $('i', '#sidebar-button').remove();
            $('#sidebar-button').append('');
	    $('#sidebar').removeClass('sidebar-shadow');
        }
    );
}

function moveInSidebar(){
    $('#sidebar-button').unbind('click');
    $('#sidebar').addClass('sidebar-shadow');
    $('#sidebar').animate(
        {left: '+=300'},
        'fast',
        function(){});
    $('#sidebar-button').animate(
        {left: '+=300'},
        'fast',
        function(){
            $('#sidebar-button').click(moveOutSidebar);
            $('i', '#sidebar-button').remove();
            $('#sidebar-button').append('');
        }
    );
}

$(document).ready(function() {
    setTimeout('initMap()',100);
    $('#sidebar-button').click(moveInSidebar);
});<|endoftext|>


<commit_before>
window.templates || (window.templates = {});

templates.repl_main = function()
{
  return [
    "div", [[
      ["div", [
         "ol", "", "class", "repl-lines js-source"
         ], "class", "repl-output"],
      ["div", [[
        ["span", ">>> ", "class", "repl-prefix"],
        ["div", ["textarea", "", "handler", "repl-textarea", "rows", "1"]]
      ]], "class", "repl-input"]
    ]], "class", "padding"
  ];
};

templates.repl_output_native = function(s)
{
  return ["span", s, "class", "repl-native"];
};

templates.repl_output_native_or_pobj = function(thing)
{
  if (thing.type == "native") {
    return templates.repl_output_native(thing.value);
  }
  else
  {
    return templates.repl_output_pobj(thing);
  };
};

templates.repl_output_pobj = function(data)
{
  return [
    'code',
    data.name,
    'handler', 'inspect-object-link',
    'rt-id', data.rt_id.toString(),
    'obj-id', data.obj_id.toString(),
    'class', 'repl-pobj'
  ];
};

templates.repl_output_traceentry = function(frame, index)
{
    var tpl = ['li',
      ui_strings.S_TEXT_CALL_STACK_FRAME_LINE.
        replace("%(FUNCTION_NAME)s", ( frame.objectValue ? frame.objectValue.functionName : ui_strings.ANONYMOUS_FUNCTION_NAME ) ).
        replace("%(LINE_NUMBER)s", ( frame.lineNumber || '-' ) ).
        replace("%(SCRIPT_ID)s", ( frame.scriptID || '-' ) ),
      'ref-id', index.toString(),
      'script-id', String(frame.scriptID), //.toString(),
      'line-number', String(frame.lineNumber),
      'scope-variable-object-id', String(frame.variableObject),
      'this-object-id', String(frame.thisObject),
      'arguments-object-id', String(frame.argumentObject)
    ];
  return tpl;
};

templates.repl_output_trace = function(trace)
{
  var fun = function(f) {
    return templates.repl_output_traceentry(f, trace.frameList.indexOf(f));
  };
  var lis = trace.frameList.map(fun);
  var tpl = ["div", ["ol", lis, "class", "console-trace",
                     'handler', 'select-trace-frame',
                     'runtime-id', trace.runtimeID.toString()
                    ],
                    "class", "console-trace-container"];
  return tpl;
};
<commit_msg>Removed unneeded inner function for a map operation<commit_after>
window.templates || (window.templates = {});

templates.repl_main = function()
{
  return [
    "div", [[
      ["div", [
         "ol", "", "class", "repl-lines js-source"
         ], "class", "repl-output"],
      ["div", [[
        ["span", ">>> ", "class", "repl-prefix"],
        ["div", ["textarea", "", "handler", "repl-textarea", "rows", "1"]]
      ]], "class", "repl-input"]
    ]], "class", "padding"
  ];
};

templates.repl_output_native = function(s)
{
  return ["span", s, "class", "repl-native"];
};

templates.repl_output_native_or_pobj = function(thing)
{
  if (thing.type == "native") {
    return templates.repl_output_native(thing.value);
  }
  else
  {
    return templates.repl_output_pobj(thing);
  };
};

templates.repl_output_pobj = function(data)
{
  return [
    'code',
    data.name,
    'handler', 'inspect-object-link',
    'rt-id', data.rt_id.toString(),
    'obj-id', data.obj_id.toString(),
    'class', 'repl-pobj'
  ];
};

templates.repl_output_traceentry = function(frame, index)
{
    var tpl = ['li',
      ui_strings.S_TEXT_CALL_STACK_FRAME_LINE.
        replace("%(FUNCTION_NAME)s", ( frame.objectValue ? frame.objectValue.functionName : ui_strings.ANONYMOUS_FUNCTION_NAME ) ).
        replace("%(LINE_NUMBER)s", ( frame.lineNumber || '-' ) ).
        replace("%(SCRIPT_ID)s", ( frame.scriptID || '-' ) ),
      'ref-id', index.toString(),
      'script-id', String(frame.scriptID), //.toString(),
      'line-number', String(frame.lineNumber),
      'scope-variable-object-id', String(frame.variableObject),
      'this-object-id', String(frame.thisObject),
      'arguments-object-id', String(frame.argumentObject)
    ];
  return tpl;
};

templates.repl_output_trace = function(trace)
{
  var lis = trace.frameList.map(templates.repl_output_traceentry);
  var tpl = ["div", ["ol", lis, "class", "console-trace",
                     'handler', 'select-trace-frame',
                     'runtime-id', trace.runtimeID.toString()
                    ],
                    "class", "console-trace-container"];
  return tpl;
};
<|endoftext|>


<commit_before>//--------------------------------HELPER OBJECTS--------------------------------

//An object that maps keys to values.  A map cannot contain duplicate keys; each
//key can map to at most one value. Keys and values are compared by identity,
//not equality. This is useful in cases where specific instances of objects are
//to be stored or associated. Almost anything may be used as a key or value,
//including primitives, null, undefined, arrays, objects, or functions. The only
//exception is NaN, which may never be used as a key.
IdentityMap = function() {
    let keys = [];
    let values = [];

    //Returns the number of key-value mappings in this identity map.
    this.size = function() {
        return keys.length;
    };

    //Returns true if this map contains no key-value mappings.
    this.isEmpty = function() {
        return keys.length === 0;
    };

    //Tests whether the specified object reference is a key in this identity
    //map.
    this.containsKey = function(key) {
        return keys.indexOf(key) !== -1;
    };

    //Tests whether the specified object reference is a value in this identity
    //map.
    this.containsValue = function(value) {
        return values.indexOf(value) !== -1;
    };

    //Returns the value to which the specified key is mapped, or null if this
    //map contains no mapping for the key. A return value of null does not
    //necessarily indicate that the map contains no mapping for the key; it's
    //also possible that the map explicitly maps the key to null.
    this.get = function(key) {
        let index = keys.indexOf(key);

        if (index === -1) {
            return null;
        }

        return values[index];
    };

    //Associates the specified value with the specified key in this identity
    //map. If the map previously contained a mapping for the key, the old value
    //is replaced and returned. Otherwise, null is returned. A null return can
    //also indicate that the map previously associated the key with null. Note
    //that NaN may not be used as a key, as it has no identity by definition.
    this.put = function(key, value) {
        if (key !== key) {
            return null; //Key is NaN.
        }

        let index = keys.indexOf(key);

        if (index !== -1) {
            let oldValue = values[index];
            values[index] = value;
            return oldValue;
        }

        keys.push(key);
        values.push(value);
        return null;
    };

    //Removes and returns the mapping for this key from this map if present.
    //Returns null if no such mapping existed. A null return can also indicate
    //that the map previously associated the key with null.
    this.remove = function(key) {
        let index = keys.indexOf(key);

        if (index === -1) {
            return null;
        }

        let oldValue = values[index];
        keys.splice(index, 1);
        values.splice(index, 1);
        return oldValue;
    };

    //Removes all of the mappings from this map.
    this.clear = function() {
        keys = [];
        values = [];
    };

    //Performs the given action for each key-value pair in this identity map
    //until all entries have been processed.
    this.forEachEntry = function(action) {
        let size = keys.length;
        for (let i = 0; i < size; i++) {
            action(keys[i], values[i]);
        }
    };
}

//A collection that contains no identical elements.
IdentitySet = function() {
    //This is exposed so we can iterate over it easily. Do not modify.
    this.elements = [];

    //Returns the number of elements in this identity set.
    this.size = function() {
        return this.elements.length;
    };

    //Returns true if this set contains no elements.
    this.isEmpty = function() {
        return this.elements.length === 0;
    };

    //Tests whether the specified object is an element in this identity set.
    this.contains = function(obj) {
        return this.elements.indexOf(obj) !== -1;
    };

    //Adds the specified element to this set, and returns true if it is not
    //already present.
    this.add = function(element) {
        if (element !== element) {
            return false; //Element is NaN.
        }

        if (this.elements.indexOf(element) !== -1) {
            return false;
        }

        this.elements.push(element);
        return true;
    };

    //Removes the specified element from this set and returns true, if it is
    //an element in this set.
    this.remove = function(element) {
        let index = keys.indexOf(element);
        if (index === -1) {
            return false;
        }

        this.elements.splice(index, 1);
        return true;
    };

    //Removes all of the mappings from this map.
    this.clear = function() {
        this.elements = [];
    };

    //Performs the given action for each element in this identity set until all
    //entries have been processed.
    this.forEach = function(action) {
        for (let element of this.elements) {
            action(element);
        }
    };
}

//Directed acyclic graph class, for input into LayeredGraph.
DAG = function() {
    //Does a strict check to ensure no cycles are created in this graph.
    //Will reduce performance, especially on larger graphs.
    let ENSURE_NO_CYCLES = true;

    let self = this;
    this.vertices = new IdentityMap();

    let Vertex = function(name) {
        this.name = name;
        this.vertsOut = new IdentitySet();
        this.vertsIn = new IdentitySet();
        let self = this;

        this.isSink = function() {
            return self.vertsOut.isEmpty();
        }

        this.isSource = function() {
            return self.vertsIn.isEmpty();
        }
    }

    //Returns whether a vertex exists in this graph with the given name.
    this.containsVertex = function(name) {
        return self.vertices.containsKey(name);
    };

    //Adds the given vertex to this graph, returning true if the vertex did not
    //already exist or false otherwise.
    this.addVertex = function(name) {
        if (self.containsVertex(name)) {
            return false;
        }

        let vertex = new Vertex(name);
        self.vertices.put(name, vertex);
        return true;
    };

    //Removes the given vertex from this graph and returns true, or returns
    //false if no such vertex exists. Also removes any links which reference the
    //given vertex.
    this.removeVertex = function(name) {
        let vertex = self.vertices.remove(name);
        if (vertex === null) {
            return false;
        }

        for (let vEnd of vertex.vertsOut.elements) {
            vEnd.vertsIn.remove(vertex);
        }
        for (let vStart of vertex.vertsIn.elements) {
            vStart.vertsOut.remove(vertex);
        }
        return true;
    };

    let wouldCreateCycle = function(vStart, vEnd) {
        let frontier = new IdentitySet();
        frontier.add(vEnd);

        //Start at end vertex and traverse graph, checking if we can reach the
        //start from the end.
        while (!frontier.isEmpty()) {
            let newFrontier = new IdentitySet();

            for (let current of frontier.elements) {
                for (let vOut of current.vertsOut.elements) {
                    if (vOut === vStart) {
                        return true;
                    }
                    newFrontier.add(vOut);
                }
            }

            frontier = newFrontier;
        }

        return false;
    };

    //Returns whether an edge exists in this graph between the given vertices.
    this.containsEdge = function(start, end) {
        if (!self.containsVertex(start) || !self.containsVertex(end)) {
            return false;
        }

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);
        return vStart.vertsOut.contains(vEnd);
    };

    //Adds the given edge to this graph, returning true if the edge did not
    //already exist or false otherwise. Throws an exception if the given nodes
    //do not both exist. Throws an exception upon trying to add a edge that
    //would make this graph cyclic.
    this.addEdge = function(start, end) {
        if (!self.containsVertex(start)) throw new Error("No such vertex '" + start + "'");
        if (!self.containsVertex(end)) throw new Error("No such vertex '" + end + "'");

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);

        if (vStart.vertsOut.contains(vEnd)) {
            return false;
        }

        if (ENSURE_NO_CYCLES && wouldCreateCycle(vStart, vEnd)) {
            throw new Error("An edge from '" + start + "' to '" + end + "' would create a cycle");
        }

        vStart.vertsOut.add(vEnd);
        vEnd.vertsIn.add(vStart);
        return true;
    };

    //Removes the given edge from this graph and returns true, or returns false
    //if no such edge exists.
    this.removeEdge = function(start, end) {
        if (!self.containsVertex(start) || !self.containsVertex(end)) {
            return false;
        }

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);

        if (!vStart.vertsOut.contains(vEnd)) {
            return false;
        }

        vStart.vertsOut.remove(vEnd);
        vEnd.vertsIn.remove(vStart);
        return true;
    };

    //Performs the given action on the vertex names of each edge in this graph.
    this.forEachEdge = function(action) {
        self.vertices.forEachEntry(function(start, vStart) {
            for (let vEnd of vStart.vertsOut.elements) {
                action(start, vEnd.name);
            }
        });
    };
};

//-----------------------------ACTUAL LAYOUT CODE-------------------------------

LayeredGraph = function(dag) {
    let Node = function(vertex) {
        this.vertex = vertex;
        this.layer = 0;
        this.column = 0;

        this.edgesOut = [];
        this.edgesIn = [];

        this.edgeCount = 0;
        this.edgeColumnTotal = 0;
        this.edgeAvgColumn = 0.0;
    };

    let nodes = new IdentityMap();
    dag.vertices.forEachEntry(function(name, vertex) {
        nodes.put(name, new Node(vertex));
    });

    //Assign layers based on topological depth.
    {
        let frontier = new IdentitySet();

        //Start with sources.
        nodes.forEachEntry(function(name, node) {
            if (node.vertex.isSource()) {
                frontier.add(node);
            }
        });

        //Traverse graph.
        while (!frontier.isEmpty()) {
            let newFrontier = new IdentitySet();

            for (let nCurrent of frontier.elements) {
                for (let vNext of nCurrent.vertex.vertsOut.elements) {
                    let nNext = nodes.get(vNext.name);
                    nNext.layer = Math.max(nCurrent.layer + 1, nNext.layer);
                    newFrontier.add(nNext);
                }
            }

            frontier = newFrontier;
        }
    }

    let maxLayer = 0;
    nodes.forEachEntry(function(name, node) {
        maxLayer = Math.max(maxLayer, node.layer);
    });

    let table = [];
    for (let i = 0; i <= maxLayer; i++) {
        table.push([]);
    }

    let insertNode = function(node) {
        let layer = table[node.layer];
        node.column = layer.length;
        layer.push(node);
    }

    //Assign arbitrary columns.
    nodes.forEachEntry(function(name, node) {
        insertNode(node);
    });

    //Generate edges and dummy nodes.
    let Edge = function(source, target) {
        this.source = source;
        this.target = target;

        source.edgesOut.push(this);
        target.edgesIn.push(this);
    };

    let dummyNodes = [];

    nodes.forEachEntry(function(start, nStart) {
        nStart.vertex.vertsOut.forEach(function(vEnd) {
            let nDummyStart = nStart;
            let nEnd = nodes.get(vEnd.name);

            while (nEnd.layer > nDummyStart.layer + 1)
            {
                let dummy = new Node();
                dummy.layer = nDummyStart.layer + 1;
                insertNode(dummy);
                dummyNodes.push(dummy);
                new Edge(nDummyStart, dummy);
                nDummyStart = dummy;
            }

            new Edge(nDummyStart, nEnd);
        });
    });

    //Sorts layerToSift by average connection position.
    //The idea is to put nodes close to connected nodes, to reduce edge crossings.
    let siftLayer = function(layerToSift, referenceLayer, isRefAbove) {
        for (let node of layerToSift) {
            node.edgeCount = 0;
            node.edgeColumnTotal = 0;
        }

        if (isRefAbove) {
            for (let nAbove of referenceLayer) {
                for (let edge of nAbove.edgesIn) {
                    edge.source.edgeCount++;
                    edge.source.edgeColumnTotal += nAbove.column;
                }
            }
        }
        else {
            for (let nBelow of referenceLayer) {
                for (let edge of nBelow.edgesOut) {
                    edge.target.edgeCount++;
                    edge.target.edgeColumnTotal += nBelow.column;
                }
            }
        }

        for (let node of layerToSift) {
            node.edgeAvgColumn = node.edgeColumnTotal/node.edgeCount;
        }

        layerToSift.sort(function(a, b) {
            return a.edgeAvgColumn - b.edgeAvgColumn;
        });

        for (let i = 0; i < layerToSift.length; i++) {
            layerToSift[i].column = i;
        }
    }

    for (let iteration = 0; iteration < 2; iteration++) {
        //Sift from bottom to top.
        for (let i = 1; i <= maxLayer; i++)
            siftLayer(table[i], table[i - 1], false);

        //Then sift again from top to bottom.
        for (let i = maxLayer - 1; i >= 0; i--)
            siftLayer(table[i], table[i + 1], true);
    }

    /*//Debug print
    for (let i = 0; i <= maxLayer; i++) {
        let layer = table[i];
        for (let node of layer) {
            console.log(node);
        }
    }
    */

    //Expose table and dimensions.
    this.table = table;
    this.height = maxLayer;
    this.width = 0;
    for (let i = 0; i < table.length; i++) {
        this.width = Math.max(this.width, table[i].length - 1);
    }

    //Populate node array.
    this.nodes = [];
    let self = this;
    nodes.forEachEntry(function(name, node) {
        self.nodes.push(node);
    });

    for (let node of dummyNodes) {
        this.nodes.push(node);
    }

    //Populate edge array.
    this.edges = [];
    for (let node of this.nodes) {
        for (let edge of node.edgesOut) {
            this.edges.push(edge);
        }
    }
};
<commit_msg>Reversed graph layer assignment<commit_after>//--------------------------------HELPER OBJECTS--------------------------------

//An object that maps keys to values.  A map cannot contain duplicate keys; each
//key can map to at most one value. Keys and values are compared by identity,
//not equality. This is useful in cases where specific instances of objects are
//to be stored or associated. Almost anything may be used as a key or value,
//including primitives, null, undefined, arrays, objects, or functions. The only
//exception is NaN, which may never be used as a key.
IdentityMap = function() {
    let keys = [];
    let values = [];

    //Returns the number of key-value mappings in this identity map.
    this.size = function() {
        return keys.length;
    };

    //Returns true if this map contains no key-value mappings.
    this.isEmpty = function() {
        return keys.length === 0;
    };

    //Tests whether the specified object reference is a key in this identity
    //map.
    this.containsKey = function(key) {
        return keys.indexOf(key) !== -1;
    };

    //Tests whether the specified object reference is a value in this identity
    //map.
    this.containsValue = function(value) {
        return values.indexOf(value) !== -1;
    };

    //Returns the value to which the specified key is mapped, or null if this
    //map contains no mapping for the key. A return value of null does not
    //necessarily indicate that the map contains no mapping for the key; it's
    //also possible that the map explicitly maps the key to null.
    this.get = function(key) {
        let index = keys.indexOf(key);

        if (index === -1) {
            return null;
        }

        return values[index];
    };

    //Associates the specified value with the specified key in this identity
    //map. If the map previously contained a mapping for the key, the old value
    //is replaced and returned. Otherwise, null is returned. A null return can
    //also indicate that the map previously associated the key with null. Note
    //that NaN may not be used as a key, as it has no identity by definition.
    this.put = function(key, value) {
        if (key !== key) {
            return null; //Key is NaN.
        }

        let index = keys.indexOf(key);

        if (index !== -1) {
            let oldValue = values[index];
            values[index] = value;
            return oldValue;
        }

        keys.push(key);
        values.push(value);
        return null;
    };

    //Removes and returns the mapping for this key from this map if present.
    //Returns null if no such mapping existed. A null return can also indicate
    //that the map previously associated the key with null.
    this.remove = function(key) {
        let index = keys.indexOf(key);

        if (index === -1) {
            return null;
        }

        let oldValue = values[index];
        keys.splice(index, 1);
        values.splice(index, 1);
        return oldValue;
    };

    //Removes all of the mappings from this map.
    this.clear = function() {
        keys = [];
        values = [];
    };

    //Performs the given action for each key-value pair in this identity map
    //until all entries have been processed.
    this.forEachEntry = function(action) {
        let size = keys.length;
        for (let i = 0; i < size; i++) {
            action(keys[i], values[i]);
        }
    };
}

//A collection that contains no identical elements.
IdentitySet = function() {
    //This is exposed so we can iterate over it easily. Do not modify.
    this.elements = [];

    //Returns the number of elements in this identity set.
    this.size = function() {
        return this.elements.length;
    };

    //Returns true if this set contains no elements.
    this.isEmpty = function() {
        return this.elements.length === 0;
    };

    //Tests whether the specified object is an element in this identity set.
    this.contains = function(obj) {
        return this.elements.indexOf(obj) !== -1;
    };

    //Adds the specified element to this set, and returns true if it is not
    //already present.
    this.add = function(element) {
        if (element !== element) {
            return false; //Element is NaN.
        }

        if (this.elements.indexOf(element) !== -1) {
            return false;
        }

        this.elements.push(element);
        return true;
    };

    //Removes the specified element from this set and returns true, if it is
    //an element in this set.
    this.remove = function(element) {
        let index = keys.indexOf(element);
        if (index === -1) {
            return false;
        }

        this.elements.splice(index, 1);
        return true;
    };

    //Removes all of the mappings from this map.
    this.clear = function() {
        this.elements = [];
    };

    //Performs the given action for each element in this identity set until all
    //entries have been processed.
    this.forEach = function(action) {
        for (let element of this.elements) {
            action(element);
        }
    };
}

//Directed acyclic graph class, for input into LayeredGraph.
DAG = function() {
    //Does a strict check to ensure no cycles are created in this graph.
    //Will reduce performance, especially on larger graphs.
    let ENSURE_NO_CYCLES = true;

    let self = this;
    this.vertices = new IdentityMap();

    let Vertex = function(name) {
        this.name = name;
        this.vertsOut = new IdentitySet();
        this.vertsIn = new IdentitySet();
        let self = this;

        this.isSink = function() {
            return self.vertsOut.isEmpty();
        }

        this.isSource = function() {
            return self.vertsIn.isEmpty();
        }
    }

    //Returns whether a vertex exists in this graph with the given name.
    this.containsVertex = function(name) {
        return self.vertices.containsKey(name);
    };

    //Adds the given vertex to this graph, returning true if the vertex did not
    //already exist or false otherwise.
    this.addVertex = function(name) {
        if (self.containsVertex(name)) {
            return false;
        }

        let vertex = new Vertex(name);
        self.vertices.put(name, vertex);
        return true;
    };

    //Removes the given vertex from this graph and returns true, or returns
    //false if no such vertex exists. Also removes any links which reference the
    //given vertex.
    this.removeVertex = function(name) {
        let vertex = self.vertices.remove(name);
        if (vertex === null) {
            return false;
        }

        for (let vEnd of vertex.vertsOut.elements) {
            vEnd.vertsIn.remove(vertex);
        }
        for (let vStart of vertex.vertsIn.elements) {
            vStart.vertsOut.remove(vertex);
        }
        return true;
    };

    let wouldCreateCycle = function(vStart, vEnd) {
        let frontier = new IdentitySet();
        frontier.add(vEnd);

        //Start at end vertex and traverse graph, checking if we can reach the
        //start from the end.
        while (!frontier.isEmpty()) {
            let newFrontier = new IdentitySet();

            for (let current of frontier.elements) {
                for (let vOut of current.vertsOut.elements) {
                    if (vOut === vStart) {
                        return true;
                    }
                    newFrontier.add(vOut);
                }
            }

            frontier = newFrontier;
        }

        return false;
    };

    //Returns whether an edge exists in this graph between the given vertices.
    this.containsEdge = function(start, end) {
        if (!self.containsVertex(start) || !self.containsVertex(end)) {
            return false;
        }

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);
        return vStart.vertsOut.contains(vEnd);
    };

    //Adds the given edge to this graph, returning true if the edge did not
    //already exist or false otherwise. Throws an exception if the given nodes
    //do not both exist. Throws an exception upon trying to add a edge that
    //would make this graph cyclic.
    this.addEdge = function(start, end) {
        if (!self.containsVertex(start)) throw new Error("No such vertex '" + start + "'");
        if (!self.containsVertex(end)) throw new Error("No such vertex '" + end + "'");

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);

        if (vStart.vertsOut.contains(vEnd)) {
            return false;
        }

        if (ENSURE_NO_CYCLES && wouldCreateCycle(vStart, vEnd)) {
            throw new Error("An edge from '" + start + "' to '" + end + "' would create a cycle");
        }

        vStart.vertsOut.add(vEnd);
        vEnd.vertsIn.add(vStart);
        return true;
    };

    //Removes the given edge from this graph and returns true, or returns false
    //if no such edge exists.
    this.removeEdge = function(start, end) {
        if (!self.containsVertex(start) || !self.containsVertex(end)) {
            return false;
        }

        let vStart = self.vertices.get(start);
        let vEnd = self.vertices.get(end);

        if (!vStart.vertsOut.contains(vEnd)) {
            return false;
        }

        vStart.vertsOut.remove(vEnd);
        vEnd.vertsIn.remove(vStart);
        return true;
    };

    //Performs the given action on the vertex names of each edge in this graph.
    this.forEachEdge = function(action) {
        self.vertices.forEachEntry(function(start, vStart) {
            for (let vEnd of vStart.vertsOut.elements) {
                action(start, vEnd.name);
            }
        });
    };
};

//-----------------------------ACTUAL LAYOUT CODE-------------------------------

LayeredGraph = function(dag) {
    let Node = function(vertex) {
        this.vertex = vertex;
        this.layer = 0;
        this.column = 0;

        this.edgesOut = [];
        this.edgesIn = [];

        this.edgeCount = 0;
        this.edgeColumnTotal = 0;
        this.edgeAvgColumn = 0.0;
    };

    let nodes = new IdentityMap();
    dag.vertices.forEachEntry(function(name, vertex) {
        nodes.put(name, new Node(vertex));
    });

    //Assign layers based on reverse (!) topological depth.
    {
        let frontier = new IdentitySet();

        //Start with sinks.
        nodes.forEachEntry(function(name, node) {
            if (node.vertex.isSink()) {
                frontier.add(node);
            }
        });

        //Traverse graph.
        while (!frontier.isEmpty()) {
            let newFrontier = new IdentitySet();

            for (let nCurrent of frontier.elements) {
                for (let vNext of nCurrent.vertex.vertsIn.elements) {
                    let nNext = nodes.get(vNext.name);
                    nNext.layer = Math.max(nCurrent.layer + 1, nNext.layer);
                    newFrontier.add(nNext);
                }
            }

            frontier = newFrontier;
        }
    }

    let maxLayer = 0;
    nodes.forEachEntry(function(name, node) {
        maxLayer = Math.max(maxLayer, node.layer);
    });

    //Reverse layers so that nodes like to stay close to their parents. Should
    //result in less crowding on layer zero.
    nodes.forEachEntry(function(name, node) {
        node.layer = maxLayer - node.layer;
    });

    let table = [];
    for (let i = 0; i <= maxLayer; i++) {
        table.push([]);
    }

    let insertNode = function(node) {
        let layer = table[node.layer];
        node.column = layer.length;
        layer.push(node);
    }

    //Assign arbitrary columns.
    nodes.forEachEntry(function(name, node) {
        insertNode(node);
    });

    //Generate edges and dummy nodes.
    let Edge = function(source, target) {
        this.source = source;
        this.target = target;

        source.edgesOut.push(this);
        target.edgesIn.push(this);
    };

    let dummyNodes = [];

    nodes.forEachEntry(function(start, nStart) {
        nStart.vertex.vertsOut.forEach(function(vEnd) {
            let nDummyStart = nStart;
            let nEnd = nodes.get(vEnd.name);

            while (nEnd.layer > nDummyStart.layer + 1)
            {
                let dummy = new Node();
                dummy.layer = nDummyStart.layer + 1;
                insertNode(dummy);
                dummyNodes.push(dummy);
                new Edge(nDummyStart, dummy);
                nDummyStart = dummy;
            }

            new Edge(nDummyStart, nEnd);
        });
    });

    //Sorts layerToSift by average connection position.
    //The idea is to put nodes close to connected nodes, to reduce edge crossings.
    let siftLayer = function(layerToSift, referenceLayer, isRefAbove) {
        for (let node of layerToSift) {
            node.edgeCount = 0;
            node.edgeColumnTotal = 0;
        }

        if (isRefAbove) {
            for (let nAbove of referenceLayer) {
                for (let edge of nAbove.edgesIn) {
                    edge.source.edgeCount++;
                    edge.source.edgeColumnTotal += nAbove.column;
                }
            }
        }
        else {
            for (let nBelow of referenceLayer) {
                for (let edge of nBelow.edgesOut) {
                    edge.target.edgeCount++;
                    edge.target.edgeColumnTotal += nBelow.column;
                }
            }
        }

        for (let node of layerToSift) {
            node.edgeAvgColumn = node.edgeColumnTotal/node.edgeCount;
        }

        layerToSift.sort(function(a, b) {
            return a.edgeAvgColumn - b.edgeAvgColumn;
        });

        for (let i = 0; i < layerToSift.length; i++) {
            layerToSift[i].column = i;
        }
    }

    for (let iteration = 0; iteration < 2; iteration++) {
        //Sift from bottom to top.
        for (let i = 1; i <= maxLayer; i++)
            siftLayer(table[i], table[i - 1], false);

        //Then sift again from top to bottom.
        for (let i = maxLayer - 1; i >= 0; i--)
            siftLayer(table[i], table[i + 1], true);
    }

    /*//Debug print
    for (let i = 0; i <= maxLayer; i++) {
        let layer = table[i];
        for (let node of layer) {
            console.log(node);
        }
    }
    */

    //Expose table and dimensions.
    this.table = table;
    this.height = maxLayer;
    this.width = 0;
    for (let i = 0; i < table.length; i++) {
        this.width = Math.max(this.width, table[i].length - 1);
    }

    //Populate node array.
    this.nodes = [];
    let self = this;
    nodes.forEachEntry(function(name, node) {
        self.nodes.push(node);
    });

    for (let node of dummyNodes) {
        this.nodes.push(node);
    }

    //Populate edge array.
    this.edges = [];
    for (let node of this.nodes) {
        for (let edge of node.edgesOut) {
            this.edges.push(edge);
        }
    }
};
<|endoftext|>