<commit_before>var	ucd_version = '6.2.0',
	grid_elements = [],
	grid_base,
	current_cp,
	data_ready = false,
	data = null,
	data_defaults = {
		u16: function(cp) {
			return cp_char(cp).split('').map(function(x) {
				return ('0000' + x.charCodeAt(0).toString(16).
					toUpperCase()).slice(-4);
			}).join(' ');
		},
		u8: function(cp) {
			return unescape(encodeURIComponent(cp_char(cp))).
				split('').map(function(x) {
					return ('00' + x.charCodeAt(0).
						toString(16).toUpperCase()).
						slice(-2);
				}).join(' ');
		},
		ent: function(cp) {
			return '&#' + cp + ';';
		},
		name: '(unknown or unassigned)',
		block: '(unknown)',
		age: '(unknown)',
		gc: 'Unassigned (Cn)',
		mpy: '(not applicable)'
	};

function init_grid() {
	var row, cell, div;
	for (var i = 0; i < 256; i++) {
		if (i % 16 == 0) {
			row = $('<tr>');
			$('#grid').append(row);
		}
		cell = $('<td>');
		div = $('<div>');
		div.attr('id', 'g' + i);
		div.click(click_handler);
		cell.append(div);
		row.append(cell);
		grid_elements.push(div);
	}
}

function cp_char(cp) {
	if (cp > 0xffff) {
		cp -= 0x10000;
		return String.fromCharCode(
			0xd800 + (cp >> 10),
			0xdc00 + (cp & 0x3ff)
		);
	} else {
		return String.fromCharCode(cp);
	}
}

function cp_string(cp) {
	var s = cp.toString(16).toUpperCase();
	if (s.length < 5)
		s = ('000' + s).slice(-4);
	return 'U+' + s;
}

function update_grid() {
	grid_elements.forEach(function(e, i) {
		var cp = grid_base + i;
		e.text(cp_char(cp));
	});
}

function update_info() {
	var cp = current_cp;
	$('#cp').text(cp_string(cp));
	$('#big').text(cp_char(cp));
	if (!data_ready)
		return;
	for (var x in data_defaults)
		$('#data_' + x).text(get_data(cp, x));
}

function set_hash(cp) {
	location.hash = cp.toString(16);
}

function hashchange_handler() {
	var cp = parseInt(location.hash.substr(1), 16);
	if (isNaN(cp) || cp < 0 || cp > 0x10ffff)
		if (current_cp == undefined)
			return set_hash(0);
		else if (!location.hash.length)
			return;
		else
			return set_hash(current_cp);
	current_cp = cp;
	var new_grid_base = cp - cp % 256;
	if (new_grid_base != grid_base) {
		grid_base = new_grid_base;
		update_grid();
	}
	$('#goto_hex').val(cp.toString(16));
	$('#goto_dec').val(cp);
	$('#goto_char').val(cp_char(cp));
	$('#grid div').removeClass('selected');
	grid_elements[cp % 256].addClass('selected');
	update_info();
}

function click_handler() {
	var i = parseInt(this.id.substr(1));
	set_hash(grid_base + i);
}

function load_data() {
	$('#loading_noscript').hide();
	$('#loading_files').show();
	$.get('data.json', function(d) {
		data = d;
		data.forEach(function(o, i) {
			o.cp = i;
		});
		data_ready = true;
		$('#loading').hide();
		$('#ui').show();
		update_info();
	}, 'json');
}

function get_data(cp, prop) {
	if (!data[cp] || !data[cp][prop]) {
		var def = data_defaults[prop];
		if (def instanceof Function)
			return def(cp);
		else
			return def;
	}
	return data[cp][prop];
}

function set_data(cp, prop, value) {
	if (!data[cp])
		data[cp] = {};
	data[cp][prop] = value;
}

init_grid();
hashchange_handler();
load_data();

$(window).on('hashchange', hashchange_handler);
$(window).keydown(function(e) {
	if (e.metaKey || e.shiftKey || e.ctrlKey || e.altKey)
		return;
	switch (e.keyCode) {
	case 33: // page up
		set_hash(current_cp - 256); break;
	case 34: // page down
		set_hash(current_cp + 256); break;
	case 35: // end
		if (current_cp == grid_base + 255)
			set_hash(0x10ffff);
		else
			set_hash(grid_base + 255);
		break;
	case 36: // home
		if (current_cp == grid_base)
			set_hash(0);
		else
			set_hash(grid_base);
		break;
	case 37: // left arrow
		set_hash(current_cp - 1); break;
	case 38: // up arrow
		set_hash(current_cp - 16); break;
	case 39: // right arrow
		set_hash(current_cp + 1); break;
	case 40: // down arrow
		set_hash(current_cp + 16); break;
	}
});
$('input').keydown(function(e) {
	e.stopPropagation();
});
$('#ui_tabs a').click(function(e) {
	$('#ui_content > div').hide();
	$('#' + this.id.substr(4)).show();
	$('#ui_tabs a').removeClass('selected');
	$(this).addClass('selected');
	e.preventDefault();
});
$('#ucd_version').text(ucd_version);
$('#search_form, #search_han').on('submit change', function(e) {
	var q = $('#search_query').val();
	var sr = $('#search_results');
	if (!q.length)
		return;
	sr.empty();
	data.filter(function(o) {
		return (o.name || '').toLowerCase().indexOf(
			q.toLowerCase()) != -1;
	}).slice(0, 50).filter(function(o) {
		if (!$('#search_han').is(':checked') && o.han)
			return false;
		return true;
	}).forEach(function(o) {
		sr.append(
			$('<div>').
			text(cp_string(o.cp) + '\u2001' + o.name).
			click(function() {
				set_hash(o.cp);
			})
		);
	});
	e.preventDefault();
});
$('#goto_hex').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	set_hash(parseInt(this.value, 16));
});
$('#goto_dec').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	set_hash(parseInt(this.value, 10));
});
$('#goto_char').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	else if (this.value.length == 1)
		set_hash(this.value.charCodeAt(0));
	else
		set_hash(
			0x10000 +
			((this.value.charCodeAt(0) - 0xd800) << 10) +
			(this.value.charCodeAt(1) - 0xdc00)
		);
});
$('#goto_char').on('focus click', function() {
	// select entire box, even when there is only an invisible character
	// this prevents confusion when inputing in a seemingly empty box fails
	$(this).select();
});
<commit_msg>make use of document.title<commit_after>var	ucd_version = '6.2.0',
	grid_elements = [],
	grid_base,
	current_cp,
	data_ready = false,
	data = null,
	data_defaults = {
		u16: function(cp) {
			return cp_char(cp).split('').map(function(x) {
				return ('0000' + x.charCodeAt(0).toString(16).
					toUpperCase()).slice(-4);
			}).join(' ');
		},
		u8: function(cp) {
			return unescape(encodeURIComponent(cp_char(cp))).
				split('').map(function(x) {
					return ('00' + x.charCodeAt(0).
						toString(16).toUpperCase()).
						slice(-2);
				}).join(' ');
		},
		ent: function(cp) {
			return '&#' + cp + ';';
		},
		name: '(unknown or unassigned)',
		block: '(unknown)',
		age: '(unknown)',
		gc: 'Unassigned (Cn)',
		mpy: '(not applicable)'
	};

function init_grid() {
	var row, cell, div;
	for (var i = 0; i < 256; i++) {
		if (i % 16 == 0) {
			row = $('<tr>');
			$('#grid').append(row);
		}
		cell = $('<td>');
		div = $('<div>');
		div.attr('id', 'g' + i);
		div.click(click_handler);
		cell.append(div);
		row.append(cell);
		grid_elements.push(div);
	}
}

function cp_char(cp) {
	if (cp > 0xffff) {
		cp -= 0x10000;
		return String.fromCharCode(
			0xd800 + (cp >> 10),
			0xdc00 + (cp & 0x3ff)
		);
	} else {
		return String.fromCharCode(cp);
	}
}

function cp_string(cp) {
	var s = cp.toString(16).toUpperCase();
	if (s.length < 5)
		s = ('000' + s).slice(-4);
	return 'U+' + s;
}

function update_grid() {
	grid_elements.forEach(function(e, i) {
		var cp = grid_base + i;
		e.text(cp_char(cp));
	});
}

function update_info() {
	var cp = current_cp;
	$('#cp').text(cp_string(cp));
	$('#big').text(cp_char(cp));
	if (!data_ready)
		return;
	document.title = cp_string(cp) + ' ' + get_data(cp, 'name');
	for (var x in data_defaults)
		$('#data_' + x).text(get_data(cp, x));
}

function set_hash(cp) {
	location.hash = cp.toString(16);
}

function hashchange_handler() {
	var cp = parseInt(location.hash.substr(1), 16);
	if (isNaN(cp) || cp < 0 || cp > 0x10ffff)
		if (current_cp == undefined)
			return set_hash(0);
		else if (!location.hash.length)
			return;
		else
			return set_hash(current_cp);
	current_cp = cp;
	var new_grid_base = cp - cp % 256;
	if (new_grid_base != grid_base) {
		grid_base = new_grid_base;
		update_grid();
	}
	$('#goto_hex').val(cp.toString(16));
	$('#goto_dec').val(cp);
	$('#goto_char').val(cp_char(cp));
	$('#grid div').removeClass('selected');
	grid_elements[cp % 256].addClass('selected');
	update_info();
}

