<commit_before>// ==== Pebble Surfcast Application ====
// pebble-js-app.js
//
// This file provides the following functionality:
// * Creates a unique list of the counties represented by the chosen spots
// * On receipt of forecast request from watch, queries the Spitcast API for forecast data for the duration requested
// * Also queries the Spitcast API for tide height data for the same duration in the counties represented by the chosen spots
// * Sends JSON blobs with forecast and tide height data to watch using Pebble's send AppMessage
//

// TODO: Double check variable scopes!!!

// ========== GLOBAL VARIABLES ==========
var mySpots = [
		{ county: "orange-county", spot_id: 602, spot_name: "Surfside Jetty" },
		{ county: "santa-cruz", spot_id: 2, spot_name: "Steamer Lane" },
		{ county: "san-diego", spot_id: 594, spot_name: "Oceanside Pier" },
		{ county: "san-francisco", spot_id: 697, spot_name: "Kellys Cove" },
		{ county: "monterey", spot_id: 154, spot_name: "Carmel Beach" }
	];
var myCounties = [ "orange-county","santa-cruz","san-diego","san-francisco","monterey" ];
var myForecasts = [];
var myTides = [];
var queuedMessage = {};
var duration = 5;
// ========== GLOBAL VARIABLES ==========

// This function uses regex to return the number day of the forecast
function getDayFromString( str ) {
	return( parseInt( str.replace( /^[A-Za-z\s]*/,"" ).replace( /\s[0-9]{4}$/,"" ),10 ) );
}

// This function uses regex to return the hour of the forecast
function getHourFromString( str ) {
	var hour = 0;

	if( str.match( '12AM' ) != null ) { hour = -12; }
	else if( str.match( '12PM' ) != null ) { hour = 0; }
	else if( str.match( /PM$/ ) != null ) { hour = 12; }

	hour += parseInt( str.replace( /[AP][M]$/,"" ),10 );

	return( hour );
}

// This function returns an integer key value equivalent to the provided forecast condition string
function conditionIdFromString( conditionType ) {
	switch( conditionType ) {
  	case "Poor": return 0; break;
		case "Poor-Fair": return 1; break;
		case "Fair": return 2; break;
		case "Fair-Good": return 3; break;
		case "Good": return 4; break;
  }
}

// This function performs the API calls for forecast data & calls sendAppMessage to send the response to the Pebble watch for display
function fetchSpotConditions( spot, duration ) {
	var request = new XMLHttpRequest();
	request.open( 'GET', 'http://api.spitcast.com/api/spot/forecast/'+spot.spot_id.toString()+'/?dcat=week', true );

	request.onload = function( e ) {
		if( request.readyState == 4 && request.status == 200 ) {
			var response = JSON.parse( request.responseText );

			if( response && response.length > 0 ) {
				for( var i=0; i<duration; i++ ) {
					var result = response[ i ];
					myForecasts.push({ "spot": result.spot_name.toString(), "county": spot.county, "date": getDayFromString( result.date ), "time": getHourFromString( result.hour ), "general": conditionIdFromString( result.shape_full ), "swell": conditionIdFromString( result.shape_detail.swell ), "tide": conditionIdFromString( result.shape_detail.tide ), "wind": conditionIdFromString( result.shape_detail.wind ), "swell_size": result.size.toString() /* No support for snprintf in app */ });
				}
			}
		}
		else { console.log( "Error" ); }
	}
	request.send( null );
}

// This function performs the API calls for tide data & calls sendAppMessage to send the response to the Pebble watch for display
function fetchCountyTides( county, duration ) {
	var request = new XMLHttpRequest();
	request.open( 'GET', 'http://api.spitcast.com/api/county/tide/'+county+'/', true );

	request.onload = function( e ) {
		if( request.readyState == 4 && request.status == 200 ) {
			var response = JSON.parse( request.responseText );

			if( response && response.length > 0 ) {
				for( var i=0; i<duration; i++ ) {
					var result = response[ i ];
					var height = Math.floor( result.tide*100 );
					myTides.push({ "county": county.toString(), "date": getDayFromString( result.date ), "hour": getHourFromString( result.hour ), "tide_height": height });
				}
			}
		}
		else { console.log( "Error" ); }
	}
	request.send( null );
}

// Can optimize this further by allowing param of retrievalDate, to minimize messages to send to watch
// This function facilitates the collection of forecasts and tide heights
function fetchSurfcast( duration ) {
	for( var i=0; i<mySpots.length; i++ ){
		fetchSpotConditions( mySpots[ i ],duration );
	}

	for( var j=0; j<myCounties.length; j++ ){
		fetchCountyTides( myCounties[ j ],duration );
	}
}

// This function transmits the next forecast/tide data to the Pebble watch
function transmitNextForecast() {
	console.log( "Forecasts: "+myForecasts.length );
	console.log( "Tides: "+myTides.length );

	if( myForecasts.length > 0 ) {
		queuedMessage = myForecasts.pop();
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Forecast transmission" );
	}
	else if( myTides.length > 0 ) {
		queuedMessage = myTides.pop();
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Tide transmission" );
	}
	else {
		Pebble.sendAppMessage({ "request_status": 0 })
		console.log( "Empty tides/forecasts" );
	}
}

// This function retransmits forecast/tide data that may not have made it to the Pebble watch
function retransmitForecast() {
	if( Object.keys( queuedMessage ).length > 0 ){
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Resent dropped message" );
	}
	else {
		console.log( "No messages in queue" );
		transmitNextForecast();
	}
}

// This listener directs the preparation for forecast requests
PebbleEventListener.addEventListener( "ready",
                        function( e ) {
                          console.log( "connect!" + e.ready );
													fetchSurfcast( duration );
													console.log( e.type );
                        });

// This listener directs the forecast request & response activities
PebbleEventListener.addEventListener( "appmessage",
                        function( e ) {
                          console.log( e.type );
													console.log( e.payload.request_status );
													console.log( "message!" );
													if( e.payload.request_status == 2 ){ retransmitForecast(); }
													else if( e.payload.request_status == 3 ){ fetchSurfcast( duration ); }
													else { transmitNextForecast(); }
                        });
// Removed "if( myTides.length > 0 || myForecasts.length >0 )" from last else because the extra +1 iteration seems to be necessary to signal the watch app

// This listener is not currently used -- leaving for possible future improvements
PebbleEventListener.addEventListener( "webviewclosed",
                                     function(e) {
                                     console.log( "webview closed" );
                                     console.log( e.type );
                                     console.log( e.response );
                                     });<commit_msg>Light cleanup of comments & unused code<commit_after>// ==== Pebble Surfcast Application ====
// pebble-js-app.js
//
// This file provides the following functionality:
// * On receipt of forecast request from watch, queries the Spitcast API for forecast data for the duration requested
// * Also queries the Spitcast API for tide height data for the same duration in the counties represented by the chosen spots
// * Sends JSON blobs with forecast and tide height data to watch using Pebble's sendAppMessage
//

// TODO: Double check variable scopes!!!

// ========== GLOBAL VARIABLES ==========
var mySpots = [
		{ county: "orange-county", spot_id: 602, spot_name: "Surfside Jetty" },
		{ county: "santa-cruz", spot_id: 2, spot_name: "Steamer Lane" },
		{ county: "san-diego", spot_id: 594, spot_name: "Oceanside Pier" },
		{ county: "san-francisco", spot_id: 697, spot_name: "Kellys Cove" },
		{ county: "monterey", spot_id: 154, spot_name: "Carmel Beach" }
	];
var myCounties = [ "orange-county","santa-cruz","san-diego","san-francisco","monterey" ];
var myForecasts = [];
var myTides = [];
var queuedMessage = {};
var duration = 5;
// ========== GLOBAL VARIABLES ==========

// This function uses regex to return the number day of the forecast
function getDayFromString( str ) {
	return( parseInt( str.replace( /^[A-Za-z\s]*/,"" ).replace( /\s[0-9]{4}$/,"" ),10 ) );
}

// This function uses regex to return the hour of the forecast
function getHourFromString( str ) {
	var hour = 0;

	if( str.match( '12AM' ) != null ) { hour = -12; }
	else if( str.match( '12PM' ) != null ) { hour = 0; }
	else if( str.match( /PM$/ ) != null ) { hour = 12; }

	hour += parseInt( str.replace( /[AP][M]$/,"" ),10 );

	return( hour );
}

// This function returns an integer key value equivalent to the provided forecast condition string
function conditionIdFromString( conditionType ) {
	switch( conditionType ) {
  	case "Poor": return 0; break;
		case "Poor-Fair": return 1; break;
		case "Fair": return 2; break;
		case "Fair-Good": return 3; break;
		case "Good": return 4; break;
  }
}

// This function performs the API calls for forecast data & calls sendAppMessage to send the response to the Pebble watch for display
function fetchSpotConditions( spot, duration ) {
	var request = new XMLHttpRequest();
	request.open( 'GET', 'http://api.spitcast.com/api/spot/forecast/'+spot.spot_id.toString()+'/?dcat=week', true );

	request.onload = function( e ) {
		if( request.readyState == 4 && request.status == 200 ) {
			var response = JSON.parse( request.responseText );

			if( response && response.length > 0 ) {
				for( var i=0; i<duration; i++ ) {
					var result = response[ i ];
					myForecasts.push({ "spot": result.spot_name.toString(), "county": spot.county, "date": getDayFromString( result.date ), "time": getHourFromString( result.hour ), "general": conditionIdFromString( result.shape_full ), "swell": conditionIdFromString( result.shape_detail.swell ), "tide": conditionIdFromString( result.shape_detail.tide ), "wind": conditionIdFromString( result.shape_detail.wind ), "swell_size": result.size.toString() /* No support for snprintf in app */ });
				}
			}
		}
		else { console.log( "Error" ); }
	}
	request.send( null );
}

// This function performs the API calls for tide data & calls sendAppMessage to send the response to the Pebble watch for display
function fetchCountyTides( county, duration ) {
	var request = new XMLHttpRequest();
	request.open( 'GET', 'http://api.spitcast.com/api/county/tide/'+county+'/', true );

	request.onload = function( e ) {
		if( request.readyState == 4 && request.status == 200 ) {
			var response = JSON.parse( request.responseText );

			if( response && response.length > 0 ) {
				for( var i=0; i<duration; i++ ) {
					var result = response[ i ];
					var height = Math.floor( result.tide*100 );
					myTides.push({ "county": county.toString(), "date": getDayFromString( result.date ), "hour": getHourFromString( result.hour ), "tide_height": height });
				}
			}
		}
		else { console.log( "Error" ); }
	}
	request.send( null );
}

