<commit_before>COLOR_LIMITED = "#F7E89D";
COLOR_BULLET = "#F0B2A1";
FAST_FORWARD_SPEED = 3000;
schedule = undefined;
fastForward = false;

/* drawing positions */
var SOUTH_X_POSITION = 150;
var NORTH_X_POSITION = SOUTH_X_POSITION+50;

function zeroPadded(num) {
  return ('0'+num).slice(-2);
}

function dwellTime(now) {
  if(window.schedule == SCHEDULES.weekday) {
    if(now.getHours() >= 7 && now.getHours() <= 9) {
      return 70 * 1000; // morning rush hour
    } else if(now.getHours() >= 17 && now.getHours() <= 19) {
      return 70 * 1000; // evening rush hour
    } else {
      return 40 * 1000; // daytime
    }
  } else {
    // weekend
    return 30 * 1000;
  }
}

function scheduleForDay(today){
  var day = today.getDay();
  if(day == 0){
    return "sunday";
  }
  if(day == 6){
    return "saturday";
  }
  return "weekday";
}

$(function(){
  map = Raphael($('#map').get(0));
  var current = new Date();;
  var dayStart = new Date();
  dayStart.setHours(0);
  dayStart.setMinutes(0);
  dayStart.setSeconds(0);
  var dayEnd = new Date();
  dayEnd.setHours(23);
  dayEnd.setMinutes(59);
  dayEnd.setSeconds(59);

  var timeSlider = $('#timeSlider');
  timeSlider.attr('min', dayStart.getTime());
  timeSlider.attr('max', dayEnd.getTime());
  timeSlider.attr('step', 1000*60); // 1-minute steps
  timeSlider.get(0).value = current.getTime();

  timeSlider.on('change', function() {
    window.virtualTime = parseInt(this.value, 10);
  });

  $("#fastforward").change(function(){
    window.fastForward = this.checked;
  });

  $("input[name=daytype]").click(function(){
    window.schedule = SCHEDULES[$(this).val()];
  });

  $("input[value="+scheduleForDay(new Date())+"]").click();

  drawMap(mileposts);
  window.virtualTime = new Date().getTime();
  animate();
});

var lastFrameTime = new Date();

function animate() {
  requestAnimationFrame(animate);
  var delta = new Date() - lastFrameTime;
  lastFrameTime = new Date();
  if(window.fastForward) {
    delta = delta * 30;
  }

  window.virtualTime += delta;
  var now = new Date(window.virtualTime);
  drawTrains(now);

  var slider = document.getElementById('timeSlider');
  if(Math.abs(slider.value-window.virtualTime) > 1000) {
    $('#now').text(now.getHours() + ":" + zeroPadded(now.getMinutes()) + ":" + zeroPadded(now.getSeconds()));
    slider.value = window.virtualTime;
  }
}

/* ************* Schedule querying ***************** */
function timepointToTime(timepoint){
  var time = new Date();
  time.setHours(timepoint.split(":")[0]);
  time.setMinutes(timepoint.split(":")[1]);
  time.setSeconds(0);
  return time;
}

function getActiveTrains(time) {
  var activeTrains = [];

  for(var train in window.schedule){
    var stops = window.schedule[train];
    var journeyStart = timepointToTime(stops[0][1]);
    var journeyEnd = timepointToTime(stops[stops.length-1][1]);
    if(journeyStart < time && journeyEnd > time){
      activeTrains.push([train, stops]);
    }
  }
  return activeTrains;
}
/* ************* Position calculation ************** */

function trainPositionInterpolated(start, end, time){
  var startTime = timepointToTime(start[1]);
  var endTime = timepointToTime(end[1]);
  endTime = new Date(endTime - dwellTime(endTime));

  var segmentDuration = endTime-startTime;
  var segmentCompleted = time-startTime;

  if(time > endTime){
    position = 1;
  } else {
    position = segmentCompleted/segmentDuration;
  }

  var startMilepost;
  for(var i = 0; i < mileposts.length; i++) {
    if(mileposts[i][0] == start[0]) {
      startMilepost = mileposts[i][1];
      break;
    }
  }

  var endMilepost;
  for(var i = 0; i < mileposts.length; i++) {
    if(mileposts[i][0] == end[0]) {
      endMilepost = mileposts[i][1];
      break;
    }
  }
  if(startMilepost === undefined) {
    throw new Error('Milepost "'+start[0]+'" not found');
  }
  if(endMilepost === undefined) {
    throw new Error('Milepost "'+start[0]+'" not found');
  }

  var distance = endMilepost - startMilepost;
  return startMilepost + (distance * position);
}

/* Given a train, figure out where on the line it should be right now */
function trainPosition(time, stops){
  for(var idx = 0; idx < stops.length; idx++){
    if(time < timepointToTime(stops[idx][1])){
      /* it's between thisTrain[idx-1] and thisTrain[idx] */
      return trainPositionInterpolated(stops[idx-1], stops[idx], time);
    }
  }
}

/* ************* Train drawing ********************* */

var trainsOnMap = {};

function isNorthboundTrain(name) {
  return "13579".indexOf(name[2]) != -1;
}

function isBulletTrain(name) {
  return name[0] == '3' || name[0] == '8';
}
function isLimitedTrain(name) {
  return name[0] == '2';
}

function createTrain(name) {
  map.setStart();

  var background = map.rect(-20, -10, 40, 20, 5);
  if(isBulletTrain(name)) {
    background.attr('fill', '#F0B2A1');
  } else if(isLimitedTrain(name)) {
    background.attr('fill', '#F7E89D');
  } else {
    background.attr('fill', 'white');
  }

  var namePath = map.text(0, 0, name);
  namePath.attr('font-size', 12)

  var train = map.setFinish();

  return train;
}

function drawTrains(time) {
  var activeTrains = getActiveTrains(time);

  for(var i = 0; i < activeTrains.length; i++) {
    var name = activeTrains[i][0];
    var stops = activeTrains[i][1];

    if(!trainsOnMap[name]) {
      var train = createTrain(name);
      trainsOnMap[name] = train;
    } else {
      var train = trainsOnMap[name];
    }

    var x = isNorthboundTrain(name) ? NORTH_X_POSITION : SOUTH_X_POSITION;
    var yPosition = trainPosition(time, stops);
    yPosition = Math.round(yPosition*50) / 50;

    var y = yPosition*verticalScale + 40;
    var t = 't'+x+','+y;

    if(train.yPosition != y) {
      train.transform(t);
      train.yPosition = y;
    }
  }

  for(var name in trainsOnMap) {
    var keep = false;
    for(var i = 0; i < activeTrains.length; i++) {
      if(activeTrains[i][0] == name) {
        keep = true;
        break;
      }
    }
    if(!keep) {
      var el = trainsOnMap[name];
      el.remove();
      delete trainsOnMap[name];
    }
  }
}

/* ************* Background map drawing ************ */

function drawMap(mileposts) {
  var routePath = map.path("M0 0L0 1560M1 0L-10 10M-1 0L10 10");
  routePath.attr('stroke-width', 4);

  var label = map.text(NORTH_X_POSITION, 10,"N");
  label.attr('font-size', 14);
  label.attr('font-weight', 'bold');

  var north = routePath.clone();
  north.transform("t"+NORTH_X_POSITION+",20")

  label = map.text(SOUTH_X_POSITION, 10, "S");
  label.attr('font-size', 14);
  label.attr('font-weight', 'bold');

  var south = routePath.clone();
  south.transform("s1,-1,0,800t"+SOUTH_X_POSITION+",20");

  var topY = 40;
  var bottomY = map.height-40;

  window.verticalScale = (bottomY-topY)/(mileposts[mileposts.length-1][1]);

  for(var i = 0; i < mileposts.length; i++) {
    var name = mileposts[i][0];
    var miles = mileposts[i][1];

    var t = map.text(SOUTH_X_POSITION-30, topY+miles*verticalScale, name);
    t.attr('font-size', 14);
    t.attr('text-anchor', 'end');

    var c = map.circle(NORTH_X_POSITION, topY+miles*verticalScale, 6);
    c.attr('fill', 'white');
    c.attr('stroke', 'red');
    c.attr('stroke-width', 2);

    var c = map.circle(SOUTH_X_POSITION, topY+miles*verticalScale, 6);
    c.attr('fill', 'white');
    c.attr('stroke', 'red');
    c.attr('stroke-width', 2);
  }
  routePath.remove();

}
<commit_msg>Pointers to next stop for each train<commit_after>COLOR_LIMITED = "#F7E89D";
COLOR_BULLET = "#F0B2A1";
FAST_FORWARD_SPEED = 3000;
schedule = undefined;
fastForward = false;

/* drawing positions */
var SOUTH_X_POSITION = 150;
var NORTH_X_POSITION = SOUTH_X_POSITION+50;

function zeroPadded(num) {
  return ('0'+num).slice(-2);
}

function dwellTime(now) {
  if(window.schedule == SCHEDULES.weekday) {
    if(now.getHours() >= 7 && now.getHours() <= 9) {
      return 70 * 1000; // morning rush hour
    } else if(now.getHours() >= 17 && now.getHours() <= 19) {
      return 70 * 1000; // evening rush hour
    } else {
      return 40 * 1000; // daytime
    }
  } else {
    // weekend
    return 30 * 1000;
  }
}

function scheduleForDay(today){
  var day = today.getDay();
  if(day == 0){
    return "sunday";
  }
  if(day == 6){
    return "saturday";
  }
  return "weekday";
}

$(function(){
  map = Raphael($('#map').get(0));
  var current = new Date();;
  var dayStart = new Date();
  dayStart.setHours(0);
  dayStart.setMinutes(0);
  dayStart.setSeconds(0);
  var dayEnd = new Date();
  dayEnd.setHours(23);
  dayEnd.setMinutes(59);
  dayEnd.setSeconds(59);

  var timeSlider = $('#timeSlider');
  timeSlider.attr('min', dayStart.getTime());
  timeSlider.attr('max', dayEnd.getTime());
  timeSlider.attr('step', 1000*60); // 1-minute steps
  timeSlider.get(0).value = current.getTime();

  timeSlider.on('change', function() {
    window.virtualTime = parseInt(this.value, 10);
  });

  $("#fastforward").change(function(){
    window.fastForward = this.checked;
  });

  $("input[name=daytype]").click(function(){
    window.schedule = SCHEDULES[$(this).val()];
  });

  $("input[value="+scheduleForDay(new Date())+"]").click();

  drawMap(mileposts);
  window.virtualTime = new Date().getTime();
  animate();
});

var lastFrameTime = new Date();