function click_handler() {
	var i = parseInt(this.id.substr(1));
	set_hash(grid_base + i);
}

function load_data() {
	$('#loading_noscript').hide();
	$('#loading_files').show();
	$.get('data.json', function(d) {
		data = d;
		data.forEach(function(o, i) {
			o.cp = i;
		});
		data_ready = true;
		$('#loading').hide();
		$('#ui').show();
		update_info();
	}, 'json');
}

function get_data(cp, prop) {
	if (!data[cp] || !data[cp][prop]) {
		var def = data_defaults[prop];
		if (def instanceof Function)
			return def(cp);
		else
			return def;
	}
	return data[cp][prop];
}

function set_data(cp, prop, value) {
	if (!data[cp])
		data[cp] = {};
	data[cp][prop] = value;
}

init_grid();
hashchange_handler();
load_data();

$(window).on('hashchange', hashchange_handler);
$(window).keydown(function(e) {
	if (e.metaKey || e.shiftKey || e.ctrlKey || e.altKey)
		return;
	switch (e.keyCode) {
	case 33: // page up
		set_hash(current_cp - 256); break;
	case 34: // page down
		set_hash(current_cp + 256); break;
	case 35: // end
		if (current_cp == grid_base + 255)
			set_hash(0x10ffff);
		else
			set_hash(grid_base + 255);
		break;
	case 36: // home
		if (current_cp == grid_base)
			set_hash(0);
		else
			set_hash(grid_base);
		break;
	case 37: // left arrow
		set_hash(current_cp - 1); break;
	case 38: // up arrow
		set_hash(current_cp - 16); break;
	case 39: // right arrow
		set_hash(current_cp + 1); break;
	case 40: // down arrow
		set_hash(current_cp + 16); break;
	}
});
$('input').keydown(function(e) {
	e.stopPropagation();
});
$('#ui_tabs a').click(function(e) {
	$('#ui_content > div').hide();
	$('#' + this.id.substr(4)).show();
	$('#ui_tabs a').removeClass('selected');
	$(this).addClass('selected');
	e.preventDefault();
});
$('#ucd_version').text(ucd_version);
$('#search_form, #search_han').on('submit change', function(e) {
	var q = $('#search_query').val();
	var sr = $('#search_results');
	if (!q.length)
		return;
	sr.empty();
	data.filter(function(o) {
		return (o.name || '').toLowerCase().indexOf(
			q.toLowerCase()) != -1;
	}).slice(0, 50).filter(function(o) {
		if (!$('#search_han').is(':checked') && o.han)
			return false;
		return true;
	}).forEach(function(o) {
		sr.append(
			$('<div>').
			text(cp_string(o.cp) + '\u2001' + o.name).
			click(function() {
				set_hash(o.cp);
			})
		);
	});
	e.preventDefault();
});
$('#goto_hex').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	set_hash(parseInt(this.value, 16));
});
$('#goto_dec').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	set_hash(parseInt(this.value, 10));
});
$('#goto_char').on('change keydown paste input', function() {
	if (this.value.length == 0)
		return;
	else if (this.value.length == 1)
		set_hash(this.value.charCodeAt(0));
	else
		set_hash(
			0x10000 +
			((this.value.charCodeAt(0) - 0xd800) << 10) +
			(this.value.charCodeAt(1) - 0xdc00)
		);
});
$('#goto_char').on('focus click', function() {
	// select entire box, even when there is only an invisible character
	// this prevents confusion when inputing in a seemingly empty box fails
	$(this).select();
});
<|endoftext|>


<commit_before>import {
  BaseModel, identifiedBy, field,
} from './base';
import { uniq, flatMap } from 'lodash';
import { action, computed } from 'mobx';
import { CurrentUserActions } from '../flux/current-user';

import Courses from './courses';

@identifiedBy('user')
export class User extends BaseModel {

  @action.bound
  bootstrap(data) {
    CurrentUserActions.loaded(data);
    this.update(data);
  }

  @field name;

  @field faculty_status;
  @field profile_url;

  @field is_admin;
  @field is_content_analyst;
  @field is_customer_service;

  @field({ type: 'array' }) viewed_tour_ids;

  @computed get tourAudienceTags() {
    return uniq(flatMap(Courses.values(), 'tourAudienceTags'));
  }

  replayTours() {
    // once we start saving the viewed tours we'll need two arrays
    // on for the "real" viewed state and one of the "available to view"
    this.viewed_tour_ids.clear();
  }

  viewedTour(tour) {
    // FIXME: SAVE event call goes here, or more likely it'll autorun whenever "viewed_tour_ids" updates
    this.viewed_tour_ids.push(tour.id);
  }
}

const currentUser = new User;

export default currentUser;
<commit_msg>save tour views to server<commit_after>import {
  BaseModel, identifiedBy, field,
} from './base';
import { uniq, flatMap } from 'lodash';
import { action, computed } from 'mobx';
import { CurrentUserActions } from '../flux/current-user';

import Courses from './courses';

@identifiedBy('user')
export class User extends BaseModel {

  @action.bound
  bootstrap(data) {
    CurrentUserActions.loaded(data);
    this.update(data);
  }

  @field name;

  @field faculty_status;
  @field profile_url;

  @field is_admin;
  @field is_content_analyst;
  @field is_customer_service;

  @field({ type: 'array' }) viewed_tour_ids;

  @computed get tourAudienceTags() {
    return uniq(flatMap(Courses.values(), 'tourAudienceTags'));
  }

  replayTours() {
    // once we start saving the viewed tours we'll need two arrays
    // on for the "real" viewed state and one of the "available to view"
    this.viewed_tour_ids.clear();
  }

  viewedTour(tour) {
    this.viewed_tour_ids.push(tour.id);
    this.saveTourView(tour); // saveTourView is method created by api
  }
}

const currentUser = new User;

export default currentUser;
<|endoftext|>


<commit_before>/*jshint multistr: true */

/*
 * Load Map
**/

// Load tiles
var world = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
    attribution: 'Esri',
    maxZoom: 9
});

var admin_bounds = L.tileLayer('http://openmapsurfer.uni-hd.de/tiles/adminb/x={x}&y={y}&z={z}', {
    attribution: 'OpenStreetMap',
    maxZoom: 9,
});

var wind = L.tileLayer('http://{s}.tile.openweathermap.org/map/wind/{z}/{x}/{y}.png', {
    attribution: 'OpenWeatherMap',
    opacity: 0.5
});

// Create map
var map = L.map('map', {
    layers: [world],
});
map.fitBounds([[28.11, -80.74], [42.20, -9.54]]); // Show whole north atlantic ocean at first

// Add GitHub page to the attribution
map.attributionControl.options.prefix += ' | Source Code';

// Add the optional layers to a selection control
L.control.layers(undefined, {
    "Administration Boundaries": admin_bounds,
    "Wind": wind
}).addTo(map);

// Load icons
var icons = {
    // past location
    point: L.icon({
        iconUrl: 'img/point.svg',
        iconSize: [9, 9]
    }),

    // current location
    boat: L.icon({
        iconUrl: 'img/boat.svg',
        iconSize: [16, 16]
    })
};

// Create data layer and add polyline to it
var polyline = L.polyline([], {
    color: 'white',
    opacity: 0.2,
    lineCap: 'butt',
    weight: 2
});
var data_layer = L.featureGroup([polyline]);

/*
 * Retrieve charger location data.
 * I am using YQL as a proxy because the noaa server does not allow cross-server requests.
**/
// ID ESN MONTH DAY HOUR MINUTE DECIMAL_DATE LONGITUDE LATITUDE ? ?
var data_url = 'http://www.nefsc.noaa.gov/drifter/drift_ep_2014_1.dat';
var yql_query = 'SELECT * FROM csv WHERE url="' + data_url + '"';
var yql_url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(yql_query) + '&format=json';

var launch_year = 2014;
var boat_esn = '995094'; // ID of the boat

var current_time = moment();

var raw_data = []; // This will be populated with the raw data
var processed_data = []; // This will be populated with the data that has been processed

var stats = {
    launch_date: 0,
    last_date: 0,
    logged_duration: moment.duration(0),
    real_duration: moment.duration(0),
    distance: 0,
    average_speed: 0,
    maximum_speed: {
        speed: 0,
        point: undefined
    },
    last_24h: {
        distance: 0,
        average_speed: 0
    }
};