// Can optimize this further by allowing param of retrievalDate, to minimize messages to send to watch
// This function facilitates the collection of forecasts and tide heights
function fetchSurfcast( duration ) {
	for( var i=0; i<mySpots.length; i++ ){
		fetchSpotConditions( mySpots[ i ],duration );
	}

	for( var j=0; j<myCounties.length; j++ ){
		fetchCountyTides( myCounties[ j ],duration );
	}
}

// This function transmits the next forecast/tide data to the Pebble watch
function transmitNextForecast() {
	console.log( "Forecasts: "+myForecasts.length );
	console.log( "Tides: "+myTides.length );

	if( myForecasts.length > 0 ) {
		queuedMessage = myForecasts.pop();
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Forecast transmission" );
	}
	else if( myTides.length > 0 ) {
		queuedMessage = myTides.pop();
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Tide transmission" );
	}
	else {
		Pebble.sendAppMessage({ "request_status": 0 })
		console.log( "Empty tides/forecasts" );
	}
}

// This function retransmits forecast/tide data that may not have made it to the Pebble watch
function retransmitForecast() {
	if( Object.keys( queuedMessage ).length > 0 ){
		Pebble.sendAppMessage( queuedMessage );
		console.log( "Resent dropped message" );
	}
	else {
		console.log( "No messages in queue" );
		transmitNextForecast();
	}
}

// This listener directs the preparation for forecast requests
PebbleEventListener.addEventListener( "ready",
                        function( e ) {
                          console.log( "connect!" + e.ready );
													fetchSurfcast( duration );
													console.log( e.type );
                        });

// This listener directs the forecast request & response activities
PebbleEventListener.addEventListener( "appmessage",
                        function( e ) {
                          console.log( e.type );
													console.log( e.payload.request_status );
													console.log( "message!" );
													if( e.payload.request_status == 2 ){ retransmitForecast(); }
													else if( e.payload.request_status == 3 ){ fetchSurfcast( duration ); }
													else { transmitNextForecast(); }
                        });
// Removed "if( myTides.length > 0 || myForecasts.length >0 )" from last else because the extra +1 iteration seems to be necessary to signal the watch app

// This listener is not currently used -- leaving for possible future improvements
// PebbleEventListener.addEventListener( "webviewclosed",
//                                      function(e) {
//                                      console.log( "webview closed" );
//                                      console.log( e.type );
//                                      console.log( e.response );
//                                      });<|endoftext|>


<commit_before>var Player = function(radio) {
	this.player = $("#player");
	this.playPauseButton = $("#playPauseButton");
	this.muteButton = $("#muteButton");
	this.volumeSlider = $("#volumeSlider");
	this.radioSelector = $("#radioSelector");

	this.set_radio(radio);

	$(this.radioSelector).on('change input', (function() {
		var radio = new window['' + $(this.radioSelector).val()]();
		this.set_radio(radio);
	}).bind(this));

	$(this.player).on('play playing', (function() {
		$(this.playPauseButton).removeClass("fa-play");
		$(this.playPauseButton).addClass("fa-pause");
	}).bind(this));

	$(this.player).on('pause ended', (function() {
		$(this.playPauseButton).removeClass("fa-pause");
		$(this.playPauseButton).addClass("fa-play");
	}).bind(this));

	$(this.player).on('volumechange', (function() {

		var volume = $(this.player).prop('volume');

		if ($(this.player).prop("volume") == 0 || $(this.player).prop("muted")) {
			$(this.muteButton).removeClass("fa-volume-up");
			$(this.muteButton).addClass("fa-volume-off");
		} else {
			$(this.muteButton).removeClass("fa-volume-off");
			$(this.muteButton).addClass("fa-volume-up");
		}

		var e = new CustomEvent('volume_changed', {'detail': {'volume': volume * 100}});
		document.dispatchEvent(e);
	}).bind(this));

	$(this.volumeSlider).val($(this.player).prop("volume") * 100);

	$(this.volumeSlider).on('change input', (function() {
		$(this.player).prop("volume", $(this.volumeSlider).val() / 100);
	}).bind(this));

	$(this.playPauseButton).on('click', (function() { this.play_pause() }).bind(this));
	$(this.muteButton).on('click', (function() { this.toggle_mute()}).bind(this));

	$(document).keypress((function(e) {
		if(e.which == 32) {
			this.play_pause();
		}
	}).bind(this));

	$(this.player).prop("muted", '');

	this.rds  = new RDS(this);
	this.rds.rds_loop();

	this.lastFM = new LastFMClient();
};

Player.prototype.toggle_mute = function() {
	if ($(this.player).prop("muted")) {
		$(this.player).prop("muted", false);
	} else {
		$(this.player).prop("muted", true);
	}
}

Player.prototype.play_pause = function() {
		if ($(this.player).prop("paused")) {
			$(this.player).trigger("load");
		} else {
			$(this.player).trigger("pause");
		}
	}

Player.prototype.set_source = function(source) {
	$(this.player).children().first().attr("src", source);
	$(this.player).trigger("load");
}

Player.prototype.set_radio = function(radio) {
	this.radio = radio;
	this.set_source(radio.stream_url);
	$(this.radioSelector).val(radio.class_name);
}

Player.prototype.set_volume = function(volume) {
	$(this.player).prop("volume", volume / 100);
	$(this.volumeSlider).val($(this.player).prop("volume") * 100);
}
<commit_msg>Use all the volume icons according to the current volume level! :D<commit_after>var Player = function(radio) {
	this.player = $("#player");
	this.playPauseButton = $("#playPauseButton");
	this.muteButton = $("#muteButton");
	this.volumeSlider = $("#volumeSlider");
	this.radioSelector = $("#radioSelector");

	this.set_radio(radio);

	$(this.radioSelector).on('change input', (function() {
		var radio = new window['' + $(this.radioSelector).val()]();
		this.set_radio(radio);
	}).bind(this));

	$(this.player).on('play playing', (function() {
		$(this.playPauseButton).removeClass("fa-play");
		$(this.playPauseButton).addClass("fa-pause");
	}).bind(this));

	$(this.player).on('pause ended', (function() {
		$(this.playPauseButton).removeClass("fa-pause");
		$(this.playPauseButton).addClass("fa-play");
	}).bind(this));

	$(this.player).on('volumechange', (function() {

		var volume = $(this.player).prop('volume');

		$(this.muteButton).removeClass("fa-volume-up");
		$(this.muteButton).removeClass("fa-volume-down");
		$(this.muteButton).removeClass("fa-volume-off");
		if (volume <= 0.01 || $(this.player).prop("muted")) {
			$(this.muteButton).addClass("fa-volume-off");
		} else {
			if (volume < 0.5 ) {
				$(this.muteButton).addClass("fa-volume-down");
			} else {
				$(this.muteButton).addClass("fa-volume-up");
			}
		}

		var e = new CustomEvent('volume_changed', {'detail': {'volume': volume * 100}});
		document.dispatchEvent(e);
	}).bind(this));

	$(this.volumeSlider).val($(this.player).prop("volume") * 100);

	$(this.volumeSlider).on('change input', (function() {
		$(this.player).prop("volume", $(this.volumeSlider).val() / 100);
	}).bind(this));

	$(this.playPauseButton).on('click', (function() { this.play_pause() }).bind(this));
	$(this.muteButton).on('click', (function() { this.toggle_mute()}).bind(this));

	$(document).keypress((function(e) {
		if(e.which == 32) {
			this.play_pause();
		}
	}).bind(this));

	$(this.player).prop("muted", '');

	this.rds  = new RDS(this);
	this.rds.rds_loop();

	this.lastFM = new LastFMClient();
};

Player.prototype.toggle_mute = function() {
	if ($(this.player).prop("muted")) {
		$(this.player).prop("muted", false);
	} else {
		$(this.player).prop("muted", true);
	}
}

Player.prototype.play_pause = function() {
		if ($(this.player).prop("paused")) {
			$(this.player).trigger("load");
		} else {
			$(this.player).trigger("pause");
		}
	}

Player.prototype.set_source = function(source) {
	$(this.player).children().first().attr("src", source);
	$(this.player).trigger("load");
}

Player.prototype.set_radio = function(radio) {
	this.radio = radio;
	this.set_source(radio.stream_url);
	$(this.radioSelector).val(radio.class_name);
}

Player.prototype.set_volume = function(volume) {
	$(this.player).prop("volume", volume / 100);
	$(this.volumeSlider).val($(this.player).prop("volume") * 100);
}
<|endoftext|>


<commit_before>"use strict";

function Grapher() {
}

Grapher.prototype._find_topmost_point = function(point_list) {
  var points = point_list.split(' ').map(function(point) {
    return point.split(',').map(function(coord) {
      return parseFloat(coord);
    });
  });

  var top_x = points[0][0];
  var top_y = points[0][1];
  points.forEach(function(point) {
    var x = point[0];
    var y = point[1];

    if(
      y < top_y ||
      (y === top_y && x < top_x)
    ) {
      top_x = x;
      top_y = y;
    }
  });

  return {
    x: top_x,
    y: top_y
  };
};

Grapher.prototype._show_tooltip = function(svg, polygon, hsp) {
  svg = $(svg);
  polygon = $(polygon);

  var svg_pos = svg.offset();
  var topmost_polygon_point = this._find_topmost_point(polygon.attr('points'));

  var position_formatter = d3.format(',d');

  var tooltip_info = [
    ['Bit score', hsp.bit_score],
    ['E value', hsp.evalue],
    ['Query start', position_formatter(hsp.query_start)],
    ['Query end', position_formatter(hsp.query_end)],
    ['Query frame', hsp.query_frame],
    ['Subject start', position_formatter(hsp.subject_start)],
    ['Subject end', position_formatter(hsp.subject_end)],
    ['Subject frame', hsp.subject_frame],
  ];
  var tooltip_contents = tooltip_info.map(function(info) {
    var key = info[0];
    var value = info[1];
    return '
' + key + ': ' + value + '
';
  }).join('\n');

  var tooltip_container = d3.select('#tooltip');
  var tooltip = tooltip_container.select('.list');

  tooltip.html(tooltip_contents);
  tooltip_container.style('left', svg_pos.left + svg.width() + 'px')
                   .style('top', svg_pos.top + topmost_polygon_point.y + 'px')
                   .transition()
                   .duration(200)
                   .style('opacity', 0.9);
}