function animate() {
  requestAnimationFrame(animate);
  var delta = new Date() - lastFrameTime;
  lastFrameTime = new Date();
  if(window.fastForward) {
    delta = delta * 30;
  }

  window.virtualTime += delta;
  var now = new Date(window.virtualTime);
  drawTrains(now);

  var slider = document.getElementById('timeSlider');
  if(Math.abs(slider.value-window.virtualTime) > 1000) {
    $('#now').text(now.getHours() + ":" + zeroPadded(now.getMinutes()) + ":" + zeroPadded(now.getSeconds()));
    slider.value = window.virtualTime;
  }
}

/* ************* Schedule querying ***************** */
function timepointToTime(timepoint){
  var time = new Date();
  time.setHours(timepoint.split(":")[0]);
  time.setMinutes(timepoint.split(":")[1]);
  time.setSeconds(0);
  return time;
}

function getActiveTrains(time) {
  var activeTrains = [];

  for(var train in window.schedule){
    var stops = window.schedule[train];
    var journeyStart = timepointToTime(stops[0][1]);
    var journeyEnd = timepointToTime(stops[stops.length-1][1]);
    if(journeyStart < time && journeyEnd > time){
      activeTrains.push([train, stops]);
    }
  }
  return activeTrains;
}
/* ************* Position calculation ************** */

function trainPositionInterpolated(start, end, time){
  var startTime = timepointToTime(start[1]);
  var endTime = timepointToTime(end[1]);
  endTime = new Date(endTime - dwellTime(endTime));

  var segmentDuration = endTime-startTime;
  var segmentCompleted = time-startTime;

  if(time > endTime){
    position = 1;
  } else {
    position = segmentCompleted/segmentDuration;
  }

  var startMilepost;
  for(var i = 0; i < mileposts.length; i++) {
    if(mileposts[i][0] == start[0]) {
      startMilepost = mileposts[i][1];
      break;
    }
  }

  var endMilepost;
  for(var i = 0; i < mileposts.length; i++) {
    if(mileposts[i][0] == end[0]) {
      endMilepost = mileposts[i][1];
      break;
    }
  }
  if(startMilepost === undefined) {
    throw new Error('Milepost "'+start[0]+'" not found');
  }
  if(endMilepost === undefined) {
    throw new Error('Milepost "'+start[0]+'" not found');
  }

  var distance = endMilepost - startMilepost;
  return startMilepost + (distance * position);
}

/* Given a train, figure out where on the line it should be right now */
function trainPosition(time, stops){
  for(var idx = 0; idx < stops.length; idx++){
    if(time < timepointToTime(stops[idx][1])){
      /* it's between thisTrain[idx-1] and thisTrain[idx] */
      return trainPositionInterpolated(stops[idx-1], stops[idx], time);
    }
  }
}

function nextStopPosition(time, stops) {
  for(var idx = 0; idx < stops.length; idx++){
    if(time < timepointToTime(stops[idx][1])){
      for(var i = 0; i < mileposts.length; i++) {
        if(mileposts[i][0] == stops[idx][0]) {
          return mileposts[i][1];
        }
      }
    }
  }
  return null;
}

/* ************* Train drawing ********************* */

var trainsOnMap = {};

function isNorthboundTrain(name) {
  return "13579".indexOf(name[2]) != -1;
}

function isBulletTrain(name) {
  return name[0] == '3' || name[0] == '8';
}
function isLimitedTrain(name) {
  return name[0] == '2';
}

function createTrain(name) {
  var pointerToNextStop = map.path("M0 0L10 10");
  pointerToNextStop.attr({
    'arrow-end': 'open',
    'stroke-width': 1.5
  });

  map.setStart();

  var background = map.rect(-20, -10, 40, 20, 5);
  if(isBulletTrain(name)) {
    background.attr('fill', '#F0B2A1');
  } else if(isLimitedTrain(name)) {
    background.attr('fill', '#F7E89D');
  } else {
    background.attr('fill', 'white');
  }

  var namePath = map.text(0, 0, name);
  namePath.attr('font-size', 12)

  var train = map.setFinish();

  train.pointerToNextStop = pointerToNextStop;

  return train;
}

function placeTrain(train, x, y, nextX, nextY) {
  var t = 't'+x+','+y;
  train.transform(t);
  train.yPosition = y;

  var curveShift = (y > nextY) ? 20 : -20;

  var controlX = x + curveShift;
  var controlY = (y+nextY)/2;

  train.pointerToNextStop.attr('path', Raphael.format("M{0},{1}Q{2},{3},{4},{5}", x, y, controlX, controlY, nextX, nextY));
  train.toFront();
}

function drawTrains(time) {
  var activeTrains = getActiveTrains(time);

  for(var i = 0; i < activeTrains.length; i++) {
    var name = activeTrains[i][0];
    var stops = activeTrains[i][1];

    if(!trainsOnMap[name]) {
      var train = createTrain(name);
      trainsOnMap[name] = train;
    } else {
      var train = trainsOnMap[name];
    }

    var x = isNorthboundTrain(name) ? NORTH_X_POSITION : SOUTH_X_POSITION;
    var yPosition = trainPosition(time, stops);
    yPosition = Math.round(yPosition*50) / 50;

    var y = yPosition*verticalScale + 40;

    if(train.yPosition != y) {
      placeTrain(train, x, y, x, nextStopPosition(time, stops)*verticalScale+40);
    }
  }

  for(var name in trainsOnMap) {
    var keep = false;
    for(var i = 0; i < activeTrains.length; i++) {
      if(activeTrains[i][0] == name) {
        keep = true;
        break;
      }
    }
    if(!keep) {
      var el = trainsOnMap[name];
      el.remove();
      delete trainsOnMap[name];
    }
  }
}

/* ************* Background map drawing ************ */

function drawMap(mileposts) {
  var routePath = map.path("M0 0L0 1560M1 0L-10 10M-1 0L10 10");
  routePath.attr('stroke-width', 4);

  var label = map.text(NORTH_X_POSITION, 10,"N");
  label.attr('font-size', 14);
  label.attr('font-weight', 'bold');

  var north = routePath.clone();
  north.transform("t"+NORTH_X_POSITION+",20")

  label = map.text(SOUTH_X_POSITION, 10, "S");
  label.attr('font-size', 14);
  label.attr('font-weight', 'bold');

  var south = routePath.clone();
  south.transform("s1,-1,0,800t"+SOUTH_X_POSITION+",20");

  var topY = 40;
  var bottomY = map.height-40;

  window.verticalScale = (bottomY-topY)/(mileposts[mileposts.length-1][1]);

  for(var i = 0; i < mileposts.length; i++) {
    var name = mileposts[i][0];
    var miles = mileposts[i][1];

    var t = map.text(SOUTH_X_POSITION-30, topY+miles*verticalScale, name);
    t.attr('font-size', 14);
    t.attr('text-anchor', 'end');

    var c = map.circle(NORTH_X_POSITION, topY+miles*verticalScale, 6);
    c.attr('fill', 'white');
    c.attr('stroke', 'red');
    c.attr('stroke-width', 2);

    var c = map.circle(SOUTH_X_POSITION, topY+miles*verticalScale, 6);
    c.attr('fill', 'white');
    c.attr('stroke', 'red');
    c.attr('stroke-width', 2);
  }
  routePath.remove();

}
<|endoftext|>


<commit_before>(function(scope){

	var Soundcloud = function() {
		function _init() {
			SC.initialize({
				client_id: 'f10d28a5f721c5c7355db5c599b5209a'
			});
		}

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				SC.get('/tracks', { q: query }, function(tracks) {
					resolve(tracks || null);
				});
			});
			return promise;
		}

		function _renderResults(results) {
			return results.map(function(track){
				return '

'+unescape(track.title)+'

Downloaded: '+track.download_count+'

Favorited: '+track.favoritings_count+'

';
			}).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;

		_init();
	};

	var Spotify = function() {
		var _request = new XMLHttpRequest();

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				var url = 'http://ws.spotify.com/search/1/track.json?q='+query;
				_request.open('GET', url, true);
				_request.onload = function() {
					var data = JSON.parse(_request.responseText) || null;
					resolve(data);
				};
				_request.onerror = function(obj) {
					reject(obj);
				};
				_request.send();
			});
			return promise;
		}

		function _renderResults(results) {
			var html = _.template(
				'
'+
					'
<%= a %>
'+
					'
<%= t %>
'+
					'
Popularity: <%= p %>
'+
				'
');
			var tracks = results.tracks;
			var output = tracks.map(function(track){
				var popularity = track.popularity;
				var artists = track.artists.map(function(artist){
					return artist.name;
				});
				if (popularity < 0.1) { return null; }
				return html({
					a: artists.join(", "),
					t: track.name,
					p: popularity
				});
			});

			return _.compact(output).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;
	};

	var LastFM = function() {
		var _request = new XMLHttpRequest();

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				var url = 'http://ws.audioscrobbler.com/2.0/?method=artist.search&artist='+query+'&api_key=0d0c017a1e01dfd2284a60c5ccbf565a&format=json';

				_request.open('GET', url, true);
				_request.onload = function(obj) {
					var data = JSON.parse(_request.responseText) || null;
					resolve(data);
				};
				_request.onerror = function(obj) {
					reject(obj);
				};
				_request.send();
			});
			return promise;
		}

		function _renderResults(results) {
			// Array of artists
			var artists = results.results.artistmatches.artist;
			return artists.map(function(artist){
				return '

'+artist.name+'

Listeners: '+artist.listeners+'

';
			}).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;
	};

	var Bandcamp = function() {

	};

	// MODULES ABOVE HERE

	var oSoundcloud = new Soundcloud();
	var oSpotify = new Spotify();
	var oLastFM = new LastFM();

	function _init() {
		_bindEvents();
	}

	function _bindEvents() {
		document.querySelector('input.query').addEventListener('keyup', function(e){
			if (e.keyCode === 13 && this.value !== '') {
				_search(this.value);
				return;
			}
		});
	}

	function _renderResults(results) {
		document.querySelector('.soundcloud-list').innerHTML = oSoundcloud.renderResults(results.soundcloud);
		document.querySelector('.spotify-list').innerHTML = oSpotify.renderResults(results.spotify);
		document.querySelector('.lastfm-list').innerHTML = oLastFM.renderResults(results.lastfm);
	}

	function _search(q) {
		var query = escape(q);
		var promises = [
			oSoundcloud.getResults(query),
			oSpotify.getResults(query),
			oLastFM.getResults(query)
		];

		Promise.all(promises).then(function(result){
			console.log(result);
			_renderResults({
				soundcloud: result[0],
				spotify: result[1],
				lastfm: result[2]
			});
			return {
				soundcloud: result[0],
				spotify: result[1],
				lastfm: result[2]
			};
		});
	}

	_init();

})(window);<commit_msg>focusing text input on load<commit_after>(function(scope){

	var Soundcloud = function() {
		function _init() {
			SC.initialize({
				client_id: 'f10d28a5f721c5c7355db5c599b5209a'
			});
		}

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				SC.get('/tracks', { q: query }, function(tracks) {
					resolve(tracks || null);
				});
			});
			return promise;
		}

		function _renderResults(results) {
			return results.map(function(track){
				return '

'+unescape(track.title)+'

Downloaded: '+track.download_count+'

Favorited: '+track.favoritings_count+'

';
			}).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;

		_init();
	};

	var Spotify = function() {
		var _request = new XMLHttpRequest();

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				var url = 'http://ws.spotify.com/search/1/track.json?q='+query;
				_request.open('GET', url, true);
				_request.onload = function() {
					var data = JSON.parse(_request.responseText) || null;
					resolve(data);
				};
				_request.onerror = function(obj) {
					reject(obj);
				};
				_request.send();
			});
			return promise;
		}

		function _renderResults(results) {
			var html = _.template(
				'
'+
					'
<%= a %>
'+
					'
<%= t %>
'+
					'
Popularity: <%= p %>
'+
				'
');
			var tracks = results.tracks;
			var output = tracks.map(function(track){
				var popularity = track.popularity;
				var artists = track.artists.map(function(artist){
					return artist.name;
				});
				if (popularity < 0.1) { return null; }
				return html({
					a: artists.join(", "),
					t: track.name,
					p: popularity
				});
			});

			return _.compact(output).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;
	};

	var LastFM = function() {
		var _request = new XMLHttpRequest();

		function _getResults(query) {
			var promise = new Promise(function(resolve, reject){
				var url = 'http://ws.audioscrobbler.com/2.0/?method=artist.search&artist='+query+'&api_key=0d0c017a1e01dfd2284a60c5ccbf565a&format=json';

				_request.open('GET', url, true);
				_request.onload = function(obj) {
					var data = JSON.parse(_request.responseText) || null;
					resolve(data);
				};
				_request.onerror = function(obj) {
					reject(obj);
				};
				_request.send();
			});
			return promise;
		}

		function _renderResults(results) {
			// Array of artists
			var artists = results.results.artistmatches.artist;
			return artists.map(function(artist){
				return '

'+artist.name+'

Listeners: '+artist.listeners+'

';
			}).join('');
		}

		this.getResults = _getResults;
		this.renderResults = _renderResults;
	};

	var Bandcamp = function() {

	};

	// MODULES ABOVE HERE

	var oSoundcloud = new Soundcloud();
	var oSpotify = new Spotify();
	var oLastFM = new LastFM();

	function _init() {
		_bindEvents();
		document.querySelector('input.query').focus();
	}

	function _bindEvents() {
		document.querySelector('input.query').addEventListener('keyup', function(e){
			if (e.keyCode === 13 && this.value !== '') {
				_search(this.value);
				return;
			}
		});
	}

	function _renderResults(results) {
		document.querySelector('.soundcloud-list').innerHTML = oSoundcloud.renderResults(results.soundcloud);
		document.querySelector('.spotify-list').innerHTML = oSpotify.renderResults(results.spotify);
		document.querySelector('.lastfm-list').innerHTML = oLastFM.renderResults(results.lastfm);
	}

	function _search(q) {
		var query = escape(q);
		var promises = [
			oSoundcloud.getResults(query),
			oSpotify.getResults(query),
			oLastFM.getResults(query)
		];

		Promise.all(promises).then(function(result){
			console.log(result);
			_renderResults({
				soundcloud: result[0],
				spotify: result[1],
				lastfm: result[2]
			});
			return {
				soundcloud: result[0],
				spotify: result[1],
				lastfm: result[2]
			};
		});
	}

	_init();

})(window);<|endoftext|>