// Download data
new Ajax.Request(yql_url, {
    method: 'get',
    onSuccess: function(transport){
        // Split downloaded data into a list
        transport.responseJSON.query.results.row.each(function(row, i){
            split_row = row.col0.replace(/\s+/g, ' ').replace(/(^\s|\s$)/g, '').split(' ');

            // Filter out points from previous launches
            if (split_row[1] == boat_esn) raw_data.push(split_row);
        });

        // Process the data into usable variables
        var year = launch_year; // Keep track of the point's year, as it is not in the raw data
        raw_data.each(function(raw_point, i){
            // Increment year if the last waypoint's decimal date is larger than this
            if (i > 0 && parseFloat(raw_data[i-1][6]) > parseFloat(raw_data[i][6])) year++;

            var point = processed_data[i] = {
                latitude:  parseFloat(raw_point[8]),
                longitude: parseFloat(raw_point[7]),
                date:      moment("{0}/{1}/{2} {3}:{4}".format(year, raw_point[2], raw_point[3], raw_point[4], raw_point[5])),
                isLatest:  i === raw_data.length - 1,
                isFirst:   i === 0,
                previous:  processed_data[i-1],
                next:      undefined
            };

            point.latLng = L.latLng(point.latitude, point.longitude);
            if (!point.isFirst) point.previous.next = point;
            point.time_ago = moment.duration(current_time.diff(point.date));
        });

        // Analyse the data
        processed_data.reverse(false).each(function(point, i) {
            /*
             *  Fill up launch date and last date
             */
            if (point.isFirst)       stats.launch_date = point.date;
            else if (point.isLatest) stats.last_date   = point.date;

            /*
             *  Calculate deltas from last point, if this is not the first
             */
            point.distance_delta = point.isFirst ? 0: point.latLng.distanceTo(point.previous.latLng)/1000;
            point.time_delta = point.isFirst ? moment.duration(0): moment.duration(point.date.diff(point.previous.date));
            point.average_speed  = point.isFirst ? 0: kph_to_knots(point.distance_delta/point.time_delta.asHours());

            if (point.average_speed > stats.maximum_speed.speed)
            {
                stats.maximum_speed.speed = point.average_speed;
                stats.maximum_speed.point = point;
            }

            /*
             *  Increment global stats
             */
            stats.distance += point.distance_delta;
            stats.logged_duration.add(point.time_delta);

            /*
             *  Calculate last 24h stats
             */

            // If this point was in the last 24h
            if (point.time_ago.asHours() <= 24)
            {
                stats.last_24h.distance += point.distance_delta;
            }
            // If the next point was in the last 24h
            else if (!point.isFirst && point.previous.time_ago.asHours() <= 24)
            {
                stats.last_24h.distance += (1-(24/point.time_ago.asHours()))*point.distance_delta;
            }
        });
        /*
         *  Calculate remaining stats
         */
        stats.real_duration = moment.duration(current_time.diff(stats.launch_date));
        stats.average_speed = kph_to_knots(stats.distance/stats.logged_duration.asHours());
        stats.last_24h.average_speed = kph_to_knots(stats.last_24h.distance/24);

        // Create markers
        processed_data.each(function(point, i) {
            var pointMarker = L.marker(point.latLng, {
                // The marker will be a little boat if it's the latest point
                icon: (point.isLatest) ? icons.boat: icons.point
            });

            // Add marker to point object
            point.marker = pointMarker;

            // Add info to a popup
            pointMarker.bindPopup(
                'Date: ' + point.date.format("DD-MM-YYYY HH:mm") + '
' +
                (point.isFirst ? '': 'Average Speed: ' + point.average_speed.round(2) + ' knots
') +
                'Coordinates: ' + point.latitude + '°, ' + point.longitude + '°'
            );

            // Add point to polyline and data layer
            polyline.addLatLng(point.latLng);
            data_layer.addLayer(pointMarker);
        });

        // Add stats to the map
        var legend = L.control({position: 'bottomleft'});
        legend.onAdd = function (map){
            var div = L.DomUtil.create('div', 'info-box');
            div.innerHTML += '\
            Launch date: ' + stats.launch_date.format("DD-MM-YYYY HH:mm") + ' UTC
 \
            Last fix date: ' + stats.last_date.format("DD-MM-YYYY HH:mm") + ' UTC
 \
            Days travelled: ' + stats.real_duration.asDays().round(1) + '
 \
            Distance travelled: ' + stats.distance.round() + ' km
 \
            Average speed ' + stats.average_speed.round(2) + ' knots
\
            Maximum speed ' + stats.maximum_speed.speed.round(2) + ' knots \

 \
            Distance travelled (last 24h): ' + stats.last_24h.distance.round() + ' km
 \
            Average speed (last 24h) ' + stats.last_24h.average_speed.round(2) + ' knots \
            ';
            return div;
        };
        legend.addTo(map);

        // Fit map to polyline bounds, always showing Lisbon
        var lisbon_latLng = L.latLng([38.726662, -9.155274]);
        var bounds = polyline.getBounds().extend(lisbon_latLng);
        map.fitBounds(bounds, {padding: [10, 10]});

        // Add polyline and markers to map
        data_layer.addTo(map);
    },

    onFailure: function() {
        alert('There was an error loading the data...');
    },

    // Fix for Prototype sending more headers than it should
    onCreate: function(response) {
        var t = response.transport;
        t.setRequestHeader = t.setRequestHeader.wrap(function(original, k, v) {
            if (/^(accept|accept-language|content-language)$/i.test(k))
                return original(k, v);
            if (/^content-type$/i.test(k) &&
                /^(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)(;.+)?$/i.test(v))
                return original(k, v);
            return;
        });
    }
});

/*
 * Helper Functions
**/

// String formatting
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) {
      return typeof args[number] != 'undefined' ? args[number] : match;
    });
  };
}

// Round to decimal places
Number.prototype.round = function(places) {
  return !places ? Math.round(this): +(Math.round(this + "e+" + places)  + "e-" + places);
};

// km/h to knots
function kph_to_knots(value)
{
    return value/1.852;
}
<commit_msg>Added the old GPS transmitter to the page, along with a selector. Because of this change, it is not easier to add more transmitters, by simply adding them to the `transmitters` list.<commit_after>/*jshint multistr: true */

/*
 * Load Map
**/

// List of transmitters
var transmitters = [
    {
        name: 'New GPS',
        url: 'http://www.nefsc.noaa.gov/drifter/drift_ep_2014_1.dat',
        esn: '995094',
        year: 2014,
        enabled: true
    },
    {
        name: 'Old GPS',
        url: 'http://www.nefsc.noaa.gov/drifter/drift_ep_2012_1.dat',
        esn: '1236780',
        year: 2013
    }
];

// Load tiles
var world = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
    attribution: 'Esri',
    maxZoom: 9
});

var admin_bounds = L.tileLayer('http://openmapsurfer.uni-hd.de/tiles/adminb/x={x}&y={y}&z={z}', {
    attribution: 'OpenStreetMap',
    maxZoom: 9,
});

var wind = L.tileLayer('http://{s}.tile.openweathermap.org/map/wind/{z}/{x}/{y}.png', {
    attribution: 'OpenWeatherMap',
    opacity: 0.5
});

// Create map
var map = L.map('map', {
    layers: [world],
});
map.fitBounds([[28.11, -80.74], [42.20, -9.54]]); // Show whole north atlantic ocean at first

// Add GitHub page to the attribution
map.attributionControl.options.prefix += ' | Source Code';

// Load icons
var icons = {
    // past location
    point: L.icon({
        iconUrl: 'img/point.svg',
        iconSize: [9, 9]
    }),

    // current location
    boat: L.icon({
        iconUrl: 'img/boat.svg',
        iconSize: [16, 16]
    })
};

var data_layers = L.control.layers(undefined, undefined, {collapsed: false});

var visible_legend = L.control({position: 'bottomleft'});
visible_legend.onAdd = function (map) {
    return L.DomUtil.create('div', 'info-box');
};
visible_legend.update = function (html) {
    this._container.innerHTML = html;
}
visible_legend.addTo(map);
visible_legend.update('Loading...');