Grapher.prototype._hide_tooltip = function() {
  var tooltip = d3.select('#tooltip');
  tooltip.transition()
         .duration(200)
         .style('opacity', 0);
}

Grapher.prototype._fade_other_polygons = function(svg, hovered_index, opacity) {
   svg.selectAll('.hit')
      .filter(function(hit, index) {
        return index !== hovered_index;
      }).transition()
      .duration(200)
      .style('opacity', opacity);
}

Grapher.prototype._rotate_axis_labels = function(text, text_anchor, dx, dy) {
  text.style('text-anchor', text_anchor)
      .attr('dx', dx)
      .attr('dy', dy)
      .attr('transform', 'rotate(-90)');
}

Grapher.prototype._create_axis = function(svg, scale, orientation, height, text_anchor, dx, dy) {
  var scinotation_formatter = d3.format('.2s');
  var formatter = function(val) {
    return scinotation_formatter(val) + 'b';
  }

  var axis = d3.svg.axis()
               .scale(scale)
               .tickFormat(formatter)
               .orient(orientation);

  var container = svg.append('g')
     .attr('class', 'axis')
     .attr('transform', 'translate(0,' + height + ')')
     .call(axis);
  this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);

  return {
    container: container,
    axis:      axis
  };
}

Grapher.prototype._is_domain_within_orig = function(original_domain, new_domain) {
  return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
}

Grapher.prototype._zoom_scale = function(scale, original_domain, zoom_from, scale_by) {
  var l = scale.domain()[0];
  var r = scale.domain()[1];

  l = zoom_from - (zoom_from - l) / scale_by;
  r = zoom_from + (r - zoom_from) / scale_by;

  l = Math.round(l);
  r = Math.round(r);
  // If Math.round(r) - Math.round(l) = 1 from previous zoom event, and for
  // this event, fractional parts of l and r such that l rounds up and r rounds
  // down, we end up with l = r. This is bad, since we're telling d3 to create
  // scale consisting of only a single point, not an interval.
  if(l == r)
    r = l + 1;

  var new_domain = [l, r];
  if(this._is_domain_within_orig(original_domain, new_domain))
    scale.domain(new_domain);
  else
    scale.domain(original_domain);
}

Grapher.prototype._pan_scale = function(existing_scale, original_domain, delta) {
  var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
  var scaled_delta = -delta * scale;

  var domain = existing_scale.domain();
  var l = domain[0] + scaled_delta;
  var r = domain[1] + scaled_delta;
  var new_domain = [l, r];

  if(this._is_domain_within_orig(original_domain, new_domain))
    existing_scale.domain(new_domain);
}

Grapher.prototype._create_graph = function(svg, hit, query_height, query_scale, subject_height, subject_scale) {
  var self = this;

  // Remove all existing child elements.
  svg.selectAll('*').remove();

  // Add polygons.
  svg.selectAll('polygon')
     .data(hit.hsps)
     .enter()
     .append('polygon')
     .attr('class', 'hit')
     .attr('fill', function(hsp) {
       var val = parseInt(255*(1 - hsp.normalized_bit_score), 10);
       var colour = 'rgba(' + [val, val, val].join(',') + ',1.0)';
       return colour;
     }).attr('points', function(hsp) {
       var points = [
         [query_scale(hsp.query_start),     query_height   + 1],
         [subject_scale(hsp.subject_start), subject_height - 1],
         [subject_scale(hsp.subject_end),   subject_height - 1],
         [query_scale(hsp.query_end),       query_height   + 1],
       ];

       return points.map(function(point) {
        return point[0] + ',' + point[1];
       }).join(' ');
     }).on('mouseover', function(hovered_hsp, hovered_index) {
       self._show_tooltip(svg[0][0], this, hovered_hsp);
       self._fade_other_polygons(svg, hovered_index, 0.1);
     }).on('mouseout', function(hovered_hsp, hovered_index) {
       self._hide_tooltip.apply(this, arguments);
       self._fade_other_polygons(svg, hovered_index, 1);
     });

  var query_axis   = self._create_axis(svg, query_scale,   'top',    query_height,   'start', '0.8em', '1em');
  var subject_axis = self._create_axis(svg, subject_scale, 'bottom', subject_height, 'end',   '-1em',  '-0.4em');
}

Grapher.prototype._display_graph = function(iteration, hit, table_row) {
  var self = this;

  var padding_x = 20;
  var padding_y = 50;
  var canvas_width = 500;
  var canvas_height = 300;

  var svg = table_row.append('td')
                     .append('svg')
                     .attr('width', canvas_width)
                     .attr('height', canvas_height);

  var original_subject_domain = [0, hit.subject_length];

  var query_scale = d3.scale.linear()
                         .domain([0, iteration.query_length])
                         .range([padding_x, canvas_width - padding_x]);
  var subject_scale = d3.scale.linear()
                         .domain(original_subject_domain)
                         .range([padding_x, canvas_width - padding_x]);

  var query_height = padding_y;
  var subject_height = canvas_height - padding_y;
  this._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);

  var last_x = null;
  svg.on('mousedown',  function() { last_x = d3.event.clientX; });
  svg.on('mouseup',    function() { last_x = null;             });
  svg.on('mouseleave', function() { last_x = null;             });
  svg.on('mousemove',  function() {
    if(last_x === null)
      return;

    var new_x = d3.event.clientX;
    var delta = new_x - last_x;
    last_x = new_x;

    self._pan_scale(subject_scale, original_subject_domain, delta);
    self._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);
  });

  function handle_mouse_wheel() {
    var evt = d3.event;
    evt.preventDefault();

    var scale_by = 2;
    var direction = (evt.deltaY < 0 || evt.wheelDelta > 0) ? 1 : -1;
    if(direction < 0)
      scale_by = 1/scale_by;

    var mouse_coords = d3.mouse(svg[0][0]);
    // Take x-coordinate of mouse, figure out where that lies on subject
    // axis, then place that point on centre of new zoomed axis.
    var zoom_from = subject_scale.invert(mouse_coords[0]);

    self._zoom_scale(
      subject_scale,
      original_subject_domain,
      zoom_from,
      scale_by,
      hit.subject_length
    );
    self._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);
  }
  svg.on('mousewheel', handle_mouse_wheel); // Chrome
  svg.on('wheel',      handle_mouse_wheel); // Firefox, IE
}

Grapher.prototype.display_blast_iterations = function(iterations, results_table, iface) {
  var self = this;

  $('#results-container').show(); // Hidden by default at app start.
  $(results_table).find('tr').remove();

  iterations.forEach(function(iteration) {
    var hits = iteration.filtered_hits;
    // Do not display iteration if it has no hits (e.g., because they've all
    // been filtered out via subject filter).
    if(hits.length === 0)
      return;

    iface.create_header(results_table, iteration.query_def);
    hits.forEach(function(hit) {
      var table_row = d3.select(results_table).append('tr');
      var label_cell = table_row.append('td')
      label_cell.html('ID: ' + hit.subject_id +
        '
Def: ' + hit.subject_def);
      self._display_graph(iteration, hit, table_row);
    });
  });
}
<commit_msg>Enable scaling both subject and query axes.<commit_after>"use strict";

function Grapher() {
}

Grapher.prototype._find_topmost_point = function(point_list) {
  var points = point_list.split(' ').map(function(point) {
    return point.split(',').map(function(coord) {
      return parseFloat(coord);
    });
  });

  var top_x = points[0][0];
  var top_y = points[0][1];
  points.forEach(function(point) {
    var x = point[0];
    var y = point[1];

    if(
      y < top_y ||
      (y === top_y && x < top_x)
    ) {
      top_x = x;
      top_y = y;
    }
  });

  return {
    x: top_x,
    y: top_y
  };
};

Grapher.prototype._show_tooltip = function(svg, polygon, hsp) {
  svg = $(svg);
  polygon = $(polygon);

  var svg_pos = svg.offset();
  var topmost_polygon_point = this._find_topmost_point(polygon.attr('points'));

  var position_formatter = d3.format(',d');

  var tooltip_info = [
    ['Bit score', hsp.bit_score],
    ['E value', hsp.evalue],
    ['Query start', position_formatter(hsp.query_start)],
    ['Query end', position_formatter(hsp.query_end)],
    ['Query frame', hsp.query_frame],
    ['Subject start', position_formatter(hsp.subject_start)],
    ['Subject end', position_formatter(hsp.subject_end)],
    ['Subject frame', hsp.subject_frame],
  ];
  var tooltip_contents = tooltip_info.map(function(info) {
    var key = info[0];
    var value = info[1];
    return '
' + key + ': ' + value + '
';
  }).join('\n');

  var tooltip_container = d3.select('#tooltip');
  var tooltip = tooltip_container.select('.list');

  tooltip.html(tooltip_contents);
  tooltip_container.style('left', svg_pos.left + svg.width() + 'px')
                   .style('top', svg_pos.top + topmost_polygon_point.y + 'px')
                   .transition()
                   .duration(200)
                   .style('opacity', 0.9);
}

Grapher.prototype._hide_tooltip = function() {
  var tooltip = d3.select('#tooltip');
  tooltip.transition()
         .duration(200)
         .style('opacity', 0);
}

Grapher.prototype._fade_other_polygons = function(svg, hovered_index, opacity) {
   svg.selectAll('.hit')
      .filter(function(hit, index) {
        return index !== hovered_index;
      }).transition()
      .duration(200)
      .style('opacity', opacity);
}

Grapher.prototype._rotate_axis_labels = function(text, text_anchor, dx, dy) {
  text.style('text-anchor', text_anchor)
      .attr('dx', dx)
      .attr('dy', dy)
      .attr('transform', 'rotate(-90)');
}

Grapher.prototype._create_axis = function(svg, scale, orientation, height, text_anchor, dx, dy) {
  var scinotation_formatter = d3.format('.2s');
  var formatter = function(val) {
    return scinotation_formatter(val) + 'b';
  }

  var axis = d3.svg.axis()
               .scale(scale)
               .tickFormat(formatter)
               .orient(orientation);

  var container = svg.append('g')
     .attr('class', 'axis')
     .attr('transform', 'translate(0,' + height + ')')
     .call(axis);
  this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);

  return {
    container: container,
    axis:      axis
  };
}