<commit_before>
$(function() {

    // Handle resizing
    function resizeSubBlocks() {
        var marginPct = 0;
        var pageHeight = $(window).height();
        var marginHeight = pageHeight * marginPct / 100;
        var usableHeight = pageHeight - marginHeight * 2;

        // Make sub-blocks half the size of the page
        var subBlockHeight = usableHeight / 2;
        $(".sub-block").css({"height": subBlockHeight});
    }

    $(window).resize(function() {
        resizeSubBlocks();
    });

    resizeSubBlocks();

    // Bootstrap Angular
    var taskMatrixApp = angular.module('taskMatrixApp', ['ngRoute', 'ngAnimate', 'localstorage', 'couchstorage', 'datastorage']);

    taskMatrixApp.factory('dataModelService', ['$rootScope', 'datastorage', function ($rootScope, datastorage) {

        var service = {

            model: {},

            SaveState: function () {
                datastorage.save(service.model, service.RestoreState);
                console.log("saved state");
            },

            RestoreState: function (event, args) {
                //messageArgs = (typeof message === "undefined") ? undefined : {message: message};

                datastorage.load(args.url, function(json) {
                    data = angular.fromJson(json);

					service.model = upgradeSchema(args.url, data);

                    $rootScope.$broadcast("staterestored", args);
                });
            }
        }

        $rootScope.$on("savestate", service.SaveState);
        $rootScope.$on("restorestate", service.RestoreState);

        return service;
    }]);

    taskMatrixApp.config(['$routeProvider', function($routeProvider) {
        $routeProvider.when('', { controller: 'taskMatrixCtrl'})
            .otherwise({ controller: 'taskMatrixCtrl'});
    }]);

    taskMatrixApp.controller('taskMatrixCtrl', ['$scope', '$rootScope', '$route', '$timeout', '$location', 'dataModelService', function($scope, $rootScope, $route, $timeout, $location, dataModelService) {
        $scope.message = "";
        $scope.showMessage = false;
        $scope.dataModelService = dataModelService;
        $scope.categories = dataModelService.model.taskCategories;

        $scope.change = function() {
            $rootScope.$broadcast('savestate');
        };

        $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) {
			var loc = $location.absUrl().replace(/[/:]/g, "-");
            $rootScope.$broadcast('restorestate', {url: loc});
        });

        $scope.$on("staterestored", function(event, args) {
            args = (typeof args === "undefined") ? {} : args;

            // Update shortcut after state has been restored asynchronously
            $scope.categories = dataModelService.model.taskCategories;

            if (args.message) {
                // Let the display stabilise before displaying the message
                $timeout(function() {
                    $scope.message = args.message;
                    $scope.showMessage = true;

                    $timeout(function() {
                        $scope.showMessage = false;
                    }, 2500);
                }, 1000);
            }
        });

        $scope.addTask = function(category, task, persist) {
            task = (typeof task === "undefined") ? {description: "", done: false} : task;
            persist = (typeof persist === "undefined") ? true : persist;

            category.tasks.list.push(task);

            if (persist)
                $rootScope.$broadcast('savestate');
        };

        $scope.addTaskAfterIndex = function(category, index) {
            category.tasks.list.splice(index + 1, 0, {description: "", done: false});
            $rootScope.$broadcast('savestate');
        };

        $scope.deleteTask = function(category, index, persist) {
            persist = (typeof persist === "undefined") ? true : persist;

            task = category.tasks.list.splice(index, 1)[0];

            var target;

            if (index == category.tasks.list.length && index > 0) {
                target = index - 1;
            } else {
                target = index;
            }

            $scope.focusTask(category, target);

            if (persist)
                $rootScope.$broadcast('savestate');

            return task;
        };

        $scope.focusTask = function(category, index) {
            if (category.tasks.list.length > index && index >= 0) {
                setTimeout(function() {
                    $("#" + category.description + "-" + index).focus();
                }, 0);
            }
        };

        $scope.moveTask = function(category, index, direction) {
            var target = index + direction;

            if (target < 0 || target >= category.tasks.list.length) {
                return;
            }

            var tmp = category.tasks.list[index];
            category.tasks.list[index] = category.tasks.list[target];
            category.tasks.list[target] = tmp;

            $rootScope.$broadcast('savestate');

            $scope.focusTask(category, target);
        };

        $scope.moveTaskToCategory = function(destination, source, index) {
            if (destination === source)
                return;

            task = $scope.deleteTask(source, index, false);

            $scope.addTask(destination, task);
        };

        $scope.focusCategory = function(category) {
            if (category.tasks.list.length == 0)
                $scope.addTask(category);
            else
                $scope.focusTask(category, 0);
        }

        $scope.keypress = function($event) {
            var isHandledHere = true;

            if ($event.altKey && !$event.shiftKey) {
                switch($event.keyCode) {
                case 78: // N
                    $scope.focusCategory($scope.categories.urgentImportant);
                    break;
                case 83: // S
                    $scope.focusCategory($scope.categories.important);
                    break;
                case 68: // D
                    $scope.focusCategory($scope.categories.urgent);
                    break;
                case 87: // W
                    $scope.focusCategory($scope.categories.waste);
                    break;
                default:
                    isHandledHere = false;
                }
            } else
                isHandledHere = false;

            if (isHandledHere)
                $event.preventDefault();
        };

        $scope.taskKeypress = function($event, category, index) {
            var isHandledHere = true;

            if ($event.altKey && $event.shiftKey) {
                switch($event.keyCode) {
                case 78: // N
                    $scope.moveTaskToCategory($scope.categories.urgentImportant, category, index);
                    break;
                case 83: // S
                    $scope.moveTaskToCategory($scope.categories.important, category, index);
                    break;
                case 68: // D
                    $scope.moveTaskToCategory($scope.categories.urgent, category, index);
                    break;
                case 87: // W
                    $scope.moveTaskToCategory($scope.categories.waste, category, index);
                    break;
                default:
                    isHandledHere = false;
                }
            } else if ($event.ctrlKey) {
                switch($event.keyCode) {
                case 45: // Insert
                    $scope.addTask(category);
                    break;
                case 8: // Backspace
                case 46: // Delete
                case 13: // Enter/Return
                    $scope.deleteTask(category, index);
                    break;
                case 38: // Up
                    $scope.moveTask(category, index, -1);
                    break;
                case 40: // Down
                    $scope.moveTask(category, index, 1);
                    break;
                default:
                    isHandledHere = false;
                }
            } else if (!$event.ctrlKey && !$event.altKey) {
                switch($event.keyCode) {
                case 13: // Enter/Return
                    $scope.addTaskAfterIndex(category, index);
                    break;
                case 38: // Up
                    $scope.focusTask(category, index - 1);
                    break;
                case 40: // Down
                    $scope.focusTask(category, index + 1);
                    break;
                default:
                    isHandledHere = false;
                }
            }

            if (isHandledHere)
                $event.preventDefault();
        };
    }]);

    taskMatrixApp.directive('taskCategory', function() {
        return {
            restrict: 'E',
            scope: {
                category: '=',
                deleteTask: '&',
                addTask: '&',
                change: '&',
                taskKeypress: '&',
                title: '@'
                   },
            templateUrl: 'angular-templates/task-category.html'
        };
    });

    taskMatrixApp.directive('initialFocus', function() {
        var timer;

        return function(scope, element, attr) {
            if (timer) clearTimeout(timer);

            timer = setTimeout(function() {
                element[0].focus();
            }, 0);
        }
    });

    angular.element(document).ready(function() {
        angular.bootstrap(document, ['taskMatrixApp']);
    });

});