for (var i = 0; i < transmitters.length; ++i) {
    var transmitter = transmitters[i];
    var polyline = L.polyline([], {
        color: 'white',
        opacity: 0.2,
        lineCap: 'butt',
        weight: 2
    });
    var data_layer = L.featureGroup([polyline]);
    data_layers.addBaseLayer(data_layer, transmitter.name);

    /*
     * Retrieve location data.
     * I am using YQL as a proxy because the noaa server does not allow cross-server requests.
    **/
    // ID ESN MONTH DAY HOUR MINUTE DECIMAL_DATE LONGITUDE LATITUDE ? ?
    var yql_query = 'SELECT * FROM csv WHERE url="' + transmitter.url + '"';
    var yql_url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(yql_query) + '&format=json';

    // Download data
    new Ajax.Request(yql_url, {
        method: 'get',
        onSuccess: function (i, transmitter, polyline, data_layer) {
            return function(transport) {
                var current_time = moment();

                var raw_data = []; // This will be populated with the raw data
                var processed_data = []; // This will be populated with the data that has been processed

                var stats = {
                    launch_date: 0,
                    last_date: 0,
                    logged_duration: moment.duration(0),
                    real_duration: moment.duration(0),
                    distance: 0,
                    average_speed: 0,
                    maximum_speed: {
                        speed: 0,
                        point: undefined
                    },
                    last_24h: {
                        distance: 0,
                        average_speed: 0
                    }
                };

                // Split downloaded data into a list
                transport.responseJSON.query.results.row.each(function(row, i){
                    split_row = row.col0.replace(/\s+/g, ' ').replace(/(^\s|\s$)/g, '').split(' ');

                    // Filter out points from previous launches
                    if (split_row[1] == transmitter.esn) raw_data.push(split_row);
                });

                // Process the data into usable variables
                var year = transmitter.year; // Keep track of the point's year, as it is not in the raw data
                raw_data.each(function(raw_point, i){
                    // Increment year if the last waypoint's decimal date is larger than this
                    if (i > 0 && parseFloat(raw_data[i-1][6]) > parseFloat(raw_data[i][6])) year++;

                    var point = processed_data[i] = {
                        latitude:  parseFloat(raw_point[8]),
                        longitude: parseFloat(raw_point[7]),
                        date:      moment("{0}/{1}/{2} {3}:{4}".format(year, raw_point[2], raw_point[3], raw_point[4], raw_point[5])),
                        isLatest:  i === raw_data.length - 1,
                        isFirst:   i === 0,
                        previous:  processed_data[i-1],
                        next:      undefined
                    };

                    point.latLng = L.latLng(point.latitude, point.longitude);
                    if (!point.isFirst) point.previous.next = point;
                    point.time_ago = moment.duration(current_time.diff(point.date));
                });

                // Analyse the data
                processed_data.reverse(false).each(function(point, i) {
                    /*
                     *  Fill up launch date and last date
                     */
                    if (point.isFirst)       stats.launch_date = point.date;
                    else if (point.isLatest) stats.last_date   = point.date;

                    /*
                     *  Calculate deltas from last point, if this is not the first
                     */
                    point.distance_delta = point.isFirst ? 0: point.latLng.distanceTo(point.previous.latLng)/1000;
                    point.time_delta = point.isFirst ? moment.duration(0): moment.duration(point.date.diff(point.previous.date));
                    point.average_speed  = point.isFirst ? 0: kph_to_knots(point.distance_delta/point.time_delta.asHours());

                    if (point.average_speed > stats.maximum_speed.speed)
                    {
                        stats.maximum_speed.speed = point.average_speed;
                        stats.maximum_speed.point = point;
                    }

                    /*
                     *  Increment global stats
                     */
                    stats.distance += point.distance_delta;
                    stats.logged_duration.add(point.time_delta);

                    /*
                     *  Calculate last 24h stats
                     */

                    // If this point was in the last 24h
                    if (point.time_ago.asHours() <= 24)
                    {
                        stats.last_24h.distance += point.distance_delta;
                    }
                    // If the next point was in the last 24h
                    else if (!point.isFirst && point.previous.time_ago.asHours() <= 24)
                    {
                        stats.last_24h.distance += (1-(24/point.time_ago.asHours()))*point.distance_delta;
                    }
                });
                /*
                 *  Calculate remaining stats
                 */
                stats.real_duration = moment.duration(current_time.diff(stats.launch_date));
                stats.average_speed = kph_to_knots(stats.distance/stats.logged_duration.asHours());
                stats.last_24h.average_speed = kph_to_knots(stats.last_24h.distance/24);

                // Create markers
                processed_data.each(function(point, i) {
                    var pointMarker = L.marker(point.latLng, {
                        // The marker will be a little boat if it's the latest point
                        icon: (point.isLatest) ? icons.boat: icons.point
                    });

                    // Add marker to point object
                    point.marker = pointMarker;

                    // Add info to a popup
                    pointMarker.bindPopup(
                        'Date: ' + point.date.format("DD-MM-YYYY HH:mm") + '
' +
                        (point.isFirst ? '': 'Average Speed: ' + point.average_speed.round(2) + ' knots
') +
                        'Coordinates: ' + point.latitude + '°, ' + point.longitude + '°'
                    );

                    // Add point to polyline and data layer
                    polyline.addLatLng(point.latLng);
                    data_layer.addLayer(pointMarker);
                });

                // Add stats to the map
                transmitter.html = '\
                    Launch date: ' + stats.launch_date.format("DD-MM-YYYY HH:mm") + ' UTC
 \
                    Last fix date: ' + stats.last_date.format("DD-MM-YYYY HH:mm") + ' UTC
 \
                    Days travelled: ' + stats.real_duration.asDays().round(1) + '
 \
                    Distance travelled: ' + stats.distance.round() + ' km
 \
                    Average speed ' + stats.average_speed.round(2) + ' knots
\
                    Maximum speed ' + stats.maximum_speed.speed.round(2) + ' knots \

 \
                    Distance travelled (last 24h): ' + stats.last_24h.distance.round() + ' km
 \
                    Average speed (last 24h) ' + stats.last_24h.average_speed.round(2) + ' knots \
                    ';

                // If it is the default transmitter, add to map
                if (transmitter.enabled)
                {
                    data_layer.addTo(map);
                    visible_legend.update(transmitter.html);

                    // Fit map to polyline bounds, always showing Lisbon
                    var lisbon_latLng = L.latLng([38.726662, -9.155274]);
                    var bounds = polyline.getBounds().extend(lisbon_latLng);
                    map.fitBounds(bounds, {padding: [10, 10]});
                }

                // If it is the last transmitter, enable the transmitter selector
                if (i == transmitters.length-1)
                {
                    data_layers.addTo(map);

                    // Add the optional layers to a selection control
                    L.control.layers(undefined, {
                        "Administration Boundaries": admin_bounds,
                        "Wind": wind
                    }).addTo(map);
                }
            };
        }(i, transmitter, polyline, data_layer),

        onFailure: function() {
            alert('There was an error loading the data...');
        },

        // Fix for Prototype sending more headers than it should
        onCreate: function(response) {
            var t = response.transport;
            t.setRequestHeader = t.setRequestHeader.wrap(function(original, k, v) {
                if (/^(accept|accept-language|content-language)$/i.test(k))
                    return original(k, v);
                if (/^content-type$/i.test(k) &&
                    /^(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)(;.+)?$/i.test(v))
                    return original(k, v);
                return;
            });
        }
    });
}

// Change legend when the layer is changed
map.on('baselayerchange', function(e) {
    for (var i = 0; i < transmitters.length; ++i) {
        var transmitter = transmitters[i];

        if (e.name == transmitter.name) {
            visible_legend.update(transmitter.html);
        }
    }
});

/*
 * Helper Functions
**/

// String formatting
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) {
      return typeof args[number] != 'undefined' ? args[number] : match;
    });
  };
}

// Round to decimal places
Number.prototype.round = function(places) {
  return !places ? Math.round(this): +(Math.round(this + "e+" + places)  + "e-" + places);
};

// km/h to knots
function kph_to_knots(value)
{
    return value/1.852;
}
<|endoftext|>


<commit_before>/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import {logger} from "../../../../src/logger";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {ScanQRCode, ShowQRCode} from "../../../../src/crypto/verification/QRCode";

const Olm = global.Olm;

describe("QR code verification", function() {
    if (!global.Olm) {
        logger.warn('Not running device verification tests: libolm not present');
        return;
    }

    beforeAll(function() {
        return Olm.init();
    });

    describe("showing", function() {
        it("should emit an event to show a QR code", async function() {
            const channel = {
                send: jest.fn(),
            };
            const qrCode = new ShowQRCode(channel, {
                getUserId: () => "@alice:example.com",
                deviceId: "ABCDEFG",
                getDeviceEd25519Key: function() {
                    return "device+ed25519+key";
                },
            });
            const spy = jest.fn((e) => {
                qrCode.done();
            });
            qrCode.on("show_qr_code", spy);
            await qrCode.verify();
            expect(spy).toHaveBeenCalledWith({
                url: "https://matrix.to/#/@alice:example.com?device=ABCDEFG"
                    + "&action=verify&key_ed25519%3AABCDEFG=device%2Bed25519%2Bkey",
            });
        });
    });

    describe("scanning", function() {
        const QR_CODE_URL = "https://matrix.to/#/@alice:example.com?device=ABCDEFG"
              + "&action=verify&key_ed25519%3AABCDEFG=device%2Bed25519%2Bkey";
        it("should verify when a QR code is sent", async function() {
            const device = DeviceInfo.fromStorage(
                {
                    algorithms: [],
                    keys: {
                        "curve25519:ABCDEFG": "device+curve25519+key",
                        "ed25519:ABCDEFG": "device+ed25519+key",
                    },
                    verified: false,
                    known: false,
                    unsigned: {},
                },
                "ABCDEFG",
            );
            const client = {
                getStoredDevice: jest.fn().mockReturnValue(device),
                setDeviceVerified: jest.fn(),
            };
            const channel = {
                send: jest.fn(),
            };
            const qrCode = new ScanQRCode(channel, client);
            qrCode.on("confirm_user_id", ({userId, confirm}) => {
                if (userId === "@alice:example.com") {
                    confirm();
                } else {
                    qrCode.cancel(new Error("Incorrect user"));
                }
            });
            qrCode.on("scan", ({done}) => {
                done(QR_CODE_URL);
            });
            await qrCode.verify();
            expect(client.getStoredDevice)
                .toHaveBeenCalledWith("@alice:example.com", "ABCDEFG");
            expect(client.setDeviceVerified)
                .toHaveBeenCalledWith("@alice:example.com", "ABCDEFG");
        });

        it("should error when the user ID doesn't match", async function() {
            const client = {
                getStoredDevice: jest.fn(),
                setDeviceVerified: jest.fn(),
            };
            const channel = {
                send: jest.fn(),
            };
            const qrCode = new ScanQRCode(channel, client, "@bob:example.com", "ABCDEFG");
            qrCode.on("scan", ({done}) => {
                done(QR_CODE_URL);
            });
            const spy = jest.fn();
            await qrCode.verify().catch(spy);
            expect(spy).toHaveBeenCalled();
            expect(channel.send).toHaveBeenCalled();
            expect(client.getStoredDevice).not.toHaveBeenCalled();
            expect(client.setDeviceVerified).not.toHaveBeenCalled();
        });

        it("should error if the key doesn't match", async function() {
            const device = DeviceInfo.fromStorage(
                {
                    algorithms: [],
                    keys: {
                        "curve25519:ABCDEFG": "device+curve25519+key",
                        "ed25519:ABCDEFG": "a+different+device+ed25519+key",
                    },
                    verified: false,
                    known: false,
                    unsigned: {},
                },
                "ABCDEFG",
            );
            const client = {
                getStoredDevice: jest.fn().mockReturnValue(device),
                setDeviceVerified: jest.fn(),
            };
            const channel = {
                send: jest.fn(),
            };
            const qrCode = new ScanQRCode(
                channel, client, "@alice:example.com", "ABCDEFG");
            qrCode.on("scan", ({done}) => {
                done(QR_CODE_URL);
            });
            const spy = jest.fn();
            await qrCode.verify().catch(spy);
            expect(spy).toHaveBeenCalled();
            expect(channel.send).toHaveBeenCalled();
            expect(client.getStoredDevice).toHaveBeenCalled();
            expect(client.setDeviceVerified).not.toHaveBeenCalled();
        });
    });
});
<commit_msg>Remove tests for old QR code stuff<commit_after>/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import {logger} from "../../../../src/logger";