Grapher.prototype._is_domain_within_orig = function(original_domain, new_domain) {
  return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
}

Grapher.prototype._zoom_scale = function(scale, original_domain, zoom_from, scale_by) {
  var l = scale.domain()[0];
  var r = scale.domain()[1];

  l = zoom_from - (zoom_from - l) / scale_by;
  r = zoom_from + (r - zoom_from) / scale_by;

  l = Math.round(l);
  r = Math.round(r);
  // If Math.round(r) - Math.round(l) = 1 from previous zoom event, and for
  // this event, fractional parts of l and r such that l rounds up and r rounds
  // down, we end up with l = r. This is bad, since we're telling d3 to create
  // scale consisting of only a single point, not an interval.
  if(l == r)
    r = l + 1;

  var new_domain = [l, r];
  if(this._is_domain_within_orig(original_domain, new_domain))
    scale.domain(new_domain);
  else
    scale.domain(original_domain);
}

Grapher.prototype._pan_scale = function(existing_scale, original_domain, delta) {
  var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
  var scaled_delta = -delta * scale;

  var domain = existing_scale.domain();
  var l = domain[0] + scaled_delta;
  var r = domain[1] + scaled_delta;
  var new_domain = [l, r];

  if(this._is_domain_within_orig(original_domain, new_domain))
    existing_scale.domain(new_domain);
}

Grapher.prototype._create_graph = function(svg, hit, query_height, query_scale, subject_height, subject_scale) {
  var self = this;

  // Remove all existing child elements.
  svg.selectAll('*').remove();

  // Add polygons.
  svg.selectAll('polygon')
     .data(hit.hsps)
     .enter()
     .append('polygon')
     .attr('class', 'hit')
     .attr('fill', function(hsp) {
       var val = parseInt(255*(1 - hsp.normalized_bit_score), 10);
       var colour = 'rgba(' + [val, val, val].join(',') + ',1.0)';
       return colour;
     }).attr('points', function(hsp) {
       var points = [
         [query_scale(hsp.query_start),     query_height   + 1],
         [subject_scale(hsp.subject_start), subject_height - 1],
         [subject_scale(hsp.subject_end),   subject_height - 1],
         [query_scale(hsp.query_end),       query_height   + 1],
       ];

       return points.map(function(point) {
        return point[0] + ',' + point[1];
       }).join(' ');
     }).on('mouseover', function(hovered_hsp, hovered_index) {
       self._show_tooltip(svg[0][0], this, hovered_hsp);
       self._fade_other_polygons(svg, hovered_index, 0.1);
     }).on('mouseout', function(hovered_hsp, hovered_index) {
       self._hide_tooltip.apply(this, arguments);
       self._fade_other_polygons(svg, hovered_index, 1);
     });

  var query_axis   = self._create_axis(svg, query_scale,   'top',    query_height,   'start', '0.8em', '1em');
  var subject_axis = self._create_axis(svg, subject_scale, 'bottom', subject_height, 'end',   '-1em',  '-0.4em');
}

Grapher.prototype._find_nearest_scale = function(point, scales) {
  var nearest = null;
  var smallest_distance = Number.MAX_VALUE;

  scales.forEach(function(scale_and_height) {
    var delta = Math.abs(scale_and_height.height - point[1]);
    if(delta < smallest_distance) {
      nearest = scale_and_height.scale;
      smallest_distance = delta;
    }
  });

  return nearest;
}

Grapher.prototype._display_graph = function(iteration, hit, table_row) {
  var self = this;

  var padding_x = 20;
  var padding_y = 50;
  var canvas_width = 500;
  var canvas_height = 300;

  var svg = table_row.append('td')
                     .append('svg')
                     .attr('width', canvas_width)
                     .attr('height', canvas_height);

  var query_scale = d3.scale.linear()
                         .domain([0, iteration.query_length])
                         .range([padding_x, canvas_width - padding_x]);
  query_scale.original_domain = query_scale.domain();
  var subject_scale = d3.scale.linear()
                         .domain([0, hit.subject_length])
                         .range([padding_x, canvas_width - padding_x]);
  subject_scale.original_domain = subject_scale.domain();

  var query_height = padding_y;
  var subject_height = canvas_height - padding_y;
  this._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);

  var last_x = null;
  svg.on('mousedown',  function() { last_x = d3.event.clientX; });
  svg.on('mouseup',    function() { last_x = null;             });
  svg.on('mouseleave', function() { last_x = null;             });
  svg.on('mousemove',  function() {
    if(last_x === null)
      return;

    var new_x = d3.event.clientX;
    var delta = new_x - last_x;
    last_x = new_x;

    self._pan_scale(subject_scale, subject_scale.original_domain, delta);
    self._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);
  });

  function handle_mouse_wheel() {
    var evt = d3.event;
    evt.preventDefault();

    var scale_by = 2;
    var direction = (evt.deltaY < 0 || evt.wheelDelta > 0) ? 1 : -1;
    if(direction < 0)
      scale_by = 1/scale_by;

    var mouse_coords = d3.mouse(svg[0][0]);
    var target_scale = self._find_nearest_scale(mouse_coords, [
      { name: 'subject', height: subject_height, scale: subject_scale },
      { name: 'query',   height: query_height,   scale: query_scale },
    ]);
    // Take x-coordinate of mouse, figure out where that lies on subject
    // axis, then place that point on centre of new zoomed axis.
    var zoom_from = target_scale.invert(mouse_coords[0]);

    self._zoom_scale(
      target_scale,
      target_scale.original_domain,
      zoom_from,
      scale_by
    );
    self._create_graph(svg, hit, query_height, query_scale, subject_height, subject_scale);
  }
  svg.on('mousewheel', handle_mouse_wheel); // Chrome
  svg.on('wheel',      handle_mouse_wheel); // Firefox, IE
}