<commit_msg>Cleanup.<commit_after>
$(function() {

    // Handle resizing
    function resizeSubBlocks() {
        var marginPct = 0;
        var pageHeight = $(window).height();
        var marginHeight = pageHeight * marginPct / 100;
        var usableHeight = pageHeight - marginHeight * 2;

        // Make sub-blocks half the size of the page
        var subBlockHeight = usableHeight / 2;
        $(".sub-block").css({"height": subBlockHeight});
    }

    $(window).resize(function() {
        resizeSubBlocks();
    });

    resizeSubBlocks();

    // Bootstrap Angular
    var taskMatrixApp = angular.module('taskMatrixApp', ['ngRoute', 'ngAnimate', 'localstorage', 'couchstorage', 'datastorage']);

    taskMatrixApp.factory('dataModelService', ['$rootScope', 'datastorage', function ($rootScope, datastorage) {

        var service = {

            model: {},

            SaveState: function () {
                datastorage.save(service.model, service.RestoreState);
                console.log("saved state");
            },

            RestoreState: function (event, args) {
                datastorage.load(args.url, function(json) {
                    data = angular.fromJson(json);

					service.model = upgradeSchema(args.url, data);

                    $rootScope.$broadcast("staterestored", args);
                });
            }
        }

        $rootScope.$on("savestate", service.SaveState);
        $rootScope.$on("restorestate", service.RestoreState);

        return service;
    }]);

    taskMatrixApp.config(['$routeProvider', function($routeProvider) {
        $routeProvider.when('', { controller: 'taskMatrixCtrl'})
            .otherwise({ controller: 'taskMatrixCtrl'});
    }]);

    taskMatrixApp.controller('taskMatrixCtrl', ['$scope', '$rootScope', '$route', '$timeout', '$location', 'dataModelService', function($scope, $rootScope, $route, $timeout, $location, dataModelService) {
        $scope.message = "";
        $scope.showMessage = false;
        $scope.dataModelService = dataModelService;
        $scope.categories = dataModelService.model.taskCategories;

        $scope.change = function() {
            $rootScope.$broadcast('savestate');
        };

        $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) {
			var loc = $location.absUrl().replace(/[/:]/g, "-");
            $rootScope.$broadcast('restorestate', {url: loc});
        });

        $scope.$on("staterestored", function(event, args) {
            args = (typeof args === "undefined") ? {} : args;

            // Update shortcut after state has been restored asynchronously
            $scope.categories = dataModelService.model.taskCategories;

            if (args.message) {
                // Let the display stabilise before displaying the message
                $timeout(function() {
                    $scope.message = args.message;
                    $scope.showMessage = true;

                    $timeout(function() {
                        $scope.showMessage = false;
                    }, 2500);
                }, 1000);
            }
        });

        $scope.addTask = function(category, task, persist) {
            task = (typeof task === "undefined") ? {description: "", done: false} : task;
            persist = (typeof persist === "undefined") ? true : persist;

            category.tasks.list.push(task);

            if (persist)
                $rootScope.$broadcast('savestate');
        };

        $scope.addTaskAfterIndex = function(category, index) {
            category.tasks.list.splice(index + 1, 0, {description: "", done: false});
            $rootScope.$broadcast('savestate');
        };

        $scope.deleteTask = function(category, index, persist) {
            persist = (typeof persist === "undefined") ? true : persist;

            task = category.tasks.list.splice(index, 1)[0];

            var target;

            if (index == category.tasks.list.length && index > 0) {
                target = index - 1;
            } else {
                target = index;
            }

            $scope.focusTask(category, target);

            if (persist)
                $rootScope.$broadcast('savestate');

            return task;
        };

        $scope.focusTask = function(category, index) {
            if (category.tasks.list.length > index && index >= 0) {
                setTimeout(function() {
                    $("#" + category.description + "-" + index).focus();
                }, 0);
            }
        };

        $scope.moveTask = function(category, index, direction) {
            var target = index + direction;

            if (target < 0 || target >= category.tasks.list.length) {
                return;
            }

            var tmp = category.tasks.list[index];
            category.tasks.list[index] = category.tasks.list[target];
            category.tasks.list[target] = tmp;

            $rootScope.$broadcast('savestate');

            $scope.focusTask(category, target);
        };

        $scope.moveTaskToCategory = function(destination, source, index) {
            if (destination === source)
                return;

            task = $scope.deleteTask(source, index, false);

            $scope.addTask(destination, task);
        };

        $scope.focusCategory = function(category) {
            if (category.tasks.list.length == 0)
                $scope.addTask(category);
            else
                $scope.focusTask(category, 0);
        }

        $scope.keypress = function($event) {
            var isHandledHere = true;

            if ($event.altKey && !$event.shiftKey) {
                switch($event.keyCode) {
                case 78: // N
                    $scope.focusCategory($scope.categories.urgentImportant);
                    break;
                case 83: // S
                    $scope.focusCategory($scope.categories.important);
                    break;
                case 68: // D
                    $scope.focusCategory($scope.categories.urgent);
                    break;
                case 87: // W
                    $scope.focusCategory($scope.categories.waste);
                    break;
                default:
                    isHandledHere = false;
                }
            } else
                isHandledHere = false;

            if (isHandledHere)
                $event.preventDefault();
        };

        $scope.taskKeypress = function($event, category, index) {
            var isHandledHere = true;

            if ($event.altKey && $event.shiftKey) {
                switch($event.keyCode) {
                case 78: // N
                    $scope.moveTaskToCategory($scope.categories.urgentImportant, category, index);
                    break;
                case 83: // S
                    $scope.moveTaskToCategory($scope.categories.important, category, index);
                    break;
                case 68: // D
                    $scope.moveTaskToCategory($scope.categories.urgent, category, index);
                    break;
                case 87: // W
                    $scope.moveTaskToCategory($scope.categories.waste, category, index);
                    break;
                default:
                    isHandledHere = false;
                }
            } else if ($event.ctrlKey) {
                switch($event.keyCode) {
                case 45: // Insert
                    $scope.addTask(category);
                    break;
                case 8: // Backspace
                case 46: // Delete
                case 13: // Enter/Return
                    $scope.deleteTask(category, index);
                    break;
                case 38: // Up
                    $scope.moveTask(category, index, -1);
                    break;
                case 40: // Down
                    $scope.moveTask(category, index, 1);
                    break;
                default:
                    isHandledHere = false;
                }
            } else if (!$event.ctrlKey && !$event.altKey) {
                switch($event.keyCode) {
                case 13: // Enter/Return
                    $scope.addTaskAfterIndex(category, index);
                    break;
                case 38: // Up
                    $scope.focusTask(category, index - 1);
                    break;
                case 40: // Down
                    $scope.focusTask(category, index + 1);
                    break;
                default:
                    isHandledHere = false;
                }
            }

            if (isHandledHere)
                $event.preventDefault();
        };
    }]);

    taskMatrixApp.directive('taskCategory', function() {
        return {
            restrict: 'E',
            scope: {
                category: '=',
                deleteTask: '&',
                addTask: '&',
                change: '&',
                taskKeypress: '&',
                title: '@'
                   },
            templateUrl: 'angular-templates/task-category.html'
        };
    });

    taskMatrixApp.directive('initialFocus', function() {
        var timer;

        return function(scope, element, attr) {
            if (timer) clearTimeout(timer);

            timer = setTimeout(function() {
                element[0].focus();
            }, 0);
        }
    });

    angular.element(document).ready(function() {
        angular.bootstrap(document, ['taskMatrixApp']);
    });

});

<|endoftext|>


<commit_before>$.doTemplate = (function() {

    // template object constructor
    var t = function (config) {

        this.source = config.source;
        this.data = config.data;

        if (this.source) this.compiler = $.doTemplate.engine(this.source);

        if (this.data) this.compile(config.data);
        else this.compiled = null;

        return this;
    };

    // add some inherited methods
    $.extend(t.prototype, {

        // compile data using the compiler
        compile: function(data) {

            var frag = document.createDocumentFragment(),
                tmp = document.createElement('div'),
                compiler = this.compiler || $.doTemplate.engine(this.source),
                compiled_src, $item,
                add = function(i, object) {

                    // get the compiled source string
                    compiled_src = compiler(object);

                    // create a jQuery object
                    $item = $(compiled_src);

                    // is there some DOM?
                    if ($item[0]) {

                        $item.data('doTemplate', {
                            source: this.source,
                            data: object
                        }).each(function() {
                            frag.appendChild(this);
                        });

                    } else {
                        if (frag.constructor != String) frag = '';
                        frag += compiled_src;
                    };
                };

            // handle correct data
            data = data || this.data || null;
            if (!data) return this;

            // force data into an array if needed
            if (data.constructor != Array) data = [data];
            $.each(data, add);

            // store compiled version as jQuery object (so we can clone it on render)
            if (frag.constructor == String) this.compiled = frag;
            else this.compiled = $(frag);

            return this;
        },

        appendTo: function(selector) {
            return this.render(selector, 'append');
        },

        prependTo: function(selector) {
            return this.render(selector, 'prepend');
        },

        insertBefore: function(selector) {
            return this.render(selector, 'before');
        },

        insertAfter: function(selector) {
            return this.render(selector, 'after');
        },

        replace: function(selector) {
            return this.render(selector, 'replaceWith');
        },

        render: function(selector, type) {

            // we insert a clone, inc data,  so the same compiled template can be inserted multiple time
            $(selector)[type](this.compiled.jquery ? this.compiled.clone(true) : this.compiled);
            return this;
        }
    });

    return function() {

        // reference arguments for better compression
        var args = arguments,
            obj;

        // if no arguments we return an blank template object
        if (args.length === 0) return new t();

        // 1 argument
        if (args.length === 1) {

            // if argument is jquery object or an element we use get to return a new template object
            if (args[0] instanceof jQuery || args[0].nodeType) {

                obj = $.doTemplate.__(args[0]);

                return new t({
                    source: obj.source,
                    data: obj.data
                });
            };

            // if String we assume template source
            if (args[0].constructor == String) {
                return new t({
                    source: args[0]
                });
            };

            // if Object we crete a new template object
            if (args[0].constructor == Object) return new t(args[0]);
        };

        // 2 arguments
        if (args.length === 2) {

            // string source, data
            if (args[0].constructor == String) {

                if (args[1] instanceof jQuery || args[1].nodeType) {

                    obj = $.doTemplate._(args[1]);

                    return new t({
                        source: args[0],
                        data: obj.data
                    });

                } else {

                    return new t({
                        source: args[0],
                        data: args[1]
                    });
                };
            };
        };

        // 3 arguments
        if (args.length === 3) {

            // string source, data, target
            if (args[0].constructor == String && args[1].constructor == Object) {
                return new t({
                    source: args[0],
                    data: args[1],
                    target: args[2]
                });
            };
        };
    };

})();