const Olm = global.Olm;

describe("QR code verification", function() {
    if (!global.Olm) {
        logger.warn('Not running device verification tests: libolm not present');
        return;
    }

    beforeAll(function() {
        return Olm.init();
    });

    describe("reciprocate", () => {
        it("should verify the secret", () => {
            // TODO: Actually write a test for this.
            // Tests are hard because we are running before the verification
            // process actually begins, and are largely UI-driven rather than
            // logic-driven (compared to something like SAS). In the interest
            // of time, tests are currently excluded.
        });
    });
});
<|endoftext|>


<commit_before>/*
	Endless: A bidirectional, React infinite scroll component.
	Based on ReactList (https://github.com/orgsync/react-list)
*/

/* global define, module, require, setTimeout, clearTimeout, window */
/* jshint -W116 */ // Don't warn about single-line if's.

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define(['react'], factory);
	} else if (typeof exports !== 'undefined') {
		module.exports = factory(require('react'));
	} else {
		root.Endless = factory(root.React);
	}
})(this, function(React) {
	'use strict';

	var buildReactElement = function(jsonml) {
		if (!Array.isArray(jsonml)) {
			return jsonml;
		}

		if (jsonml.length > 1 &&
			(typeof jsonml[1] !== 'object' || Array.isArray(jsonml[1]))
		) {
			jsonml.splice(1, 0, null);
		}

		return React.createElement.apply(React, jsonml.map(buildReactElement));
	};

	var requestAnimationFrame =
		(typeof window !== 'undefined' && window.requestAnimationFrame) ||
		function(cb) {
			return setTimeout(cb, 16);
		};

	var cancelAnimationFrame =
		(typeof window !== 'undefined' && window.cancelAnimationFrame) ||
		clearTimeout;

	var isEqualSubset = function(a, b) {
		for (var key in a)
			if (b[key] !== a[key]) return false;
		return true;
	};

	var isEqual = function(a, b) {
		return isEqualSubset(a, b) && isEqualSubset(b, a);
	};

	var getComputedValue = function (el, propName) {
		var value = parseFloat(window.getComputedStyle(el)[propName]);
		if(isNaN(value)) value = 0;
		return value;
	};

	return React.createClass({
		getDefaultProps: function() {
			return {
				items: [],
				margin: 2000,
				atTop: false,
				atBottom: false,
				onScroll: function( /* key, above, below */ ) {},
				onMount: function( /* event */ ) {},
				onUnmount: function() {}
			};
		},

		getInitialState: function() {
			this.lastState = this.lastState ||  { jumpRequired: true, offset: 0 };

			if (this.props.items.length) {
				if (this.props.atTop) {
					this.lastState.jumpToIndex = 0;
					this.lastState.position = 'top';
				} else if (this.props.atBottom) {
					this.lastState.jumpToIndex = this.props.items.length - 1;
					this.lastState.position = 'bottom';
				} else {
					this.lastState.jumpToIndex = 0;
					this.lastState.position = buildReactElement(this.props.items[0]).key;
				}
			}

			if (this.props.position) {
				this.lastState.position = this.props.position;
			}

			return {
				itemHeight: 25,
				columns: 1,
				topReached: this.props.atTop,
				bottomReached: this.props.atBottom,
				topRemoved: 0,
				bottomRemoved: 0
			};
		},

		update: function() {
			var items = this.props.items,
				itemsEl = this.refs.items.getDOMNode(),
				itemEls = itemsEl.children,
				jumped = false;

			if (!itemEls.length) {
				this.stid = setTimeout(this.update, 200);
				return;
			}

			if (this.lastState.jumpRequired && this.lastState.position) {
				if (this.lastState.position == 'top') {
					this.setScroll(-9E99);
				//	this.scrollTo(0, 0);
					jumped = true;
				} else if (this.lastState.position == 'bottom') {
					this.setScroll(9E99);
				//	this.scrollTo(items.length - 1, this.getBottom(itemEls[itemEls.length-1]) - this.getTop(itemEls[itemEls.length-1]));
					jumped = true;
				} else {
					for (i = 0; i < items.length; i++) {
						if (items[i].key == this.lastState.position) {
							this.scrollTo(i, this.lastState.offset);
							jumped = true;
							break;
						}
					}
				}

				if(jumped) {
					this.lastState.offset = 0;
					this.lastState.jumpRequired = false;
				}

				this.afid = requestAnimationFrame(this.update);
				return;
			}

			var viewTop = this.getScroll(),
				viewHeight = this.getViewportHeight(),
				viewBottom = viewTop + viewHeight,
				elBottom = this.getDOMNode().scrollHeight,
				last = this.lastState,
				position, i, offset, above, below, itemHeight, top, columns;

			top = this.getTop(itemEls[0]);

			for(columns=1; columns<itemEls.length && this.getTop(itemEls[columns]) == top; columns++);

			itemHeight = (this.getBottom(itemEls[itemEls.length - 1]) - top) / itemEls.length;
			if(itemHeight <= 0) itemHeight = 20; // Ugly hack for handling display:none items.
			itemHeight *= columns;

			if (this.state.bottomReached && !this.state.bottomRemoved && viewBottom >= elBottom-4) {
				position = 'bottom';
				offset = 0;
				above = columns * Math.ceil(viewHeight / itemHeight);
				below = 0;
			} else if (this.state.topReached && !this.state.topRemoved && viewTop <= 4) {
				position = 'top';
				offset = 0;
				above = 0;
				below = columns * Math.ceil(viewHeight / itemHeight);
			} else {
				for (i = 0; i < itemEls.length; i++) {
					offset = itemEls[i].offsetTop + itemEls[i].scrollHeight - viewTop;
					if (offset > 0) break;
				}
				if (i == itemEls.length) i--; // All the items are above the viewport; pick last one.

				position = buildReactElement(items[i]).key; // building in case it is JSONML
				if (viewTop < itemEls[0].offsetTop) {
					offset = viewTop - itemEls[0].offsetTop;
					above = columns * Math.ceil(-offset / itemHeight);
				} else {
					above = 0;
				}
				below = Math.max(0, columns * Math.ceil(Math.max(
					viewHeight, (viewBottom - itemEls[itemEls.length-1].offsetTop)
				) / itemHeight) - above);
			}

			above = Math.round(above);
			below = Math.round(below);

			if (last.position !== position || last.above < above || last.below < below) {

//				if(jumped) {
//					console.log(
//						'changing position despite jump',
//						last.position.split('-').pop(),
//						position.split('-').pop(),
//						last.offset, offset,
//						jumpedIndex, i
//					);
//				}

				last.position = position;
				last.offset = offset;
				last.above = above;
				last.below = below;

				this.afid = requestAnimationFrame(this.update);
				this.props.onScroll(position, above, below);
			} else if(last.offset !== offset) {
				this.afid = requestAnimationFrame(this.update);
			} else {
				this.stid = setTimeout(this.update, 200);
			}
		},

		componentDidMount: function() {
			this.update();
			this.props.onMount();
		},

		componentWillUnmount: function() {
			cancelAnimationFrame(this.afid);
			clearTimeout(this.stid);
			this.props.onUnmount();
		},

		shouldComponentUpdate: function(props, state) {
			if (isEqual(this.props, props) && isEqual(this.state, state)) return false;
			return true;
		},

		componentWillReceiveProps: function(nextProps) {

			if(nextProps.position && nextProps.position !== this.lastState.position) {
				this.lastState.position = nextProps.position;
				this.lastState.offset = 0;
				this.lastState.jumpRequiredAfterUpdate = true;
			}

			this.setState({
				topReached: this.state.topReached || nextProps.atTop,
				bottomReached: this.state.bottomReached || nextProps.atBottom,
				topRemoved: nextProps.atTop? 0: Math.max(10, this.state.topRemoved),
				bottomRemoved: nextProps.atBottom? 0: Math.max(10, this.state.bottomRemoved),
			});
		},

		componentDidUpdate: function(prevProps) {
//			if (this.lastState.jumpToIndex !== null) return;

			var prevItems = prevProps.items.map(buildReactElement),
				items = this.props.items.map(buildReactElement),
				metrics, prevMetrics = this.metrics, i,
				topAdded, topRemoved, bottomAdded, bottomRemoved;

			if (!items.length || !prevItems.length) return;

			// Calculate the new metrics (tops and bottoms of each element)
			metrics = [].slice.call(this.refs.items.getDOMNode().children).map(function(itemEl) {
				return {
					top: this.getTop(itemEl),
					bottom: this.getBottom(itemEl)
				};
			}.bind(this));

			if(prevMetrics && prevMetrics.length !== prevItems.length) {
				console.log("Endless Error: prevMetrics.length ≠ prevItems.length. "+
							"Did you modify the items array after calling setProps?", prevMetrics.length, prevItems.length);
			}

			if (metrics && prevMetrics && items.length && prevItems.length) {
				for (i = 0; i < items.length && items[i].key != prevItems[0].key; i++);
				topAdded = (i == items.length) ? 0 : metrics[i].top - metrics[0].top;

				for (i = 0; i < prevItems.length && prevItems[i].key != items[0].key; i++);
				topRemoved = (i == prevItems.length) ? 0 : prevMetrics[i].top - prevMetrics[0].top;

				for (i = items.length - 1; i >= 0 && items[i].key != prevItems[prevItems.length - 1].key; i--);
				bottomAdded = (i < 0) ? 0 : metrics[metrics.length - 1].bottom - metrics[i].bottom;

				for (i = prevItems.length - 1; i >= 0 && prevItems[i].key != items[items.length - 1].key; i--);
				bottomRemoved = (i < 0) ? 0 : prevMetrics[prevMetrics.length - 1].bottom - prevMetrics[i].bottom;

				this.setState({
					topRemoved: Math.max(0, this.state.topRemoved + topRemoved - topAdded),
					bottomRemoved: Math.max(0, this.state.bottomRemoved + bottomRemoved - bottomAdded)
				});
			}

			//	console.log('Rendered', items[0].key, 'through', items[items.length-1].key,
			//				'Removed space ', this.state.topRemoved, this.state.bottomRemoved);
			this.metrics = metrics;

			if(
				items[0].key != prevItems[0].key ||
				!prevProps.topReached && this.props.atTop ||
				this.lastState.position === 'bottom' ||
				this.lastState.jumpRequiredAfterUpdate
			) {
				this.lastState.jumpRequired = true;
				delete this.lastState.jumpRequiredAfterUpdate;
			}
		},

		getTop: function(el) {
			return el.getBoundingClientRect().top - getComputedValue(el, 'marginTop');
		},

		getBottom: function(el) {
			return el.getBoundingClientRect().bottom + getComputedValue(el, 'marginBottom');
		},

		getScrollParent: function() {
			for (var el = this.getDOMNode(); el; el = el.parentElement) {
				var overflowY = window.getComputedStyle(el).overflowY;
				if (overflowY === 'auto' || overflowY === 'scroll') {
					return el;
				}
			}
			return window;
		},

		// Get scroll position relative to the top of the list.
		getScroll: function() {
			var scrollParent = this.getScrollParent(),
				el = this.getDOMNode();

			if (scrollParent === el) {
				return el.scrollTop;
			} else if (scrollParent === window) {
				return -el.getBoundingClientRect().top;
			} else {
				return scrollParent.getBoundingClientRect().top -
					el.getBoundingClientRect().top + getComputedValue(scrollParent, 'borderTop');
			}
		},

		setScroll: function(y) {
			var scrollParent = this.getScrollParent(),
				el = this.getDOMNode();

			if (scrollParent != el) {
				y += (scrollParent.scrollTop -
					(scrollParent.getBoundingClientRect().top -
					el.getBoundingClientRect().top +
					getComputedValue(scrollParent, 'borderTop')));
			}
			y = Math.max(0, y);
			if (scrollParent === window) return window.scrollTo(0, y);
			scrollParent.scrollTop = y;
		},

		getViewportHeight: function() {
			var scrollParent = this.getScrollParent();
			return scrollParent === window ? scrollParent.innerHeight : scrollParent.clientHeight;
		},

		scrollTo: function(index, offset) {
			var y = this.refs.items.getDOMNode().children[index].offsetTop + offset;

			//	console.log(
			//		"scrolling from", this.getScroll(), "to",
			//		this.refs.items.getDOMNode().children[index].offsetTop, offset
			//	);
			this.setScroll(y);
		},

		render: function() {
			return buildReactElement(['div', {
					style: {
						position: 'relative'
					}
				},
				['div', {
					style: {
						height: this.state.topRemoved + (this.state.topReached ? 0 : this.props.margin)
					}
				}],
				['div', {
					ref: 'items'
				}].concat(this.props.items), ['div', {
					style: {
						clear: 'both',
						height: this.state.bottomRemoved + (this.state.bottomReached ? 0 : this.props.margin)
					}
				}]
			]);
		}
	});
});
<commit_msg>Fixed bug with offsets causing runaway scroll<commit_after>/*
	Endless: A bidirectional, React infinite scroll component.
	Based on ReactList (https://github.com/orgsync/react-list)
*/