Grapher.prototype.display_blast_iterations = function(iterations, results_table, iface) {
  var self = this;

  $('#results-container').show(); // Hidden by default at app start.
  $(results_table).find('tr').remove();

  iterations.forEach(function(iteration) {
    var hits = iteration.filtered_hits;
    // Do not display iteration if it has no hits (e.g., because they've all
    // been filtered out via subject filter).
    if(hits.length === 0)
      return;

    iface.create_header(results_table, iteration.query_def);
    hits.forEach(function(hit) {
      var table_row = d3.select(results_table).append('tr');
      var label_cell = table_row.append('td')
      label_cell.html('ID: ' + hit.subject_id +
        '
Def: ' + hit.subject_def);
      self._display_graph(iteration, hit, table_row);
    });
  });
}
<|endoftext|>


<commit_before>function formatAddress (pptqObject) {
    return ['city', 'region', 'country'].map(function (level) {
        return pptqObject[level];
    }).filter(function (term) {
        return term !== undefined && term.length > 0;
    }).join(', ');
}

var columnKeys = [
    'startDate',
    'format',
    'venueName',
    'venueAddress',
    'city',
    'region',
    'country',
    'email'
];

// Date objects
var now = new Date();
var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

/** Object definitions **/
function Event (pptq, app, i) {
    var key;
    for (var k in columnKeys) {
        key = columnKeys[k];
        if (key == 'startDate') {
            this.startDate = new Date(Date.parse(pptq.startDate));
        } else {
            this[key] = pptq[key];
        }
    }
    this.app = app;
    this.location = pptq.location;
    this.index = i;
    // XXX: don't make table
    //this.rowElement = this.createRow(pptq);
}

/* Create row element for event to add to table */
Event.prototype.createRow = function (pptq) {
    var row = document.createElement('tr');
    row.setAttribute('data-pptq-event', this);
    _.each(columnKeys, function (colKey) {
        var cell = document.createElement('td');
        var node;
        cell.setAttribute('data-column', colKey);
        if (colKey == 'email') {
            node = document.createElement('a');
            node.href = 'mailto:' + pptq[colKey];
            node.innerText = 'Email';
            cell.style['text-align'] = 'center';
            cell.appendChild(node);
        } else if (colKey == 'startDate') {
            cell.innerText = this.dateString();
        } else {
            cell.innerText = pptq[colKey];
        }
        row.appendChild(cell);
    }.bind(this));
    return row;
};

Event.prototype.hasLocation = function () {
    return typeof(this.location) !== 'undefined';
};

Event.prototype.setMarker = function (map) {
    var loc;
    var latLng;
    if (!this.hasLocation()) { return; }

    // Check controller if this event is currently filtered
    if (!this.app.controller.showEvent(this)) { map = null; }

    if (typeof(this.marker) === 'undefined') {
        // Marker doesn't exist, create
        loc = this.location.geometry.location;
        latLng = new google.maps.LatLng(loc.lat, loc.lng);
        this.marker = new google.maps.Marker({
            position: latLng,
            map: map,
            title: this.venueName
        });
        this.infoWindow = new google.maps.InfoWindow({
            content: this.infoWindowContent()
        });
        google.maps.event.addListener(this.marker, 'click', function () {
            this.app.markerClicked(this)
        }.bind(this));
    } else if (map !== this.marker.getMap()) {
        // Marker exists, update map if different.
        // Convenient for map = null to hide.
        this.marker.setMap(map);
    }
};

Event.prototype.infoWindowContent = function () {
    var shell = document.createElement('div');
    var container = document.createElement('div');
    var header = document.createElement('h4');
    var address = document.createElement('h6');
    var details = document.createElement('h6');
    var emailContainer = document.createElement('p');
    var emailLink = document.createElement('a');
    header.innerText = this.venueName;
    if (typeof(this.location) !== 'undefined' &&
            typeof(this.location.formatted_address) !== 'undefined') {
        address.innerText = this.location.formatted_address;
    }
    details.innerText = this.format + ' - ' + this.dateString();
    emailContainer.innerText = 'Email: ';
    emailLink.href = 'mailto:' + this.email;
    emailLink.innerText = this.email;
    emailContainer.appendChild(emailLink);
    container.className = 'info-window';
    container.appendChild(header);
    if (address.innerText.length > 0) {
        container.appendChild(address);
    }
    container.appendChild(details);
    container.appendChild(emailContainer);
    shell.appendChild(container);
    return shell.innerHTML;
};

/* Get date string of format MM/DD/YY */
Event.prototype.dateString = function () {
    return (this.startDate.getMonth() + 1) + '/' +
        this.startDate.getDate() + '/' +
        (this.startDate.getYear() % 100);
};

Event.prototype.pastDate = function () {
    return this.startDate <= yesterday;
};

/** Control Form **/
function ControlForm (formElement, app) {
    this.app = app;
    this.element = formElement;
    this.element.onchange = this.updateFilter.bind(this);
    this.format = {};
    this.updateFilter();
}

/* Audit form, update filter values */
ControlForm.prototype.updateFilter = function () {
    _.each(this.element.querySelectorAll('input[type=checkbox]'), function (box) {
        this.format[box.getAttribute('value')] = box.checked;
    }.bind(this));
    this.app.refreshEvents();
};

ControlForm.prototype.showEvent = function (pptqEvent) {
    // Check by format
    if (!this.format[pptqEvent.format.toLowerCase()]) { return false; }

    // TODO: check by date range

    return true;
}

/** Grinder Object - Top level application object **/
function Grinder (mapElement, controlFormElement, tableElement) {
    // XXX: don't show table
    //this.table = tableElement;
    this.mapElement = mapElement;
    this.controller = new ControlForm(controlFormElement, this);
    this.jsonUrl = 'pptqmil15locations.json';
    this.showPastEvents = false;
    this.events = [];
    this.eventsCreated = false;
    this.fetchEvents();
    navigator.geolocation.getCurrentPosition(this.renderMap.bind(this));
}

/* Perform ajax request of JSON object of each PPTQ event */
Grinder.prototype.fetchEvents = function () {
    $.ajax(this.jsonUrl, {
        data: 'text/json',
        success: this.renderEvents.bind(this)
    });
};

/* Instantiate app's Google Map, called upon geolocation */
Grinder.prototype.renderMap = function (currentLocation) {
    var mapOptions = {
        center: {
            lat: currentLocation.coords.latitude,
            lng: currentLocation.coords.longitude
        },
        zoom: 8
    };
    this.map = new google.maps.Map(this.mapElement, mapOptions);

    // Show all markers
    _.each(this.events, function (pptqEvent) {
        pptqEvent.setMarker(this.map);
    }.bind(this));
};

/* Parse PPTQ event JSON, create each Event object */
Grinder.prototype.renderEvents = function (data) {
    _.each(data.pptqs, function (pptq, i) {
        // Only show upcoming or current events
        var pptqEvent = new Event(pptq, this, i);
        this.events.push(pptqEvent);
        if (this.showPastEvents === false && pptqEvent.pastDate()) {
            return;
        }
        // XXX: don't make table
        //this.table.appendChild(pptqEvent.rowElement);
    }.bind(this));
    this.eventsCreated = true;
    _.each(this.events, function (pptqEvent) {
        pptqEvent.setMarker(this.map);
    }.bind(this));
};

/* Update showing of each event marker */
Grinder.prototype.refreshEvents = function () {
    _.each(this.events, function (pptqEvent) {
        pptqEvent.setMarker(this.map);
    }.bind(this));
};

/* Callback when marker is clicked.
 * Only show one info window at a time, and hide info window when
 * marker is clicked when its info window is already showing. */
Grinder.prototype.markerClicked = function (clickedEvent) {
    var previousEvent = this.activeEvent;
    var eventActivated = false;

    if (typeof(this.activeEvent) === 'undefined') {
        this.activeEvent = clickedEvent;
        eventActivated = true;
    } else {
        if (previousEvent !== clickedEvent) {
            // Different event activated
            this.activeEvent = clickedEvent;
            eventActivated = true;
        } else {
            // Active event re-clicked. Close.
            this.activeEvent = undefined;
        }
        previousEvent.infoWindow.close();
    }

    if (eventActivated === true) {
        clickedEvent.infoWindow.open(this.map, clickedEvent.marker);
    }
};

(function ($) {
    var app = new Grinder(
        document.getElementById('map-container'),
        document.getElementById('controller'));
})(jQuery);
<commit_msg>Clean some things up. Still not ideal, but functional<commit_after>var columnKeys = [
    'startDate',
    'format',
    'venueName',
    'venueAddress',
    'city',
    'region',
    'country',
    'email'
];

// Date objects
var now = new Date();
var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

/** Object definitions **/
function Event (pptq, app, i) {
    var key;
    for (var k in columnKeys) {
        key = columnKeys[k];
        if (key == 'startDate') {
            this.startDate = new Date(Date.parse(pptq.startDate));
        } else {
            this[key] = pptq[key];
        }
    }
    this.app = app;
    this.location = pptq.location;
    this.index = i;
    // XXX: don't make table
    //this.rowElement = this.createRow(pptq);
    this.createMarker();
}

// XXX: Don't show tables
// /* Create row element for event to add to table */
// Event.prototype.createRow = function (pptq) {
//     var row = document.createElement('tr');
//     row.setAttribute('data-pptq-event', this);
//     _.each(columnKeys, function (colKey) {
//         var cell = document.createElement('td');
//         var node;
//         cell.setAttribute('data-column', colKey);
//         if (colKey == 'email') {
//             node = document.createElement('a');
//             node.href = 'mailto:' + pptq[colKey];
//             node.innerText = 'Email';
//             cell.style['text-align'] = 'center';
//             cell.appendChild(node);
//         } else if (colKey == 'startDate') {
//             cell.innerText = this.dateString();
//         } else {
//             cell.innerText = pptq[colKey];
//         }
//         row.appendChild(cell);
//     }.bind(this));
//     return row;
// };

Event.prototype.hasLocation = function () {
    return typeof(this.location) !== 'undefined';
};

/* Create map marker for event (if location available) */
Event.prototype.createMarker = function () {
    var loc;
    var latLng;
    var map = this.app.map || null;
    if (!this.hasLocation()) { return; }

    loc = this.location.geometry.location;
    latLng = new google.maps.LatLng(loc.lat, loc.lng);
    this.marker = new google.maps.Marker({
        position: latLng,
        map: map,
        title: this.venueName
    });
    this.infoWindow = new google.maps.InfoWindow({
        content: this.infoWindowContent()
    });
    google.maps.event.addListener(this.marker, 'click', function () {
        this.app.markerClicked(this)
    }.bind(this));
}

/* Set event's marker visibility according to filter state */
Event.prototype.updateMarker = function () {
    if (typeof(this.marker) === 'undefined') { return; }
    if (this.app.controller.eventVisible(this)) {
        if (!this.marker.getMap()) {
            this.marker.setMap(this.app.map);
        }
    } else {
        if (this.marker.getMap()) {
            this.marker.setMap(null);
        }
    }
}

Event.prototype.setMarker = function (map) {
    var loc;
    var latLng;
    if (!this.hasLocation()) { return; }

    // Check controller if this event is currently filtered
    if (!this.app.controller.showEvent(this)) { map = null; }

    if (typeof(this.marker) === 'undefined') {
        // Marker doesn't exist, create
        loc = this.location.geometry.location;
        latLng = new google.maps.LatLng(loc.lat, loc.lng);
        this.marker = new google.maps.Marker({
            position: latLng,
            map: map,
            title: this.venueName
        });
        this.infoWindow = new google.maps.InfoWindow({
            content: this.infoWindowContent()
        });
        google.maps.event.addListener(this.marker, 'click', function () {
            this.app.markerClicked(this)
        }.bind(this));
    } else if (map !== this.marker.getMap()) {
        // Marker exists, update map if different.
        // Convenient for map = null to hide.
        this.marker.setMap(map);
    }
};

Event.prototype.infoWindowContent = function () {
    var shell = document.createElement('div');
    var container = document.createElement('div');
    var header = document.createElement('h4');
    var address = document.createElement('h6');
    var details = document.createElement('h6');
    var emailContainer = document.createElement('p');
    var emailLink = document.createElement('a');
    header.innerText = this.venueName;
    if (typeof(this.location) !== 'undefined' &&
            typeof(this.location.formatted_address) !== 'undefined') {
        address.innerText = this.location.formatted_address;
    }
    details.innerText = this.format + ' - ' + this.dateString();
    emailContainer.innerText = 'Email: ';
    emailLink.href = 'mailto:' + this.email;
    emailLink.innerText = this.email;
    emailContainer.appendChild(emailLink);
    container.className = 'info-window';
    container.appendChild(header);
    if (address.innerText.length > 0) {
        container.appendChild(address);
    }
    container.appendChild(details);
    container.appendChild(emailContainer);
    shell.appendChild(container);
    return shell.innerHTML;
};

/* Get date string of format MM/DD/YY */
Event.prototype.dateString = function () {
    return (this.startDate.getMonth() + 1) + '/' +
        this.startDate.getDate() + '/' +
        (this.startDate.getYear() % 100);
};

Event.prototype.pastDate = function () {
    return this.startDate <= yesterday;
};

/** Control Form **/
function ControlForm (formElement, app) {
    this.app = app;
    this.element = formElement;
    this.element.onchange = this.updateFilter.bind(this);
    this.format = {};
    this.updateFilter();
}

/* Audit form, update filter values */
ControlForm.prototype.updateFilter = function () {
    _.each(this.element.querySelectorAll('input[type=checkbox]'), function (box) {
        this.format[box.getAttribute('value')] = box.checked;
    }.bind(this));
    this.app.refreshEvents();
};

// Based on form state, determine if event's marker should be shown
ControlForm.prototype.eventVisible = function (pptqEvent) {
    // Check by format
    if (!this.format[pptqEvent.format.toLowerCase()]) { return false; }

    // TODO: check by date range

    return true;
}

/** Grinder Object - Top level application object **/
function Grinder (mapElement, controlFormElement, tableElement) {
    // XXX: don't show table
    //this.table = tableElement;
    this.mapElement = mapElement;
    this.controller = new ControlForm(controlFormElement, this);
    this.jsonUrl = 'pptqmil15locations.json';
    this.showPastEvents = false;
    this.events = [];
    this.eventsCreated = false;

    this.fetchEvents();
    navigator.geolocation.getCurrentPosition(this.renderMap.bind(this));
}

/* Perform ajax request of JSON object of each PPTQ event */
Grinder.prototype.fetchEvents = function () {
    $.ajax(this.jsonUrl, {
        data: 'text/json',
        success: this.loadEvents.bind(this)
    });
};

/* Instantiate app's Google Map, called upon geolocation */
Grinder.prototype.renderMap = function (currentLocation) {
    var mapOptions = {
        center: {
            lat: currentLocation.coords.latitude,
            lng: currentLocation.coords.longitude
        },
        zoom: 8
    };
    this.map = new google.maps.Map(this.mapElement, mapOptions);
    this.refreshEvents();
};

/* Parse PPTQ event JSON, create each Event object */
Grinder.prototype.loadEvents = function (data) {
    _.each(data.pptqs, function (pptq, i) {
        // Only show upcoming or current events
        var pptqEvent = new Event(pptq, this, i);
        this.events.push(pptqEvent);
        if (this.showPastEvents === false && pptqEvent.pastDate()) {
            return;
        }
        // XXX: don't make table
        //this.table.appendChild(pptqEvent.rowElement);
    }.bind(this));
    this.eventsCreated = true;
    this.refreshEvents();
};

/* Update showing of each event marker */
Grinder.prototype.refreshEvents = function () {
    // Bail if both map and events aren't ready
    if (!(this.eventsCreated && typeof(this.map) !== 'undefined')) { return; }

    _.each(this.events, function (pptqEvent) {
        pptqEvent.updateMarker();
    }.bind(this));
};

/* Callback when marker is clicked.
 * Only show one info window at a time, and hide info window when
 * marker is clicked when its info window is already showing. */
Grinder.prototype.markerClicked = function (clickedEvent) {
    var previousEvent = this.activeEvent;
    var eventActivated = false;

    if (typeof(this.activeEvent) === 'undefined') {
        this.activeEvent = clickedEvent;
        eventActivated = true;
    } else {
        if (previousEvent !== clickedEvent) {
            // Different event activated
            this.activeEvent = clickedEvent;
            eventActivated = true;
        } else {
            // Active event re-clicked. Close.
            this.activeEvent = undefined;
        }
        previousEvent.infoWindow.close();
    }

    if (eventActivated === true) {
        clickedEvent.infoWindow.open(this.map, clickedEvent.marker);
    }
};

(function ($) {
    var app = new Grinder(
        document.getElementById('map-container'),
        document.getElementById('controller'));
})(jQuery);
<|endoftext|>


<commit_before>/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Loads into global some ES6 capabilities not provided by DynJS

// Add String.prototype.startsWith
if (!String.prototype.startsWith) {
  Object.defineProperty(String.prototype, 'startsWith', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: function (searchString, position) {
      position = position || 0;
      return this.lastIndexOf(searchString, position) === position;
    }
  });
}