$.doTemplate._ = function(elem) {

    var obj;

    if (elem instanceof jQuery) elem = elem[0];
    while (elem && elem.nodeType === 1 && !(obj = jQuery.data(elem, 'doTemplate')) && (elem = elem.parentNode)) {};
    return obj || null;

};
<commit_msg>Improved text only string handling by using a textNode in those case to be able to store data and reduce the number of condition statements<commit_after>$.doTemplate = (function() {

    // template object constructor
    var t = function (config) {

        this.source = config.source;
        this.data = config.data;

        if (this.source) this.compiler = $.doTemplate.engine(this.source);

        if (this.data) this.compile(config.data);
        else this.compiled = null;

        return this;
    };

    // add some inherited methods
    $.extend(t.prototype, {

        // compile data using the compiler
        compile: function(data) {

            var frag = document.createDocumentFragment(),
                tmp = document.createElement('div'),
                compiler = this.compiler || $.doTemplate.engine(this.source),
                compiled_src, $item,
                add = function(i, object) {

                    // get the compiled source string
                    compiled_src = compiler(object);

                    // create a jQuery object
                    $item = $(compiled_src);

                    // is there some DOM? If not assume text and use a textNode instead
                    if (!$item[0]) $item = $(document.createTextNode(compiled_src));

                    $item.data('doTemplate', {
                        source: this.source,
                        data: object
                    }).each(function() {
                        frag.appendChild(this);
                    });
                };

            // handle correct data
            data = data || this.data || null;
            if (!data) return this;

            // force data into an array if needed
            if (data.constructor != Array) data = [data];
            $.each(data, add);

            // store compiled version as jQuery object (so we can clone it on render)
            if (frag.constructor == String) this.compiled = frag;
            else this.compiled = $(frag);

            return this;
        },

        appendTo: function(selector) {
            return this.render(selector, 'append');
        },

        prependTo: function(selector) {
            return this.render(selector, 'prepend');
        },

        insertBefore: function(selector) {
            return this.render(selector, 'before');
        },

        insertAfter: function(selector) {
            return this.render(selector, 'after');
        },

        replace: function(selector) {
            return this.render(selector, 'replaceWith');
        },

        render: function(selector, type) {

            // we insert a clone, inc data,  so the same compiled template can be inserted multiple time
            $(selector)[type](this.compiled.clone(true));
            return this;
        }
    });

    return function() {

        // reference arguments for better compression
        var args = arguments,
            obj;

        // if no arguments we return an blank template object
        if (args.length === 0) return new t();

        // 1 argument
        if (args.length === 1) {

            // if argument is jquery object or an element we use get to return a new template object
            if (args[0] instanceof jQuery || args[0].nodeType) {

                obj = $.doTemplate.__(args[0]);

                return new t({
                    source: obj.source,
                    data: obj.data
                });
            };

            // if String we assume template source
            if (args[0].constructor == String) {
                return new t({
                    source: args[0]
                });
            };

            // if Object we crete a new template object
            if (args[0].constructor == Object) return new t(args[0]);
        };

        // 2 arguments
        if (args.length === 2) {

            // string source, data
            if (args[0].constructor == String) {

                if (args[1] instanceof jQuery || args[1].nodeType) {

                    obj = $.doTemplate._(args[1]);

                    return new t({
                        source: args[0],
                        data: obj.data
                    });

                } else {

                    return new t({
                        source: args[0],
                        data: args[1]
                    });
                };
            };
        };

        // 3 arguments
        if (args.length === 3) {

            // string source, data, target
            if (args[0].constructor == String && args[1].constructor == Object) {
                return new t({
                    source: args[0],
                    data: args[1],
                    target: args[2]
                });
            };
        };
    };

})();

$.doTemplate._ = function(elem) {

    var obj;

    if (elem instanceof jQuery) elem = elem[0];
    while (elem && elem.nodeType === 1 && !(obj = jQuery.data(elem, 'doTemplate')) && (elem = elem.parentNode)) {};
    return obj || null;

};
<|endoftext|>


<commit_before>import { version } from '../package.json'
import { Constants } from './constants'
import { Logger } from './logger'
import { Tools } from './tools'
import { Utils } from './utils'

export class Router {

  /**
   * Represents the riot-view-router mixin.
   * @constructor
   * @param {riot} instance - Riot instance to target.
   * @param {object} settings - Router options.
   * @param {array} states - States for router to read from.
   * @returns {Router}
   */
  constructor (instance, settings, states) {
    var self = this

    self.version = version
    self.settings = {}
    self.$riot = instance
    self.$constants = Constants
    self.$logger = new Logger(self)
    self.$tools = new Tools(self)
    self.$utils = new Utils(self)
    self.$events = {}

    Object.defineProperty(self, 'location', {
      get: function() {
        return {
          hash: window.location.hash,
          href: window.location.href
        }
      },
      set: function(location) {
        window.history.pushState(null, null, location)
      }
    })

    self.running = false

    const requiredSettings = ['default']
    const optionalSettings = ['debugging', 'href', 'fallback', 'titleRoot']
    const acceptedSettings = requiredSettings.concat(optionalSettings)
    for (const setting in settings) {
      if (acceptedSettings.indexOf(setting) == -1)
        throw Error(`Unknown setting "${setting}" is not supported`)
    } // # validate router settings

    if (settings.default.indexOf(':') > -1)
      throw Error('Default state route cannot take variable parameters')

    for (const setting in self.settings) {
      const validator = self.$constants.regex.settings[setting]
      if (validator && !setting.match(validator))
        throw Error(`Setting "${setting}" has an invalid value of "${settings[setting]}"`)
    }

    self = Object.assign(self.settings, settings)
    self.settings.debugging = self.settings.debugging || false
    self.settings.marker = self.$constants.defaults.marker

    if (self.href)
      if (self.location.href.indexOf(self.href) == -1)
        throw Error('Defined href not found within location context')

    self.href = self.href || self.location.href.split(self.$constants.defaults.hash)[0]
    if (!self.href.endsWith('/'))
      self.href = `${self.href}/`

    const stateProperties = ['name', 'route', 'tag']
    states = !Array.isArray(states) ? [Object.assign({}, state)] : states.map((state) => Object.assign({}, state))
    stateProperties.forEach((prop) => {
      states.forEach((state) => {
        if (!state[prop])
          throw ReferenceError(`Required state option "${prop}" not specified`)
      })
    }) // # validate state properties
    states.forEach((state) => {
      for (const property in state) {
        const validator = self.$constants.regex.state[property]
        if (validator && !state[property].match(validator))
          throw Error(`State "${state.name}" property "${property}" has an invalid value of "${state[property]}"`)
      }
    }) // # validate state property values
    states.forEach(function(item) {
      item.route = self.$utils.splitRoute(item.route)
    }) // # get route pattern
    self.states = states

    if (!self.$utils.stateByName(self.settings.default))
      throw Error(`State "${self.settings.default}" not found in specified states`)

    if (self.settings.fallback) {
      if (!self.$utils.stateByName(self.settings.fallback))
        throw Error(`Fallback state "${self.settings.fallback}" not found in specified states`)
    }
    else {
      self.$logger.warn(`Fallback state not specified, defaulting to "${self.settings.default}"`)
      self.settings.fallback = self.settings.default
    }
  }

  /**
   * Used to navigate with hash pattern.
   * @param {string} route - Route to relocate to.
   * @param {boolean} push - Push state after navigation.
   * @returns {Promise}
   */
  navigate (route, skipPush) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.warn('(navigate) Router has not yet been started')
        return reject()
      }

      self.location = self.href + self.$constants.defaults.hash + route
      var route_check = setInterval(() => {
        if (self.location.hash == self.$constants.defaults.hash + route) {
          window.clearInterval(route_check)
          if (!skipPush)
            self.push().then(() => self._dispatch('navigation', { route }).then(resolve).catch(resolve))
          else
            self._dispatch('navigation', { route }).then(resolve).catch(resolve)
        }
      }, self.$constants.intervals.navigate)
      setTimeout(reject, self.$constants.defaults.timeout)
    })
  }

  /**
   * Used to change states.
   * @param {string} name - Name of state to transition to.
   * @param {object} opts - Arguments to pass to mounted tag.
   * @returns {Promise}
   */
  push (name, opts) {
    const self = this

    return new Promise((resolve) => {
      if (!self.running) {
        self.$logger.error('(push) Router has not yet been started')
        return reject()
      }

      if (!name) {
        var state = self.$utils.stateByRoute()
        opts = self.$utils.extractRouteVars(state)
      }
      else
        var state = self.$utils.stateByName(name)

      const location = self.location.hash.split(self.$constants.defaults.hash)[1]

      if (location !== state.route.route) {
        if (!state.route.variables.length) {
          self.navigate(state.route.route)
          return resolve() // # assume function will be retriggered
        }
        else {
          self.$logger.warn('(push) State does not match current route.')
          self.$logger.warn('(push) Could not re-route due to route variables.')
        }
      }

      if (self.$state && self.$state.onLeave)
        self.$state.onLeave(state) // # call onLeave, pass old state

      self.$tools.transition(state, opts)

      if (state.onEnter)
        state.onEnter(state) // # call onEnter, pass new state

      self.$state = state
      self._dispatch('push').then(resolve).catch(resolve)
    })
  }

  /**
   * Used to initialize the router and listeners.
   * @returns {Promise}
   */
  start () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.running = true

        function _start() {
          var view_check = window.setInterval(() => {
            const context = document.querySelector(self.settings.marker) || document.querySelector(`[${self.settings.marker}]`)
            if (context) {
              self.context = context
              self.push() // # route to initial state
              window.onhashchange = () => self.push()
              window.clearInterval(view_check)
              self._dispatch('start').then(resolve).catch(resolve)
            }
          }, self.$constants.intervals.start) // # search for view context
          setTimeout(reject, self.$constants.defaults.timeout)
        }

        if (self.location.hash.split(self.$constants.defaults.hash).length !== 2) {
          self.navigate(
            self.$utils.stateByName(
              self.settings.default
            ).route.route, true).then(_start)
        }
        else
          _start()
      }
      else {
        self.$logger.error('(start) Router already running')
        reject()
      }
    })
  }

  /**
   * Used to stop the router and listeners.
   * @returns {Promise}
   */
  stop () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(stop) Router was not running')
        reject()
      }

      self.running = false
      delete window.onhashchange
      self._dispatch('stop').then(resolve).catch(resolve)
    })
  }

  /**
   * Used to refresh the current route.
   */
  reload () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(reload) Router has not yet been started')
        return reject()
      }

      self.push().then(() => {
        self._dispatch('reload').then(resolve).catch(resolve)
      })
    })
  }

  /**
   * Used to register router specific events.
   * @param {string} event - Name of event to register.
   * @param {function} handler - Function to execute on listener.
   * @returns {Promise}
   */
  on (event, handler) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!event || self.$constants.events.supported.indexOf(event) < -1) {
        self.$logger.error(`(on) "${event}" is not a supported event`)
        reject()
      }
      else {
        self.$events[event] = handler
        resolve()
      }
    })
  }

  /**
   * Used to dispatch router specific events.
   * @param {string} event - Event to dispatch.
   * @param {object} params - Parameters to pass to event handler.
   * @returns {Promise}
   */
  _dispatch (event, params) {
    const self = this

    return new Promise((resolve) => {
      if (!event || self.$constants.events.supported.indexOf(event) < -1) {
        self.$logger.error(`(dispatch) "${event}" is not a supported event`)
        reject()
      }
      if (typeof self.$events[event] == 'function')
        resolve(self.$events[event](params))
      else
        reject()
    })
  }

}
<commit_msg>Fixed settings href reference<commit_after>import { version } from '../package.json'
import { Constants } from './constants'
import { Logger } from './logger'
import { Tools } from './tools'
import { Utils } from './utils'