/* global define, module, require, setTimeout, clearTimeout, window */
/* jshint -W116 */ // Don't warn about single-line if's.

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define(['react'], factory);
	} else if (typeof exports !== 'undefined') {
		module.exports = factory(require('react'));
	} else {
		root.Endless = factory(root.React);
	}
})(this, function(React) {
	'use strict';

	var buildReactElement = function(jsonml) {
		if (!Array.isArray(jsonml)) {
			return jsonml;
		}

		if (jsonml.length > 1 &&
			(typeof jsonml[1] !== 'object' || Array.isArray(jsonml[1]))
		) {
			jsonml.splice(1, 0, null);
		}

		return React.createElement.apply(React, jsonml.map(buildReactElement));
	};

	var requestAnimationFrame =
		(typeof window !== 'undefined' && window.requestAnimationFrame) ||
		function(cb) {
			return setTimeout(cb, 16);
		};

	var cancelAnimationFrame =
		(typeof window !== 'undefined' && window.cancelAnimationFrame) ||
		clearTimeout;

	var isEqualSubset = function(a, b) {
		for (var key in a)
			if (b[key] !== a[key]) return false;
		return true;
	};

	var isEqual = function(a, b) {
		return isEqualSubset(a, b) && isEqualSubset(b, a);
	};

	var getComputedValue = function (el, propName) {
		var value = parseFloat(window.getComputedStyle(el)[propName]);
		if(isNaN(value)) value = 0;
		return value;
	};

	return React.createClass({
		getDefaultProps: function() {
			return {
				items: [],
				margin: 2000,
				atTop: false,
				atBottom: false,
				onScroll: function( /* key, above, below */ ) {},
				onMount: function( /* event */ ) {},
				onUnmount: function() {}
			};
		},

		getInitialState: function() {
			this.lastState = this.lastState ||  { jumpRequired: true, offset: 0 };

			if (this.props.items.length) {
				if (this.props.atTop) {
					this.lastState.jumpToIndex = 0;
					this.lastState.position = 'top';
				} else if (this.props.atBottom) {
					this.lastState.jumpToIndex = this.props.items.length - 1;
					this.lastState.position = 'bottom';
				} else {
					this.lastState.jumpToIndex = 0;
					this.lastState.position = buildReactElement(this.props.items[0]).key;
				}
			}

			if (this.props.position) {
				this.lastState.position = this.props.position;
			}

			return {
				itemHeight: 25,
				columns: 1,
				topReached: this.props.atTop,
				bottomReached: this.props.atBottom,
				topRemoved: 0,
				bottomRemoved: 0
			};
		},

		update: function() {
			var items = this.props.items,
				itemsEl = this.refs.items.getDOMNode(),
				itemEls = itemsEl.children,
				jumped = false;

			if (!itemEls.length) {
				this.stid = setTimeout(this.update, 200);
				return;
			}

			if (this.lastState.jumpRequired && this.lastState.position) {
				if (this.lastState.position == 'top') {
					this.setScroll(-9E99);
				//	this.scrollTo(0, 0);
					jumped = true;
				} else if (this.lastState.position == 'bottom') {
					this.setScroll(9E99);
				//	this.scrollTo(items.length - 1, this.getBottom(itemEls[itemEls.length-1]) - this.getTop(itemEls[itemEls.length-1]));
					jumped = true;
				} else {
					for (i = 0; i < items.length; i++) {
						if (items[i].key == this.lastState.position) {
							this.scrollTo(i, this.lastState.offset);
							jumped = true;
							break;
						}
					}
				}

				if(jumped) {
					this.lastState.offset = 0;
					this.lastState.jumpRequired = false;
				} else {
					console.log("Endless Error: Jump was required but it did not happen. This usually happens when scrolling down very fast.");
				}

				this.afid = requestAnimationFrame(this.update);
				return;
			}

			var viewTop = this.getScroll(),
				viewHeight = this.getViewportHeight(),
				viewBottom = viewTop + viewHeight,
				elBottom = this.getDOMNode().scrollHeight,
				last = this.lastState,
				position, i, offset, above, below, itemHeight, top, columns;

			top = this.getTop(itemEls[0]);

			for(columns=1; columns<itemEls.length && this.getTop(itemEls[columns]) == top; columns++);

			itemHeight = (this.getBottom(itemEls[itemEls.length - 1]) - top) / itemEls.length;
			if(itemHeight <= 0) itemHeight = 20; // Ugly hack for handling display:none items.
			itemHeight *= columns;

			if (this.state.bottomReached && !this.state.bottomRemoved && viewBottom >= elBottom-4) {
				position = 'bottom';
				offset = 0;
				above = columns * Math.ceil(viewHeight / itemHeight);
				below = 0;
			} else if (this.state.topReached && !this.state.topRemoved && viewTop <= 4) {
				position = 'top';
				offset = 0;
				above = 0;
				below = columns * Math.ceil(viewHeight / itemHeight);
			} else {
				for (i = 0; i < itemEls.length; i++) {
					offset = itemEls[i].offsetTop + itemEls[i].scrollHeight - viewTop;
					if (offset >= 0) {
						offset = viewTop - itemEls[i].offsetTop;
						break;
					}
				}
				if (i == itemEls.length) i--; // All the items are above the viewport; pick last one.

				position = buildReactElement(items[i]).key; // building in case it is JSONML
				if (viewTop < itemEls[0].offsetTop) {
					offset = viewTop - itemEls[0].offsetTop;
					above = columns * Math.ceil(-offset / itemHeight);
				} else {
					above = 0;
				}
				below = Math.max(0, columns * Math.ceil(Math.max(
					viewHeight, (viewBottom - itemEls[itemEls.length-1].offsetTop)
				) / itemHeight) - above);
			}

			above = Math.round(above);
			below = Math.round(below);

			if (last.position !== position || last.above < above || last.below < below) {

				last.position = position;
				last.offset = offset;
				last.above = above;
				last.below = below;

				this.afid = requestAnimationFrame(this.update);
				this.props.onScroll(position, above, below);
			} else if(last.offset !== offset) {
				this.afid = requestAnimationFrame(this.update);
			} else {
				this.stid = setTimeout(this.update, 200);
			}
		},

		componentDidMount: function() {
			this.update();
			this.props.onMount();
		},

		componentWillUnmount: function() {
			cancelAnimationFrame(this.afid);
			clearTimeout(this.stid);
			this.props.onUnmount();
		},

		shouldComponentUpdate: function(props, state) {
			if (isEqual(this.props, props) && isEqual(this.state, state)) return false;
			return true;
		},

		componentWillReceiveProps: function(nextProps) {

			if(nextProps.position && nextProps.position !== this.lastState.position) {
				this.lastState.position = nextProps.position;
				this.lastState.offset = 0;
				this.lastState.jumpRequiredAfterUpdate = true;
			}

			this.setState({
				topReached: this.state.topReached || nextProps.atTop,
				bottomReached: this.state.bottomReached || nextProps.atBottom,
				topRemoved: nextProps.atTop? 0: Math.max(10, this.state.topRemoved),
				bottomRemoved: nextProps.atBottom? 0: Math.max(10, this.state.bottomRemoved),
			});
		},

		componentDidUpdate: function(prevProps) {
//			if (this.lastState.jumpToIndex !== null) return;

			var prevItems = prevProps.items.map(buildReactElement),
				items = this.props.items.map(buildReactElement),
				metrics, prevMetrics = this.metrics, i,
				topAdded, topRemoved, bottomAdded, bottomRemoved;

			if (!items.length || !prevItems.length) return;

			// Calculate the new metrics (tops and bottoms of each element)
			metrics = [].slice.call(this.refs.items.getDOMNode().children).map(function(itemEl) {
				return {
					top: this.getTop(itemEl),
					bottom: this.getBottom(itemEl)
				};
			}.bind(this));

			if(prevMetrics && prevMetrics.length !== prevItems.length) {
				console.log("Endless Error: prevMetrics.length ≠ prevItems.length. "+
							"Did you modify the items array after calling setProps?", prevMetrics.length, prevItems.length);
			}

			if (metrics && prevMetrics && items.length && prevItems.length) {
				for (i = 0; i < items.length && items[i].key != prevItems[0].key; i++);
				topAdded = (i == items.length) ? 0 : metrics[i].top - metrics[0].top;

				for (i = 0; i < prevItems.length && prevItems[i].key != items[0].key; i++);
				topRemoved = (i == prevItems.length) ? 0 : prevMetrics[i].top - prevMetrics[0].top;

				for (i = items.length - 1; i >= 0 && items[i].key != prevItems[prevItems.length - 1].key; i--);
				bottomAdded = (i < 0) ? 0 : metrics[metrics.length - 1].bottom - metrics[i].bottom;

				for (i = prevItems.length - 1; i >= 0 && prevItems[i].key != items[items.length - 1].key; i--);
				bottomRemoved = (i < 0) ? 0 : prevMetrics[prevMetrics.length - 1].bottom - prevMetrics[i].bottom;

				this.setState({
					topRemoved: Math.max(0, this.state.topRemoved + topRemoved - topAdded),
					bottomRemoved: Math.max(0, this.state.bottomRemoved + bottomRemoved - bottomAdded)
				});
			}

			//	console.log('Rendered', items[0].key, 'through', items[items.length-1].key,
			//				'Removed space ', this.state.topRemoved, this.state.bottomRemoved);
			this.metrics = metrics;

			if(
				items[0].key != prevItems[0].key ||
				!prevProps.topReached && this.props.atTop ||
				this.lastState.position === 'bottom' ||
				this.lastState.jumpRequiredAfterUpdate
			) {
				this.lastState.jumpRequired = true;
				delete this.lastState.jumpRequiredAfterUpdate;
			}
		},

		getTop: function(el) {
			return el.getBoundingClientRect().top - getComputedValue(el, 'marginTop');
		},

		getBottom: function(el) {
			return el.getBoundingClientRect().bottom + getComputedValue(el, 'marginBottom');
		},

		getScrollParent: function() {
			for (var el = this.getDOMNode(); el; el = el.parentElement) {
				var overflowY = window.getComputedStyle(el).overflowY;
				if (overflowY === 'auto' || overflowY === 'scroll') {
					return el;
				}
			}
			return window;
		},

		// Get scroll position relative to the top of the list.
		getScroll: function() {
			var scrollParent = this.getScrollParent(),
				el = this.getDOMNode();

			if (scrollParent === el) {
				return el.scrollTop;
			} else if (scrollParent === window) {
				return -el.getBoundingClientRect().top;
			} else {
				return scrollParent.getBoundingClientRect().top -
					el.getBoundingClientRect().top + getComputedValue(scrollParent, 'borderTop');
			}
		},

		setScroll: function(y) {
			var scrollParent = this.getScrollParent(),
				el = this.getDOMNode();

			if (scrollParent != el) {
				y += (scrollParent.scrollTop -
					(scrollParent.getBoundingClientRect().top -
					el.getBoundingClientRect().top +
					getComputedValue(scrollParent, 'borderTop')));
			}
			y = Math.max(0, y);
			if (scrollParent === window) return window.scrollTo(0, y);
			scrollParent.scrollTop = y;
		},

		getViewportHeight: function() {
			var scrollParent = this.getScrollParent();
			return scrollParent === window ? scrollParent.innerHeight : scrollParent.clientHeight;
		},

		scrollTo: function(index, offset) {
			var y = this.refs.items.getDOMNode().children[index].offsetTop + offset;

			//	console.log(
			//		"scrolling from", this.getScroll(), "to",
			//		this.refs.items.getDOMNode().children[index].offsetTop, offset
			//	);
			this.setScroll(y);
		},

		render: function() {
			return buildReactElement(['div', {
					style: {
						position: 'relative'
					}
				},
				['div', {
					style: {
						height: this.state.topRemoved + (this.state.topReached ? 0 : this.props.margin)
					}
				}],
				['div', {
					ref: 'items'
				}].concat(this.props.items), ['div', {
					style: {
						clear: 'both',
						height: this.state.bottomRemoved + (this.state.bottomReached ? 0 : this.props.margin)
					}
				}]
			]);
		}
	});
});
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;
var pg = require('pg');
var Q = require("q");
var request = require("request");
var newUser = require("./db");
var logConversation = require("./conversation");
var nlp = require("./nlp");
var pushConv = require("./push");
var newBot_msg = require("./newBot");
var findSession = require("./sessionsearch");