// Add String.prototype.endsWith
if (!String.prototype.endsWith) {
  Object.defineProperty(String.prototype, 'endsWith', {
    value: function (searchString, position) {
      var subjectString = this.toString();
      if (position === undefined || position > subjectString.length) {
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.indexOf(searchString, position);
      return lastIndex !== -1 && lastIndex === position;
    }
  });
}
<commit_msg>Add String.prototype.includes polyfill<commit_after>/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Loads into global some ES6 capabilities not provided by DynJS

// Add String.prototype.startsWith
if (!String.prototype.startsWith) {
  Object.defineProperty(String.prototype, 'startsWith', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: function (searchString, position) {
      position = position || 0;
      return this.lastIndexOf(searchString, position) === position;
    }
  });
}

// Add String.prototype.endsWith
if (!String.prototype.endsWith) {
  Object.defineProperty(String.prototype, 'endsWith', {
    value: function (searchString, position) {
      var subjectString = this.toString();
      if (position === undefined || position > subjectString.length) {
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.indexOf(searchString, position);
      return lastIndex !== -1 && lastIndex === position;
    }
  });
}

// Add String.prototype.includes
if (!String.prototype.includes) {
  String.prototype.includes = function() {'use strict';
    return String.prototype.indexOf.apply(this, arguments) !== -1;
  };
}
<|endoftext|>


<commit_before>import React from 'react';

import NavMore from './components.nav-more';

class Header extends React.Component {
  render() {
    return (

Chats

Contacts

        <NavMore/>

    );
  }

  // functions.
}

export default Header;

// if (document.querySelector('.s-header') && document.querySelector('.js-trigger')) {
//   let trigger = document.querySelector('.js-trigger'),
//       header = document.querySelector('.s-header'),
//       navChat = document.querySelector('.js-nav-main-chat'),
//       logo = document.querySelector('.s-header .icon .logo'),
//       backButton = document.querySelector('.js-back-button');

//   trigger.addEventListener('click', function (e) {
//     e.preventDefault();
//     header.classList.toggle('state-chat');
//     navChat.classList.toggle('state-active');
//     logo.classList.toggle('state-active');
//     backButton.classList.toggle('state-active');
//     this.classList.toggle('state-active');
//   });

//   backButton.addEventListener('click', function (e) {
//     e.preventDefault();
//     trigger.classList.toggle('state-active');
//     header.classList.toggle('state-chat');
//     navChat.classList.toggle('state-active');
//     logo.classList.toggle('state-active');
//     this.classList.toggle('state-active');
//   });
// }<commit_msg>add chat state to header component<commit_after>import React from 'react';

import NavMore from './components.nav-more';

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      chatActive: true
    };
  }

  render() {
    let navigation;
    let headerClass;
    let logoClass;
    let backButtonClass;

    if (this.state.chatActive) {
      navigation = <NavChat/>;
      headerClass = 's-header state-chat';
      logoClass = 'logo';
      backButtonClass = 'back-button state-active';
    } else {
      navigation = <NavMain/>;
      headerClass = 's-header';
      logoClass = 'logo state-active';
      backButtonClass = 'back-button';
    }

    return (

        {navigation}

        <NavMore/>

    );
  }

  closeConversation() {
    this.setState({
      chatActive: false
    });
  }
}

class NavMain extends React.Component {
  render() {
    return (

Chats

Contacts

    );
  }
}

class NavChat extends React.Component {
  render() {
    return (

    );
  }
}

export default Header;

// if (document.querySelector('.s-header') && document.querySelector('.js-trigger')) {
//   let trigger = document.querySelector('.js-trigger'),
//       header = document.querySelector('.s-header'),
//       navChat = document.querySelector('.js-nav-main-chat'),
//       logo = document.querySelector('.s-header .icon .logo'),
//       backButton = document.querySelector('.js-back-button');

//   trigger.addEventListener('click', function (e) {
//     e.preventDefault();
//     header.classList.toggle('state-chat');
//     navChat.classList.toggle('state-active');
//     logo.classList.toggle('state-active');
//     backButton.classList.toggle('state-active');
//     this.classList.toggle('state-active');
//   });

//   backButton.addEventListener('click', function (e) {
//     e.preventDefault();
//     trigger.classList.toggle('state-active');
//     header.classList.toggle('state-chat');
//     navChat.classList.toggle('state-active');
//     logo.classList.toggle('state-active');
//     this.classList.toggle('state-active');
//   });
// }<|endoftext|>