export class Router {

  /**
   * Represents the riot-view-router mixin.
   * @constructor
   * @param {riot} instance - Riot instance to target.
   * @param {object} settings - Router options.
   * @param {array} states - States for router to read from.
   * @returns {Router}
   */
  constructor (instance, settings, states) {
    var self = this

    self.version = version
    self.settings = {}
    self.$riot = instance
    self.$constants = Constants
    self.$logger = new Logger(self)
    self.$tools = new Tools(self)
    self.$utils = new Utils(self)
    self.$events = {}

    Object.defineProperty(self, 'location', {
      get: function() {
        return {
          hash: window.location.hash,
          href: window.location.href
        }
      },
      set: function(location) {
        window.history.pushState(null, null, location)
      }
    })

    self.running = false

    const requiredSettings = ['default']
    const optionalSettings = ['debugging', 'href', 'fallback', 'titleRoot']
    const acceptedSettings = requiredSettings.concat(optionalSettings)
    for (const setting in settings) {
      if (acceptedSettings.indexOf(setting) == -1)
        throw Error(`Unknown setting "${setting}" is not supported`)
    } // # validate router settings

    if (settings.default.indexOf(':') > -1)
      throw Error('Default state route cannot take variable parameters')

    for (const setting in self.settings) {
      const validator = self.$constants.regex.settings[setting]
      if (validator && !setting.match(validator))
        throw Error(`Setting "${setting}" has an invalid value of "${settings[setting]}"`)
    }

    self = Object.assign(self.settings, settings)
    self.settings.debugging = self.settings.debugging || false
    self.settings.marker = self.$constants.defaults.marker

    if (self.href)
      if (self.location.href.indexOf(self.href) == -1)
        throw Error('Defined href not found within location context')

    self.settings.href = self.settings.href || self.location.href.split(self.$constants.defaults.hash)[0]
    if (!self.settings.href.endsWith('/'))
      self.href = `${self.href}/`

    const stateProperties = ['name', 'route', 'tag']
    states = !Array.isArray(states) ? [Object.assign({}, state)] : states.map((state) => Object.assign({}, state))
    stateProperties.forEach((prop) => {
      states.forEach((state) => {
        if (!state[prop])
          throw ReferenceError(`Required state option "${prop}" not specified`)
      })
    }) // # validate state properties
    states.forEach((state) => {
      for (const property in state) {
        const validator = self.$constants.regex.state[property]
        if (validator && !state[property].match(validator))
          throw Error(`State "${state.name}" property "${property}" has an invalid value of "${state[property]}"`)
      }
    }) // # validate state property values
    states.forEach(function(item) {
      item.route = self.$utils.splitRoute(item.route)
    }) // # get route pattern
    self.states = states

    if (!self.$utils.stateByName(self.settings.default))
      throw Error(`State "${self.settings.default}" not found in specified states`)

    if (self.settings.fallback) {
      if (!self.$utils.stateByName(self.settings.fallback))
        throw Error(`Fallback state "${self.settings.fallback}" not found in specified states`)
    }
    else {
      self.$logger.warn(`Fallback state not specified, defaulting to "${self.settings.default}"`)
      self.settings.fallback = self.settings.default
    }
  }

  /**
   * Used to navigate with hash pattern.
   * @param {string} route - Route to relocate to.
   * @param {boolean} push - Push state after navigation.
   * @returns {Promise}
   */
  navigate (route, skipPush) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.warn('(navigate) Router has not yet been started')
        return reject()
      }

      self.location = self.href + self.$constants.defaults.hash + route
      var route_check = setInterval(() => {
        if (self.location.hash == self.$constants.defaults.hash + route) {
          window.clearInterval(route_check)
          if (!skipPush)
            self.push().then(() => self._dispatch('navigation', { route }).then(resolve).catch(resolve))
          else
            self._dispatch('navigation', { route }).then(resolve).catch(resolve)
        }
      }, self.$constants.intervals.navigate)
      setTimeout(reject, self.$constants.defaults.timeout)
    })
  }

  /**
   * Used to change states.
   * @param {string} name - Name of state to transition to.
   * @param {object} opts - Arguments to pass to mounted tag.
   * @returns {Promise}
   */
  push (name, opts) {
    const self = this

    return new Promise((resolve) => {
      if (!self.running) {
        self.$logger.error('(push) Router has not yet been started')
        return reject()
      }

      if (!name) {
        var state = self.$utils.stateByRoute()
        opts = self.$utils.extractRouteVars(state)
      }
      else
        var state = self.$utils.stateByName(name)

      const location = self.location.hash.split(self.$constants.defaults.hash)[1]

      if (location !== state.route.route) {
        if (!state.route.variables.length) {
          self.navigate(state.route.route)
          return resolve() // # assume function will be retriggered
        }
        else {
          self.$logger.warn('(push) State does not match current route.')
          self.$logger.warn('(push) Could not re-route due to route variables.')
        }
      }

      if (self.$state && self.$state.onLeave)
        self.$state.onLeave(state) // # call onLeave, pass old state

      self.$tools.transition(state, opts)

      if (state.onEnter)
        state.onEnter(state) // # call onEnter, pass new state

      self.$state = state
      self._dispatch('push').then(resolve).catch(resolve)
    })
  }

  /**
   * Used to initialize the router and listeners.
   * @returns {Promise}
   */
  start () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.running = true

        function _start() {
          var view_check = window.setInterval(() => {
            const context = document.querySelector(self.settings.marker) || document.querySelector(`[${self.settings.marker}]`)
            if (context) {
              self.context = context
              self.push() // # route to initial state
              window.onhashchange = () => self.push()
              window.clearInterval(view_check)
              self._dispatch('start').then(resolve).catch(resolve)
            }
          }, self.$constants.intervals.start) // # search for view context
          setTimeout(reject, self.$constants.defaults.timeout)
        }

        if (self.location.hash.split(self.$constants.defaults.hash).length !== 2) {
          self.navigate(
            self.$utils.stateByName(
              self.settings.default
            ).route.route, true).then(_start)
        }
        else
          _start()
      }
      else {
        self.$logger.error('(start) Router already running')
        reject()
      }
    })
  }

  /**
   * Used to stop the router and listeners.
   * @returns {Promise}
   */
  stop () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(stop) Router was not running')
        reject()
      }

      self.running = false
      delete window.onhashchange
      self._dispatch('stop').then(resolve).catch(resolve)
    })
  }

  /**
   * Used to refresh the current route.
   */
  reload () {
    const self = this

    return new Promise((resolve, reject) => {
      if (!self.running) {
        self.$logger.error('(reload) Router has not yet been started')
        return reject()
      }

      self.push().then(() => {
        self._dispatch('reload').then(resolve).catch(resolve)
      })
    })
  }

  /**
   * Used to register router specific events.
   * @param {string} event - Name of event to register.
   * @param {function} handler - Function to execute on listener.
   * @returns {Promise}
   */
  on (event, handler) {
    const self = this

    return new Promise((resolve, reject) => {
      if (!event || self.$constants.events.supported.indexOf(event) < -1) {
        self.$logger.error(`(on) "${event}" is not a supported event`)
        reject()
      }
      else {
        self.$events[event] = handler
        resolve()
      }
    })
  }

  /**
   * Used to dispatch router specific events.
   * @param {string} event - Event to dispatch.
   * @param {object} params - Parameters to pass to event handler.
   * @returns {Promise}
   */
  _dispatch (event, params) {
    const self = this

    return new Promise((resolve) => {
      if (!event || self.$constants.events.supported.indexOf(event) < -1) {
        self.$logger.error(`(dispatch) "${event}" is not a supported event`)
        reject()
      }
      if (typeof self.$events[event] == 'function')
        resolve(self.$events[event](params))
      else
        reject()
    })
  }

}
<|endoftext|>


<commit_before>var grades = [
    { grade : 1, min: .90 },
    { grade : 2, min: .79 },
    { grade : 3, min: .68 },
    { grade : 4, min: .50 },
    { grade : 5, min: .0 }
];

$(document).ready(function () {
    loadWrongQuestions();
    $('#challenge, #voctable, #mistakes, #results').hide();
    $('#load-container').fadeOut('fast');
    loadLocalFile();
    $.getJSON('dir.json?v=' + getRandVersion(), function (data) {
        var list = $('
').appendTo($("#files"));
        data.files.forEach(function (file) {
            var row = $('

').appendTo(list);
            row.append('' + file.replace('.csv', '').replace(/_/g, ' ') + '	');
            $('<button type="button" class="btn">Exercise (in order)</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), false, false);
            });

            $('<button type="button" class="btn">Exercise (shuffle)</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), true, false);
            });

            $('<button type="button" class="btn">Vocabulary table</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), false, true);
            });

            $('<button type="button" class="btn">View/Download</button>	').appendTo(row).on('click', function () {
                window.location = 'csv/' + file;
            });
        });
        list.fadeIn('fast');

        $('#lang1-2').on('change', function () {
            flipDirection = !($(this).is(':checked'));
            loadQuestion();
        });

        $('#lang2-1').on('change', function () {
            flipDirection = ($(this).is(':checked'));
            loadQuestion();
        });

        $('#submit').on('click', submit);

        $('#clear-wrong').on('click', function () {
            if (!confirm("Are you sure you want to clear your list of mistakes?"))
                return;
            clearWrongQuestions();
        });

        $('#practise-mistakes').on('click', function() {
            $('#show-mistakes').prop('checked', false);
            practiceMistakes();
        });
        $('#skip').on('click', nextQuestion);

        $('#show-mistakes').change(function() {
            if($(this).is(":checked")) {
                $('#wrongquestions').show('slow');
            }
            else {
                $('#wrongquestions').hide('slow');
            }
        });

        $("#answer").keyup(function(event){
            if(event.keyCode == 13){
                $("#submit").click();
            }
        });

        $('.back').on('click', function () {
            showMainMenu();
            clearState();
            removeLocalFile();
        });
    });
});