var sched = require("./sched");  //delete later

const scriptRules = require('./script.json');

var msgLog = {
    smoochId: '',
    received: '',
    usermessage: '',
    role: '',
    message_id: '',
    sourcetype: '',
    receivedtime: '',
    responsemessage: '',
    responsetype: '',
    responsetime: ''
  };

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hello!  I\'m A16, the Agile2016 EventBot Concierge.  To find out what I can help you with type MENU for options or KEY for Keywords.')
                .then(() => 'speak');
        }
    },

    speakers: {
      prompt: (bot) => bot.say('Type part of the session name?'),
      receive: (bot, message) => {
        const name = message.text;
        return findSession(message)
          .then(() => bot.say(`I found some`))
          .then(() => 'speak');
      }
    },

    finish: {
      receive: (bot, message) => {
        return bot.getProp('name')
          .then((name) => bot.say('That is all!'))
          .then(() => 'speak');
      }
    },

    speak: {
        receive: (bot, message) => {
            console.log("===bot user ");
            let upperText = message.text.trim().toUpperCase();

            if (upperText == 'SPEAKERS') {
              return bot.getProp('name')
              .then(() => 'speakers');
            }

            msgLog.smoochId = bot.userId;
            msgLog.received = message.received;
            msgLog.usermessage = message.text;
            msgLog.role = message.role;
            msgLog.message_id = message._id;
            msgLog.sourcetype = message.source.type;
            msgLog.receivedtime = new Date();

            //sched.schedSessions('SessionList');

            //This is the control list of smoochId that can send broadcast messages
            var authUsers = ['a30fa820d0a0f0216fa26070'];

            newUser(bot)

            //For ad hoc messages - scheduled messages are done differently in checkItems
            if (authUsers.indexOf(bot.userId) !== -1) {
              if (upperText.substr(0,4) == '/SK ') {
                upperText = upperText.substr(0,3);
                newBot_msg(message.text.substr(4));
                console.log("****after push msg:  ",message.text," authUser:  ",authUsers);
              }
            }

            function updateSilent() {
                switch (upperText) {
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "/SUPPORT":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    case "/A16":
                        console.log("*** ",upperText," ***");
                        processMessage(false);
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                var promises = [];
                var source;
                var fulfillmentSpeech;
                var simplified;
                promises.push(nlp(upperText, bot.userId));

                Q.all(promises).then(function(responses) {
                    // response is the JSON from API.ai
                    responses.forEach(function(response) {
                        //console.log("===in Q.all");
                        console.log("===received result from API.ai",response);
                        source = response.result.source;
                        if (source && source !== 'agent')
                        {
                            fulfillmentSpeech = response.result.fulfillment.speech;
                            simplified = response.result.parameters.simplified;
                        }
//                        console.log("source: ", source);
//                        console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                        console.log("simplified: ", simplified);

                        respondMessage(source, fulfillmentSpeech, simplified);
                    });
                }, function(error) {
                    console.log("===Q all error ", error);
                });
            }

            function respondMessage(source, fulfillmentSpeech, simplified)
            {
//                console.log("source: ", source);
//                console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                console.log("simplified: ", simplified);
//                console.log("===receive step 3",upperText);

              if (source != 'agent')
                {
//                    console.log("===source is ", source);
                    if (fulfillmentSpeech)
                    {
                      switch (simplified) {
                        case "hello":
//                          console.log("===in hello");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "what do you know":
//                          console.log("===in what do you know");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "can you talk":
//                          console.log("===can you talk");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        default:
//                          console.log("===in switch default");
                          msgLog.responsemessage = fulfillmentSpeech;
                          msgLog.responsetime = new Date;
                          msgLog.responsetype = 'API.AI';
                          return bot.say(fulfillmentSpeech).then(() => 'speak');
                      }
                    }
                    else if (simplified)
                    {
//                        console.log("simplified is: ", simplified);
                        upperText = simplified.toUpperCase();
                    }
                }
                if (!_.has(scriptRules, upperText)) {
//                    console.log("===no rule", upperText);
                    return bot.say(`I'm sorry that is not something I know.  Type MENU or KEY for a list of things I can help you with.`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log("=== p line",line);
                        return wait(5).then(function() {
                            msgLog.responsemessage = line;
                            msgLog.responsetime = new Date;
                            msgLog.responsetype = 'JSON';
                            console.log("=== msgLog  obj",msgLog);
                            logConversation(msgLog);
                            return bot.say(line);
                        });
                    });
                });
                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }
});
<commit_msg>cleaning up mess<commit_after>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;
var pg = require('pg');
var Q = require("q");
var request = require("request");
var newUser = require("./db");
var logConversation = require("./conversation");
var nlp = require("./nlp");
var pushConv = require("./push");
var newBot_msg = require("./newBot");
var findSession = require("./sessionsearch");