<commit_before>gruppo.innerHTML = "
14/06/2017 - Badge 1h di navigazione
Ora anche per i corsisti del gruppo B e C è possibile ottenere un Badge per un'ora di navigazione. Presto arriverà anche quello per 3 ore di navigazione, mentre quello per le 2 ore è già possibile ottenerlo. A breve dovrebbe essere disponibile il materiale del prof. Frolli sull'autismo."
gruppo.innerHTML += ""
gruppo.innerHTML += "
Badge Navigatore
Ora si possono ottenere il Badge Navigatore (per 2 ore di navigazione) e il Badge Internauta 3h (per 3 ore di navigazione). Già disponibili per il gruppo A e presto anche per il gruppo B e C."
gruppo.innerHTML += "
4/6/2017 - Presenze online
Sono state aggiornate le presenze online. Molti avevano notato che il registro delle presenze online non si aggiornava da un po' di tempo a questa parte. Da ieri è stato aggiornato. Ricordate, comunque, che le ore segnalate sul registro servono solo in piccola parte a completare la parte online. Alla fine dovrete ottenere circa 15 badge. In base alle ore di presenze online, riceverete alcuni badge, mentre altri verranno conseguiti con le attività che stanno per arrivare a breve."
gruppo.innerHTML += "
31/05/2017
Nuovo Badge Internauta per chi ha navigato per almeno un'ora in piattaforma. Per ora è disponibile solo per il gruppo A, ma arriverà presto anche per gli altri due gruppi."
gruppo.innerHTML += "
31/05/2017
Anche per il gruppo C è stato attivato il Badge DSA e PDP. Per gli altri corsi era già attivo. Per ora 7 corsisti lo hanno già ottenuto prendendo visione dei materiali. Siamo così arrivati a 8 badge. Siamo oltre la metà di quelli previsti per completare la parte on line del corso. Il badge bullismo è stato conseguito da più corsisti del gruppo C (19). Per il gruppo B invece è il Badge DOC quello più conseguito (per il gruppo C il Badge si chiama Badge lettore). Per il gruppo A, ben 31 corsisti hanno ottenuto il Badge Forum."
gruppo.innerHTML += "
30/05/2017
Nuovo Badge. Badge Operatori Scuola e Sanità che si ottiene prendendo visione dei materiali della dott.ssa Galzerano. Il badge è disponibile per tutti e tre i corsi."
gruppo.innerHTML += "
29/05/2017
Nuovi materiali aggiunti alla sezione della dott.ssa Quaranta su DSA e PDP. Chi visualizza questi materiali otterrà il nuovo Badge DSA-PDP. Siamo arrivati così al settimo badge del corso. Per il gruppo A è stato aggiunto il Badge DOC, già abilitato per i gruppi A e C (per quest'ultimo il nome del badge è Badge lettore). Andate nel menu a sinistra alla voce Badge per leggere quali documenti visionare o scaricare per ottenere questo badge. Prossimamente arriverà il Badge per la sezione della prof.ssa Galzerano."
gruppo.innerHTML +="
28/05/2017 - Nuovo Badge autismo
Aggiunto il badge autismo per chi prende visione dei materiali sull'autismo allegati nella sezione dell'incontro del 31 maggio."
gruppo.innerHTML +="
26/5/2017 - Nuovo Badge
Aggiunto un nuovo Badge: il Badge Navigatore. Si ottiene navigando nella piattaforma on line del corso per referenti di sostegno. Il tempo trascorso on line lo potete vedere nel registro presenze online."
gruppo.innerHTML += "

"
gruppo.innerHTML += "

"
<commit_msg>Update gruppoc.js<commit_after>gruppo.innerHTML = "
30/06/2017
Aggiunto un video alla sezione DSA e PDP sulla dislessia."
gruppo.innerHTML += "
14/06/2017 - Badge 1h di navigazione
Ora anche per i corsisti del gruppo B e C è possibile ottenere un Badge per un'ora di navigazione. Presto arriverà anche quello per 3 ore di navigazione, mentre quello per le 2 ore è già possibile ottenerlo. A breve dovrebbe essere disponibile il materiale del prof. Frolli sull'autismo."
gruppo.innerHTML += ""
gruppo.innerHTML += "
Badge Navigatore
Ora si possono ottenere il Badge Navigatore (per 2 ore di navigazione) e il Badge Internauta 3h (per 3 ore di navigazione). Già disponibili per il gruppo A e presto anche per il gruppo B e C."
gruppo.innerHTML += "
4/6/2017 - Presenze online
Sono state aggiornate le presenze online. Molti avevano notato che il registro delle presenze online non si aggiornava da un po' di tempo a questa parte. Da ieri è stato aggiornato. Ricordate, comunque, che le ore segnalate sul registro servono solo in piccola parte a completare la parte online. Alla fine dovrete ottenere circa 15 badge. In base alle ore di presenze online, riceverete alcuni badge, mentre altri verranno conseguiti con le attività che stanno per arrivare a breve."
gruppo.innerHTML += "
31/05/2017
Nuovo Badge Internauta per chi ha navigato per almeno un'ora in piattaforma. Per ora è disponibile solo per il gruppo A, ma arriverà presto anche per gli altri due gruppi."
gruppo.innerHTML += "
31/05/2017
Anche per il gruppo C è stato attivato il Badge DSA e PDP. Per gli altri corsi era già attivo. Per ora 7 corsisti lo hanno già ottenuto prendendo visione dei materiali. Siamo così arrivati a 8 badge. Siamo oltre la metà di quelli previsti per completare la parte on line del corso. Il badge bullismo è stato conseguito da più corsisti del gruppo C (19). Per il gruppo B invece è il Badge DOC quello più conseguito (per il gruppo C il Badge si chiama Badge lettore). Per il gruppo A, ben 31 corsisti hanno ottenuto il Badge Forum."
gruppo.innerHTML += "
30/05/2017
Nuovo Badge. Badge Operatori Scuola e Sanità che si ottiene prendendo visione dei materiali della dott.ssa Galzerano. Il badge è disponibile per tutti e tre i corsi."
gruppo.innerHTML += "
29/05/2017
Nuovi materiali aggiunti alla sezione della dott.ssa Quaranta su DSA e PDP. Chi visualizza questi materiali otterrà il nuovo Badge DSA-PDP. Siamo arrivati così al settimo badge del corso. Per il gruppo A è stato aggiunto il Badge DOC, già abilitato per i gruppi A e C (per quest'ultimo il nome del badge è Badge lettore). Andate nel menu a sinistra alla voce Badge per leggere quali documenti visionare o scaricare per ottenere questo badge. Prossimamente arriverà il Badge per la sezione della prof.ssa Galzerano."
gruppo.innerHTML +="
28/05/2017 - Nuovo Badge autismo
Aggiunto il badge autismo per chi prende visione dei materiali sull'autismo allegati nella sezione dell'incontro del 31 maggio."
gruppo.innerHTML +="
26/5/2017 - Nuovo Badge
Aggiunto un nuovo Badge: il Badge Navigatore. Si ottiene navigando nella piattaforma on line del corso per referenti di sostegno. Il tempo trascorso on line lo potete vedere nel registro presenze online."
gruppo.innerHTML += "

"
gruppo.innerHTML += "

"
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:Pear
 * @description
 * # Pear service
 * Provides controllers with a data model for pear to pear app
 * It serves as an abstraction between Pear data and backend (SwellRT)
 */

angular.module('Pear2Pear')
  .factory('pear', [
           '$rootScope', 'swellRT', '$q', '$timeout', 'base64',
           function($rootScope, swellRT, $q, $timeout, base64) {

    var proxy = {
    };

    // FIXME model prototype
    var urlId = function(id) {
      if (id === undefined) { return ''; }

      return base64.encode(id);
    };

    // map of opened projects
    var openedProjects = {};
    // map of opened communities
    var openedCommunities = {};

    var def = $q.defer();

    function makeModelPublic(model){
      model.addParticipant('@' + SwellRTConfig.swellrtServerDomain,
                           null,
                           function(err){
                             console.log('ERROR: ' + err);
                           }
                          );
    }

    var communities = {

      all: function() {

        var communities = $q.defer();
        var foundCommunities = $q.defer();
        var foundProjectNumbers = $q.defer();

        var queries = $q.all([
          foundCommunities.promise,
          foundProjectNumbers.promuse
        ]);

        var comms = {};
        var nums = {};

        SwellRT.query(
          {
            'root.type': 'community'
          },
          function(result){
            angular.forEach(result.result, function(val) {
              comms[val.root.id] = val.root;
            });

            foundCommunities.resolve(comms);
          },
          function(e){
            foundCommunities.reject(e);
          }
        );
        SwellRT.query(
          {_aggregate:
           [{$match: {
             'root.type': 'project',
             'root.shareMode': 'public'
           }},
            {$unwind: '$root.communities'},
            {$group :
             {_id:'$root.communities',
              number: { $sum : 1 }
             }
            }]},
          function(result){
            nums = result.result;
            foundProjectNumbers.resolve(nums);
          },
          function(e){
            foundCommunities.reject(e);
          }
        );

        queries.then(function(){
          angular.forEach(nums, function(val){
            comms[val._id].numProjects = val.number;
          });
          communities.resolve(comms);

        });
        return communities.promise;
      },

      find: function(urlId) {

        var id = base64.decode(urlId);
        var comDef = $q.defer();
        var community = comDef.promise;

        if (!openedCommunities[id]) {
          openedCommunities[id] = comDef.promise;
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            comDef.resolve(pr);
          }, function(error){
            console.log(error);
            comDef.reject(error);
          });
        } else {
          openedCommunities[id].then(
            function(r){
              comDef.resolve(r);
            });
        }

        var allSnapshot = function(){
              var foundProjects = $q.defer();
              SwellRT.query(
                {
                  'root.type': 'project',
                  'root.communities': id,
                  $and: [{
                    $or: [
                      {'root.shareMode': 'public'},
                      {'root.supporters': users.current()},
                      {'root.contributors': users.current()}
                    ]
                  }]
                },
                function(result){
                  var projs = [];
                  angular.forEach(result.result, function(val) {
                    projs.push(val.root);
                  });
                 foundProjects.resolve(projs);
                },
                function(e){
                  foundProjects.reject(e);
                }
              );
              return foundProjects.promise;
      };
      return {
          community: comDef.promise,
          projects: {

            allSnapshot: allSnapshot,

            all: function() {
              var projsDef = $q.defer();
              allSnapshot().then(function(all){
                var promises = {};
                if (all.length > 0){
                  angular.forEach(all, function(val){
                    var projDef = $q.defer();
                    promises[val.id] = projDef.promise;
                    projects.find(base64.encode(val.id)).then(function(model){
                      $timeout(function(){
                        projDef.resolve(model);
                      });
                    });
                  });
                  projsDef.resolve($q.all(promises));
                } else {
                  projsDef.resolve([]);
                }
              });

              return projsDef.promise;
            },
            destroy: function(projId){
              var i = community.projects.indexOf(projId);
              if (i > -1){
                community.projects.splice(i,1);
              }
            }
          }
        };
      },
      create: function(data, callback) {
        var id = window.SwellRT.createModel(function(model){

          makeModelPublic(model);
          var p = swellRT.proxy(model);

          $timeout(function(){
            p.type = 'community';
            p.name = data.name;
            p.id = id;
            p.projects = [];
            callback(p);
          });
        });
      },
      destroy: function(urlId) {
        var id = base64.decode(urlId);
        // currently not supported by SwellRT
        return urlId;
      }

    };

    var findProjects = function(urlId) {
        var id = base64.decode(urlId);
        var def = $q.defer();
        if (!openedProjects[id]) {
          openedProjects[id] = def.promise;
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            def.resolve(pr);
          }, function(error){
            console.log(error);
            def.reject(error);
          });
        } else {
          openedProjects[id].then(
            function(r){
              def.resolve(r);
            });
        }
        return def.promise;
    };
    var projects = {

      find: findProjects,

      create: function(callback, communityId) {
        var id = window.SwellRT.createModel(function(model){

          makeModelPublic(model);
          var proxyProj = swellRT.proxy(model);
          $timeout(function(){
            proxyProj.type = 'project';
            proxyProj.communities = (communityId) ? [communityId] : [];
            proxyProj.id = id;
            proxyProj.title = '';
            proxyProj.chat = [];
            proxyProj.pad = new swellRT.TextObject();
            proxyProj.needs = [];
            proxyProj.promoter = users.current();
            proxyProj.supporters = [];
            proxyProj.contributors = [users.current()];
            proxyProj.shareMode = 'link';
            var d = $q.defer();
            d.resolve(proxyProj);
            openedProjects[id] = d.promise;
            callback(proxyProj);
          });
        });
      },

      setShareMode: function(projId, shareMode){
        projects.find(projId).then(
          function(project){
            $timeout(function(){
              project.shareMode = shareMode;
            });
          }
        );
      },

      addContributor: function(projId, user){
        if (!user){
          user = users.current();
        }
        console.log('user',user);
        projects.find(projId).then(
          function(p){
            if (user && p.contributors.indexOf(user) < 0){
              p.contributors.push(user);
            }
          });
      }
    };

    var users = {
      current: function() {
        return window.sessionStorage.getItem('userId');
      },
      setCurrent: function(name) {
        var cleanedName = name ? name.trim() : name;

        return window.sessionStorage.setItem('userId', cleanedName);
      },
      isCurrent: function(user) {
        return user === users.current();
      },
      loggedIn: function() {
        return users.current() !== 'undefined' && users.current() !== null;
      }
    };

    var addChatMessage = function(projectId, message) {
      projects.find(projectId).then(function(project){
        project.chat.push({
          text: message,
          who: users.current(),
          time: (new Date()).toJSON()
        });
        projects.addContributor(projectId);
      }, function(error){
        console.log(error);
      });
    };

    var toggleSupport = function(projectId) {
      projects.find(base64.encode(projectId)).then(
        function(model){
          var index = model.supporters.indexOf(users.current());

          $timeout(function(){
            if (index > -1) {
              model.supporters.splice(index, 1);
            } else {
              model.supporters.push(users.current());
            }
          });
        },
        function(error){
          console.log(error);
        }
      );
    };

    var registerUser = function(userName, password, onSuccess, onError){
      window.SwellRT.registerUser(SwellRTConfig.server, userName, password, onSuccess, onError);
    };

    var startSession = function(userName, password, onSuccess, onError){

      // if login with user Name, close previous anonimous session
      if (userName){
        window.SwellRT.stopSession();
      }
      window.SwellRT.startSession(
        SwellRTConfig.server, userName || SwellRT.user.ANONYMOUS, password || '',
        function(){
          SwellRTConfig.swellrtServerDomain = __session.domain;
          SwellRT.on(SwellRT.events.NETWORK_CONNECTED, onSuccess);
        }, onError);
    };

    window.onSwellRTReady = function () {
      startSession(null, null, function(){
          $timeout(
            function() {
              communities.all();
              def.resolve(SwellRT);
            });
        },
        function(error) {
          console.log(error);
        });
      // to avoid multiple calls
      window.onSwellRTReady = null;
    };

    if (window.SwellRT && typeof window.onSwellRTReady === 'function') {
      window.onSwellRTReady();
    }

    return {
      communities: communities,
      projects: projects,
      users: users,
      urlId: urlId,
      addChatMessage: addChatMessage,
      toggleSupport: toggleSupport,
      startSession: startSession,
      registerUser: registerUser,
      onLoad: function(f) {
        def.promise.then(f);
      }
    };
  }]);