var topic = "";
var lang1 = [];
var lang2 = [];
var questionNum = 0;
var question = "";
var tries = 0;
var rightQuestions = 0;
var flipDirection = false;
var wrongQuestions = [];

function loadFile(file, button, doShuffle, showTable) {
    $('#files').addClass('loading');
    lang1 = [];
    lang2 = [];
    $.get('csv/' + file + "?v=" + getRandVersion(), function (data) {
        var lines = data.split('\n');
        topic = lines[0];
        $('.topic').text(topic);
        lines.splice(0, 1);
        if (doShuffle) {
            shuffle(lines);
        }
        loadData(lines, showTable);
        saveFileLocal({topic: topic, lines: lines});
        saveState();
    });
}

function loadData(lines, showTable) {
    for(var i = 0; i < lines.length; i++) {
        var line = lines[i];
        var split = line.split(';');
        if (split.length < 2)
            continue;
        lang1[i] = split[0];
        lang2[i] = split[1];
    }

    questionNum = 0;
    rightQuestions = 0;
    if (showTable) {
        loadVocTable();
        $('#voctable').show();
    }
    else {
        loadQuestion();
        $('#challenge, #mistakes').show();
    }
    $("#lang-direction").show();
    $('#files').hide();
}

function loadLocalFile() {
    var item = localStorage.getItem('lastfile');
    if (item === null)
        return;

    var parsed = JSON.parse(item);
    topic = parsed.topic;
    $('.topic').text(topic);
    loadData(parsed.lines, false);
    loadState();
}

function loadQuestion() {
    var lengthPercent = (questionNum+1) / lang1.length * 100;
    $('#progress-bar').css('width', lengthPercent + "%").text((questionNum+1) + " of " + lang1.length);
    tries = 0;
    $('#answer').removeClass('right').removeClass('wrong').prop('readonly', false).val('').focus();
    $('#submit').removeClass('moveon').text('Submit');
    $('.topic').text(topic);
    question = (flipDirection ? lang1 : lang2)[questionNum];
    $('#question').text(question);
    updateWrongQuestions();
}

function nextQuestion() {
    questionNum++;
    if (questionNum > lang1.length - 1) {
        showResults();
        clearState();
        removeLocalFile();
    }
    else {
        loadQuestion();
    }
}

function showResults() {
    $('#results').show();
    $('#challenge').hide();
    $('#topic-result').text(topic);
    $('#question-num').text(questionNum);
    var percentage = rightQuestions/questionNum;
    $('#right-percentage').text(Math.round(percentage*100));
    $('#right-num').text(rightQuestions);
    $('#wrong-num').text(questionNum - rightQuestions);
    var currentGrade = 5;
    for (var i = 0; i < grades.length; i++) {
        var grade = grades[0];
        if (percentage >= grade.min) {
            currentGrade = grade.grade;
            break;
        }
    }
    $('#grade').text(currentGrade);
}

function submit() {
    if ($(this).hasClass('moveon')) {
        nextQuestion();
        saveState();
        return;
    }
    var answerElem = $('#answer');
    var answer = answerElem.val().trim();//.toLowerCase();
    //if (answer === '')
    //    return;

    var rightAnswerTemplate = (flipDirection ? lang2 : lang1)[questionNum].trim();
    var templateNoBrackets = rightAnswerTemplate.replace(/ *\([^)]*\)*/g, "").trim();
    var rightAnswers = templateNoBrackets.split('/');
    var rightAnswersWithoutTo = templateNoBrackets.replace('to ', '').split('/');

    var maxTries = 1;
    if(rightAnswers.indexOf(answer) !== -1 || rightAnswersWithoutTo.indexOf(answer) !== -1
        || answer === templateNoBrackets || answer === templateNoBrackets.replace('to ', '')) {
        answerElem.addClass('right').removeClass('wrong');
        if (tries <= maxTries) {
            rightQuestions++;
        }
        showAnswer(rightAnswerTemplate);
    }
    else {
        tries++;
        answerElem.addClass('wrong').effect('shake', function () {
            answerElem.focus();
        });
        $(this).text('Try again!');

        if (tries > maxTries) {
            var wrongQuestion = {question: question, answer: rightAnswerTemplate};
            if (!wrongQuestions.find(function (question) {
                    return question.question === wrongQuestion.question && question.answer === wrongQuestion.answer;
                })) {
                wrongQuestions.push(wrongQuestion);
            }
            showAnswer(rightAnswerTemplate);
        }
    }
    saveState();
}

function showAnswer(answer) {
    $('#answer').val(answer).prop('readonly', true);
    $('#submit').addClass('moveon').text('Move on');
}

function practiceMistakes() {
    topic = "Practice your mistakes";

    lang1 = [];
    lang2 = [];
    for (var i = 0; i < wrongQuestions.length; i++) {
        lang2[i] = wrongQuestions[i].question;
        lang1[i] = wrongQuestions[i].answer;
    }

    questionNum = 0;
    rightQuestions = 0;
    flipDirection = false;
    loadQuestion();
    $("#lang1-2").prop('checked', true);
    $("#lang-direction").slideUp();
    $('#wrongquestions').hide('slow');
    $('#results').hide();
    $('#challenge').show();
}

function loadVocTable() {
    var content = '';
    lang1.forEach(function (question, index) {
        content += '
' + question + '	' + lang2[index] + '	' + '
';
    });
    $('#voctable-table').html(content);
}

function saveFileLocal(contents) {
    localStorage.setItem('lastfile', JSON.stringify(contents));
}

function removeLocalFile() {
    localStorage.removeItem('lastfile');
}

function loadState() {
    var state = localStorage.getItem('state');
    if (state === null)
        return;

    var parsed = JSON.parse(state);
    questionNum = parsed.questionNum;
    question = parsed.question;
    rightQuestions = parsed.rightQuestions;
    flipDirection = parsed.flipDirection;
    tries = parsed.tries;
    loadQuestion();
}

function saveState() {
    var state = {questionNum: questionNum, question: question, rightQuestions: rightQuestions, flipDirection: flipDirection, tries: tries};
    localStorage.setItem('state', JSON.stringify(state));
}

function clearState() {
    localStorage.removeItem('state');
}

function loadWrongQuestions() {
    var item = localStorage.getItem('wrongquestions');
    if (item === null)
        return;
    wrongQuestions = JSON.parse(item).data;
    showWrongQuestions()
}

function updateWrongQuestions() {
    localStorage.setItem('wrongquestions', JSON.stringify({data: wrongQuestions}));
    showWrongQuestions();
}

function clearWrongQuestions() {
    wrongQuestions = [];
    localStorage.removeItem('wrongquestions');
    showWrongQuestions();
}

function showWrongQuestions() {
    var content = '';
    wrongQuestions.forEach(function (question, index) {
        content += '
' + question.question + '	' + question.answer + '	' +
            '<button class="btn mistake" type="button" data-index="' + index + '" onclick="removeMistakeAnimate($(this), ' + index + ')">Remove</button>
';
    });
    $('#wrongquestions').html(content);
}

function removeMistakeAnimate(pointer, index) {
    var elem = pointer.parent($('tr'));
    elem.slideUp('fast', function () {
        removeMistake(index);
    });
}

function removeMistake(index) {
    wrongQuestions.splice(index, 1);
    updateWrongQuestions();
}

function shuffle(a) {
    var j, x, i;
    for (i = a.length; i; i--) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
    }
}

function getRandVersion() {
    var dateInSeconds = Math.round(new Date().getTime() / 1000).toString();
    return dateInSeconds + (Math.random() * 10000).toString();
}

function showMainMenu() {
    $('#files').show().removeClass('loading');
    $('#challenge, #voctable, #mistakes, #results').hide();
}<commit_msg>added empty line to lang array if '\n' exists after last entry. FIX: removed last line of csv if empty<commit_after>var grades = [
    { grade : 1, min: .90 },
    { grade : 2, min: .79 },
    { grade : 3, min: .68 },
    { grade : 4, min: .50 },
    { grade : 5, min: .0 }
];

$(document).ready(function () {
    loadWrongQuestions();
    $('#challenge, #voctable, #mistakes, #results').hide();
    $('#load-container').fadeOut('fast');
    loadLocalFile();
    $.getJSON('dir.json?v=' + getRandVersion(), function (data) {
        var list = $('
').appendTo($("#files"));
        data.files.forEach(function (file) {
            var row = $('

').appendTo(list);
            row.append('' + file.replace('.csv', '').replace(/_/g, ' ') + '	');
            $('<button type="button" class="btn">Exercise (in order)</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), false, false);
            });

            $('<button type="button" class="btn">Exercise (shuffle)</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), true, false);
            });

            $('<button type="button" class="btn">Vocabulary table</button>	').appendTo(row).on('click', function () {
                loadFile(file, $(this), false, true);
            });

            $('<button type="button" class="btn">View/Download</button>	').appendTo(row).on('click', function () {
                window.location = 'csv/' + file;
            });
        });
        list.fadeIn('fast');

        $('#lang1-2').on('change', function () {
            flipDirection = !($(this).is(':checked'));
            loadQuestion();
        });

        $('#lang2-1').on('change', function () {
            flipDirection = ($(this).is(':checked'));
            loadQuestion();
        });

        $('#submit').on('click', submit);

        $('#clear-wrong').on('click', function () {
            if (!confirm("Are you sure you want to clear your list of mistakes?"))
                return;
            clearWrongQuestions();
        });

        $('#practise-mistakes').on('click', function() {
            $('#show-mistakes').prop('checked', false);
            practiceMistakes();
        });
        $('#skip').on('click', nextQuestion);

        $('#show-mistakes').change(function() {
            if($(this).is(":checked")) {
                $('#wrongquestions').show('slow');
            }
            else {
                $('#wrongquestions').hide('slow');
            }
        });

        $("#answer").keyup(function(event){
            if(event.keyCode == 13){
                $("#submit").click();
            }
        });

        $('.back').on('click', function () {
            showMainMenu();
            clearState();
            removeLocalFile();
        });
    });
});

var topic = "";
var lang1 = [];
var lang2 = [];
var questionNum = 0;
var question = "";
var tries = 0;
var rightQuestions = 0;
var flipDirection = false;
var wrongQuestions = [];