var sched = require("./sched");  //delete later

const scriptRules = require('./script.json');

var msgLog = {
    smoochId: '',
    received: '',
    usermessage: '',
    role: '',
    message_id: '',
    sourcetype: '',
    receivedtime: '',
    responsemessage: '',
    responsetype: '',
    responsetime: ''
  };

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hello!  I\'m A16, the Agile2016 EventBot Concierge.  To find out what I can help you with type MENU for options or KEY for Keywords.')
                .then(() => 'speak');
        }
    },

    speakers: {
      prompt: (bot) => bot.say('Type part of the session name?'),
      receive: (bot, message) => {
        const name = message.text;
        return bot.setProp('name', name)
          .then(() => bot.say(`I will search for ${name} is that OK? %[Yes]() %[No](postback:hello)`))
          .then(() => 'speak');
      }
    },

    finish: {
      receive: (bot, message) => {
        return bot.getProp('name')
          .then((name) => bot.say('That is all!'))
          .then(() => 'speak');
      }
    },

    speak: {
        receive: (bot, message) => {
            console.log("===bot user ");
            let upperText = message.text.trim().toUpperCase();

            if (upperText == 'SPEAKERS') {
              return bot.getProp('name')
              .then(() => 'speakers');
            }

            msgLog.smoochId = bot.userId;
            msgLog.received = message.received;
            msgLog.usermessage = message.text;
            msgLog.role = message.role;
            msgLog.message_id = message._id;
            msgLog.sourcetype = message.source.type;
            msgLog.receivedtime = new Date();

            //sched.schedSessions('SessionList');

            //This is the control list of smoochId that can send broadcast messages
            var authUsers = ['a30fa820d0a0f0216fa26070'];

            newUser(bot)

            //For ad hoc messages - scheduled messages are done differently in checkItems
            if (authUsers.indexOf(bot.userId) !== -1) {
              if (upperText.substr(0,4) == '/SK ') {
                upperText = upperText.substr(0,3);
                newBot_msg(message.text.substr(4));
                console.log("****after push msg:  ",message.text," authUser:  ",authUsers);
              }
            }

            function updateSilent() {
                switch (upperText) {
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "/SUPPORT":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    case "/A16":
                        console.log("*** ",upperText," ***");
                        processMessage(false);
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                var promises = [];
                var source;
                var fulfillmentSpeech;
                var simplified;
                promises.push(nlp(upperText, bot.userId));

                Q.all(promises).then(function(responses) {
                    // response is the JSON from API.ai
                    responses.forEach(function(response) {
                        //console.log("===in Q.all");
                        console.log("===received result from API.ai",response);
                        source = response.result.source;
                        if (source && source !== 'agent')
                        {
                            fulfillmentSpeech = response.result.fulfillment.speech;
                            simplified = response.result.parameters.simplified;
                        }
//                        console.log("source: ", source);
//                        console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                        console.log("simplified: ", simplified);

                        respondMessage(source, fulfillmentSpeech, simplified);
                    });
                }, function(error) {
                    console.log("===Q all error ", error);
                });
            }

            function respondMessage(source, fulfillmentSpeech, simplified)
            {
//                console.log("source: ", source);
//                console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                console.log("simplified: ", simplified);
//                console.log("===receive step 3",upperText);

              if (source != 'agent')
                {
//                    console.log("===source is ", source);
                    if (fulfillmentSpeech)
                    {
                      switch (simplified) {
                        case "hello":
//                          console.log("===in hello");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "what do you know":
//                          console.log("===in what do you know");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "can you talk":
//                          console.log("===can you talk");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        default:
//                          console.log("===in switch default");
                          msgLog.responsemessage = fulfillmentSpeech;
                          msgLog.responsetime = new Date;
                          msgLog.responsetype = 'API.AI';
                          return bot.say(fulfillmentSpeech).then(() => 'speak');
                      }
                    }
                    else if (simplified)
                    {
//                        console.log("simplified is: ", simplified);
                        upperText = simplified.toUpperCase();
                    }
                }
                if (!_.has(scriptRules, upperText)) {
//                    console.log("===no rule", upperText);
                    return bot.say(`I'm sorry that is not something I know.  Type MENU or KEY for a list of things I can help you with.`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log("=== p line",line);
                        return wait(5).then(function() {
                            msgLog.responsemessage = line;
                            msgLog.responsetime = new Date;
                            msgLog.responsetype = 'JSON';
                            console.log("=== msgLog  obj",msgLog);
                            logConversation(msgLog);
                            return bot.say(line);
                        });
                    });
                });
                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }
});
<|endoftext|>


<commit_before>//index 0 = TypeRacer, 1 = typrX
var text = ['nonHideableWords unselectable', 'cw-QuotePanel-textToTypePanel'];
var textBoxes = ['txtInput', 'cw-TypedTextBox race-go'];

var website = -1;

//get passage from HTML
var text = document.getElementsByClassName(text[website])[0].textContent;

//get number of words in
var numWords = text.split(" ").length;

var textBox = document.getElementsByClassName('txtInput')[0];
i = 0; //word counter

//put words into textBox
function addWord() {
  word = text.split(" ")[i++]; //split text into words
  textBox.value = word; //insert word
}

addWord();

//key listener for spacebar
window.onkeyup = function(e) {
  var key = e.keyCode ? e.keyCode : e.which;
  if (key == 32) {
    addWord();
  }
};
<commit_msg>Modularized HyperTyper<commit_after>//index 0 = TypeRacer, 1 = typrX
var textOptions = ['nonHideableWords unselectable', 'cw-QuotePanel-textToTypePanel'];
var textBoxOptions = ['txtInput', 'cw-TypedTextBox race-go'];

var website = -1;
for(w = 0; w < textOptions.length; w++) {
  if(!(document.getElementsByClassName(textOptions[w])[0] == null)) {
    website = w;
    break;
  }
}

if(website > -1) {
  //get passage from HTML
  var text = document.getElementsByClassName(textOptions[website])[0].textContent;

  //get number of words in
  var numWords = text.split(" ").length;

  var textBox = document.getElementsByClassName(textBoxOptions[website])[0];
  i = 0; //word counter

  //put words into textBox
  function addWord() {
    if(i>=numWords) return;
    word = text.split(" ")[i++]; //split text into words
    textBox.value = word; //insert word
  }

  addWord();

  //key listener for spacebar
  window.onkeyup = function(e) {
    var key = e.keyCode ? e.keyCode : e.which;
    if (key == 32) {
      addWord();
    }
  };
}
<|endoftext|>