<commit_msg>fix typo, proyect numbers are now well displayed<commit_after>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:Pear
 * @description
 * # Pear service
 * Provides controllers with a data model for pear to pear app
 * It serves as an abstraction between Pear data and backend (SwellRT)
 */

angular.module('Pear2Pear')
  .factory('pear', [
           '$rootScope', 'swellRT', '$q', '$timeout', 'base64',
           function($rootScope, swellRT, $q, $timeout, base64) {

    var proxy = {
    };

    // FIXME model prototype
    var urlId = function(id) {
      if (id === undefined) { return ''; }

      return base64.encode(id);
    };

    // map of opened projects
    var openedProjects = {};
    // map of opened communities
    var openedCommunities = {};

    var def = $q.defer();

    function makeModelPublic(model){
      model.addParticipant('@' + SwellRTConfig.swellrtServerDomain,
                           null,
                           function(err){
                             console.log('ERROR: ' + err);
                           }
                          );
    }

    var communities = {

      all: function() {

        var communities = $q.defer();
        var foundCommunities = $q.defer();
        var foundProjectNumbers = $q.defer();

        var queries = $q.all([
          foundCommunities.promise,
          foundProjectNumbers.promise
        ]);

        var comms = {};
        var nums = {};

        SwellRT.query(
          {
            'root.type': 'community'
          },
          function(result){
            angular.forEach(result.result, function(val) {
              comms[val.root.id] = val.root;
            });

            foundCommunities.resolve(comms);
          },
          function(e){
            foundCommunities.reject(e);
          }
        );
        SwellRT.query(
          {_aggregate:
           [{$match: {
             'root.type': 'project',
             'root.shareMode': 'public'
           }},
            {$unwind: '$root.communities'},
            {$group :
             {_id:'$root.communities',
              number: { $sum : 1 }
             }
            }]},
          function(result){
            nums = result.result;
            foundProjectNumbers.resolve(nums);
          },
          function(e){
            foundCommunities.reject(e);
          }
        );

        queries.then(function(){
          angular.forEach(nums, function(val){
            comms[val._id].numProjects = val.number;
          });
          communities.resolve(comms);

        });
        return communities.promise;
      },

      find: function(urlId) {

        var id = base64.decode(urlId);
        var comDef = $q.defer();
        var community = comDef.promise;

        if (!openedCommunities[id]) {
          openedCommunities[id] = comDef.promise;
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            comDef.resolve(pr);
          }, function(error){
            console.log(error);
            comDef.reject(error);
          });
        } else {
          openedCommunities[id].then(
            function(r){
              comDef.resolve(r);
            });
        }

        var allSnapshot = function(){
              var foundProjects = $q.defer();
              SwellRT.query(
                {
                  'root.type': 'project',
                  'root.communities': id,
                  $and: [{
                    $or: [
                      {'root.shareMode': 'public'},
                      {'root.supporters': users.current()},
                      {'root.contributors': users.current()}
                    ]
                  }]
                },
                function(result){
                  var projs = [];
                  angular.forEach(result.result, function(val) {
                    projs.push(val.root);
                  });
                 foundProjects.resolve(projs);
                },
                function(e){
                  foundProjects.reject(e);
                }
              );
              return foundProjects.promise;
      };
      return {
          community: comDef.promise,
          projects: {

            allSnapshot: allSnapshot,

            all: function() {
              var projsDef = $q.defer();
              allSnapshot().then(function(all){
                var promises = {};
                if (all.length > 0){
                  angular.forEach(all, function(val){
                    var projDef = $q.defer();
                    promises[val.id] = projDef.promise;
                    projects.find(base64.encode(val.id)).then(function(model){
                      $timeout(function(){
                        projDef.resolve(model);
                      });
                    });
                  });
                  projsDef.resolve($q.all(promises));
                } else {
                  projsDef.resolve([]);
                }
              });

              return projsDef.promise;
            },
            destroy: function(projId){
              var i = community.projects.indexOf(projId);
              if (i > -1){
                community.projects.splice(i,1);
              }
            }
          }
        };
      },
      create: function(data, callback) {
        var id = window.SwellRT.createModel(function(model){

          makeModelPublic(model);
          var p = swellRT.proxy(model);

          $timeout(function(){
            p.type = 'community';
            p.name = data.name;
            p.id = id;
            p.projects = [];
            callback(p);
          });
        });
      },
      destroy: function(urlId) {
        var id = base64.decode(urlId);
        // currently not supported by SwellRT
        return urlId;
      }

    };

    var findProjects = function(urlId) {
        var id = base64.decode(urlId);
        var def = $q.defer();
        if (!openedProjects[id]) {
          openedProjects[id] = def.promise;
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            def.resolve(pr);
          }, function(error){
            console.log(error);
            def.reject(error);
          });
        } else {
          openedProjects[id].then(
            function(r){
              def.resolve(r);
            });
        }
        return def.promise;
    };
    var projects = {

      find: findProjects,

      create: function(callback, communityId) {
        var id = window.SwellRT.createModel(function(model){

          makeModelPublic(model);
          var proxyProj = swellRT.proxy(model);
          $timeout(function(){
            proxyProj.type = 'project';
            proxyProj.communities = (communityId) ? [communityId] : [];
            proxyProj.id = id;
            proxyProj.title = '';
            proxyProj.chat = [];
            proxyProj.pad = new swellRT.TextObject();
            proxyProj.needs = [];
            proxyProj.promoter = users.current();
            proxyProj.supporters = [];
            proxyProj.contributors = [users.current()];
            proxyProj.shareMode = 'link';
            var d = $q.defer();
            d.resolve(proxyProj);
            openedProjects[id] = d.promise;
            callback(proxyProj);
          });
        });
      },

      setShareMode: function(projId, shareMode){
        projects.find(projId).then(
          function(project){
            $timeout(function(){
              project.shareMode = shareMode;
            });
          }
        );
      },

      addContributor: function(projId, user){
        if (!user){
          user = users.current();
        }
        console.log('user',user);
        projects.find(projId).then(
          function(p){
            if (user && p.contributors.indexOf(user) < 0){
              p.contributors.push(user);
            }
          });
      }
    };

    var users = {
      current: function() {
        return window.sessionStorage.getItem('userId');
      },
      setCurrent: function(name) {
        var cleanedName = name ? name.trim() : name;

        return window.sessionStorage.setItem('userId', cleanedName);
      },
      isCurrent: function(user) {
        return user === users.current();
      },
      loggedIn: function() {
        return users.current() !== 'undefined' && users.current() !== null;
      }
    };

    var addChatMessage = function(projectId, message) {
      projects.find(projectId).then(function(project){
        project.chat.push({
          text: message,
          who: users.current(),
          time: (new Date()).toJSON()
        });
        projects.addContributor(projectId);
      }, function(error){
        console.log(error);
      });
    };

    var toggleSupport = function(projectId) {
      projects.find(base64.encode(projectId)).then(
        function(model){
          var index = model.supporters.indexOf(users.current());

          $timeout(function(){
            if (index > -1) {
              model.supporters.splice(index, 1);
            } else {
              model.supporters.push(users.current());
            }
          });
        },
        function(error){
          console.log(error);
        }
      );
    };

    var registerUser = function(userName, password, onSuccess, onError){
      window.SwellRT.registerUser(SwellRTConfig.server, userName, password, onSuccess, onError);
    };

    var startSession = function(userName, password, onSuccess, onError){

      // if login with user Name, close previous anonimous session
      if (userName){
        window.SwellRT.stopSession();
      }
      window.SwellRT.startSession(
        SwellRTConfig.server, userName || SwellRT.user.ANONYMOUS, password || '',
        function(){
          SwellRTConfig.swellrtServerDomain = __session.domain;
          SwellRT.on(SwellRT.events.NETWORK_CONNECTED, onSuccess);
        }, onError);
    };

    window.onSwellRTReady = function () {
      startSession(null, null, function(){
          $timeout(
            function() {
              communities.all();
              def.resolve(SwellRT);
            });
        },
        function(error) {
          console.log(error);
        });
      // to avoid multiple calls
      window.onSwellRTReady = null;
    };

    if (window.SwellRT && typeof window.onSwellRTReady === 'function') {
      window.onSwellRTReady();
    }

    return {
      communities: communities,
      projects: projects,
      users: users,
      urlId: urlId,
      addChatMessage: addChatMessage,
      toggleSupport: toggleSupport,
      startSession: startSession,
      registerUser: registerUser,
      onLoad: function(f) {
        def.promise.then(f);
      }
    };
  }]);
<|endoftext|>