function loadFile(file, button, doShuffle, showTable) {
    $('#files').addClass('loading');
    lang1 = [];
    lang2 = [];
    $.get('csv/' + file + "?v=" + getRandVersion(), function (data) {
        var lines = data.split('\n');
        topic = lines[0];
        $('.topic').text(topic);
        lines.splice(0, 1);

        if(lines[lines.length-1] == "") {
        	lines.splice(-1,1);
        }

        if (doShuffle) {
            shuffle(lines);
        }
        loadData(lines, showTable);
        saveFileLocal({topic: topic, lines: lines});
        saveState();
    });
}

function loadData(lines, showTable) {
    for(var i = 0; i < lines.length; i++) {
        var line = lines[i];
        var split = line.split(';');
        if (split.length < 2)
            continue;
        lang1[i] = split[0];
        lang2[i] = split[1];
    }

    questionNum = 0;
    rightQuestions = 0;
    if (showTable) {
        loadVocTable();
        $('#voctable').show();
    }
    else {
        loadQuestion();
        $('#challenge, #mistakes').show();
    }
    $("#lang-direction").show();
    $('#files').hide();
}

function loadLocalFile() {
    var item = localStorage.getItem('lastfile');
    if (item === null)
        return;

    var parsed = JSON.parse(item);
    topic = parsed.topic;
    $('.topic').text(topic);
    loadData(parsed.lines, false);
    loadState();
}

function loadQuestion() {
    var lengthPercent = (questionNum+1) / lang1.length * 100;
    $('#progress-bar').css('width', lengthPercent + "%").text((questionNum+1) + " of " + lang1.length);
    tries = 0;
    $('#answer').removeClass('right').removeClass('wrong').prop('readonly', false).val('').focus();
    $('#submit').removeClass('moveon').text('Submit');
    $('.topic').text(topic);
    question = (flipDirection ? lang1 : lang2)[questionNum];
    $('#question').text(question);
    updateWrongQuestions();
}

function nextQuestion() {
    questionNum++;
    if (questionNum > lang1.length - 1) {
        showResults();
        clearState();
        removeLocalFile();
    }
    else {
        loadQuestion();
    }
}

function showResults() {
    $('#results').show();
    $('#challenge').hide();
    $('#topic-result').text(topic);
    $('#question-num').text(questionNum);
    var percentage = rightQuestions/questionNum;
    $('#right-percentage').text(Math.round(percentage*100));
    $('#right-num').text(rightQuestions);
    $('#wrong-num').text(questionNum - rightQuestions);
    var currentGrade = 5;
    for (var i = 0; i < grades.length; i++) {
        var grade = grades[0];
        if (percentage >= grade.min) {
            currentGrade = grade.grade;
            break;
        }
    }
    $('#grade').text(currentGrade);
}

function submit() {
    if ($(this).hasClass('moveon')) {
        nextQuestion();
        saveState();
        return;
    }
    var answerElem = $('#answer');
    var answer = answerElem.val().trim();//.toLowerCase();
    //if (answer === '')
    //    return;

    var rightAnswerTemplate = (flipDirection ? lang2 : lang1)[questionNum].trim();
    var templateNoBrackets = rightAnswerTemplate.replace(/ *\([^)]*\)*/g, "").trim();
    var rightAnswers = templateNoBrackets.split('/');
    var rightAnswersWithoutTo = templateNoBrackets.replace('to ', '').split('/');

    var maxTries = 1;
    if(rightAnswers.indexOf(answer) !== -1 || rightAnswersWithoutTo.indexOf(answer) !== -1
        || answer === templateNoBrackets || answer === templateNoBrackets.replace('to ', '')) {
        answerElem.addClass('right').removeClass('wrong');
        if (tries <= maxTries) {
            rightQuestions++;
        }
        showAnswer(rightAnswerTemplate);
    }
    else {
        tries++;
        answerElem.addClass('wrong').effect('shake', function () {
            answerElem.focus();
        });
        $(this).text('Try again!');

        if (tries > maxTries) {
            var wrongQuestion = {question: question, answer: rightAnswerTemplate};
            if (!wrongQuestions.find(function (question) {
                    return question.question === wrongQuestion.question && question.answer === wrongQuestion.answer;
                })) {
                wrongQuestions.push(wrongQuestion);
            }
            showAnswer(rightAnswerTemplate);
        }
    }
    saveState();
}

function showAnswer(answer) {
    $('#answer').val(answer).prop('readonly', true);
    $('#submit').addClass('moveon').text('Move on');
}

function practiceMistakes() {
    topic = "Practice your mistakes";

    lang1 = [];
    lang2 = [];
    for (var i = 0; i < wrongQuestions.length; i++) {
        lang2[i] = wrongQuestions[i].question;
        lang1[i] = wrongQuestions[i].answer;
    }

    questionNum = 0;
    rightQuestions = 0;
    flipDirection = false;
    loadQuestion();
    $("#lang1-2").prop('checked', true);
    $("#lang-direction").slideUp();
    $('#wrongquestions').hide('slow');
    $('#results').hide();
    $('#challenge').show();
}

function loadVocTable() {
    var content = '';
    lang1.forEach(function (question, index) {
        content += '
' + question + '	' + lang2[index] + '	' + '
';
    });
    $('#voctable-table').html(content);
}

function saveFileLocal(contents) {
    localStorage.setItem('lastfile', JSON.stringify(contents));
}

function removeLocalFile() {
    localStorage.removeItem('lastfile');
}

function loadState() {
    var state = localStorage.getItem('state');
    if (state === null)
        return;

    var parsed = JSON.parse(state);
    questionNum = parsed.questionNum;
    question = parsed.question;
    rightQuestions = parsed.rightQuestions;
    flipDirection = parsed.flipDirection;
    tries = parsed.tries;
    loadQuestion();
}

function saveState() {
    var state = {questionNum: questionNum, question: question, rightQuestions: rightQuestions, flipDirection: flipDirection, tries: tries};
    localStorage.setItem('state', JSON.stringify(state));
}

function clearState() {
    localStorage.removeItem('state');
}

function loadWrongQuestions() {
    var item = localStorage.getItem('wrongquestions');
    if (item === null)
        return;
    wrongQuestions = JSON.parse(item).data;
    showWrongQuestions()
}

function updateWrongQuestions() {
    localStorage.setItem('wrongquestions', JSON.stringify({data: wrongQuestions}));
    showWrongQuestions();
}

function clearWrongQuestions() {
    wrongQuestions = [];
    localStorage.removeItem('wrongquestions');
    showWrongQuestions();
}

function showWrongQuestions() {
    var content = '';
    wrongQuestions.forEach(function (question, index) {
        content += '
' + question.question + '	' + question.answer + '	' +
            '<button class="btn mistake" type="button" data-index="' + index + '" onclick="removeMistakeAnimate($(this), ' + index + ')">Remove</button>
';
    });
    $('#wrongquestions').html(content);
}

function removeMistakeAnimate(pointer, index) {
    var elem = pointer.parent($('tr'));
    elem.slideUp('fast', function () {
        removeMistake(index);
    });
}

function removeMistake(index) {
    wrongQuestions.splice(index, 1);
    updateWrongQuestions();
}

function shuffle(a) {
    var j, x, i;
    for (i = a.length; i; i--) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
    }
}

function getRandVersion() {
    var dateInSeconds = Math.round(new Date().getTime() / 1000).toString();
    return dateInSeconds + (Math.random() * 10000).toString();
}

function showMainMenu() {
    $('#files').show().removeClass('loading');
    $('#challenge, #voctable, #mistakes, #results').hide();
}<|endoftext|>


<commit_before>var app = {
    changePicture = function(event){
     event.preventDefault();
    if (!navigator.camera) {
        app.showAlert("Camera API not supported", "Error");
        return;
    }
    var options =   {   quality: 50,
                        destinationType: Camera.DestinationType.DATA_URL,
                        sourceType: 1,      // 0:Photo Library, 1=Camera, 2=Saved Photo Album
                        encodingType: 0     // 0=JPG 1=PNG
                    };

    navigator.camera.getPicture(
        function(imageData) {
            $('.employee-image', this.el).attr('src', "data:image/jpeg;base64," + imageData);
        },
        function() {
            app.showAlert('Error taking picture', 'Error');
        },
        options);

    return false;
    },

    findByName: function() {
        console.log('findByName');
        this.store.findByName($('.search-key').val(), function(employees) {
            var l = employees.length;
            var e;
            $('.employee-list').empty();
            for (var i=0; i<l; i++) {
                e = employees[i];
                $('.employee-list').append('
' + e.firstName + ' ' + e.lastName + '
');
            }
        });
    },

    initialize: function() {
       var self = this;
    this.store = new MemoryStore(function() {
        self.showAlert('Store Initialized', 'Info');
    });
    $('.search-key').on('keyup', $.proxy(this.findByName, this));
    this.el.on('click', '.change-pic-btn', this.changePicture);
    },
    showAlert: function (message, title) {
    if (navigator.notification) {
        navigator.notification.alert(message, null, title, 'OK');
    } else {
        alert(title ? (title + ": " + message) : message);
    }
}

};

app.initialize();<commit_msg>camera<commit_after>var app = {
    changePicture: function(event){
     event.preventDefault();
    if (!navigator.camera) {
        app.showAlert("Camera API not supported", "Error");
        return;
    }
    var options =   {   quality: 50,
                        destinationType: Camera.DestinationType.DATA_URL,
                        sourceType: 1,      // 0:Photo Library, 1=Camera, 2=Saved Photo Album
                        encodingType: 0     // 0=JPG 1=PNG
                    };

    navigator.camera.getPicture(
        function(imageData) {
            $('.employee-image', this.el).attr('src', "data:image/jpeg;base64," + imageData);
        },
        function() {
            app.showAlert('Error taking picture', 'Error');
        },
        options);

    return false;
    },

    findByName: function() {
        console.log('findByName');
        this.store.findByName($('.search-key').val(), function(employees) {
            var l = employees.length;
            var e;
            $('.employee-list').empty();
            for (var i=0; i<l; i++) {
                e = employees[i];
                $('.employee-list').append('
' + e.firstName + ' ' + e.lastName + '
');
            }
        });
    },

    initialize: function() {
       var self = this;
    this.store = new MemoryStore(function() {
        self.showAlert('Store Initialized', 'Info');
    });
    $('.search-key').on('keyup', $.proxy(this.findByName, this));
    $('.change-pic-btn').on('click',this.changePicture);
    },
    showAlert: function (message, title) {
    if (navigator.notification) {
        navigator.notification.alert(message, null, title, 'OK');
    } else {
        alert(title ? (title + ": " + message) : message);
    }
}

};

app.initialize();<|endoftext|>