<commit_before>/**
 * @author Björn Schießle <bjoern@schiessle.org>
 *
 * @copyright Copyright (c) 2016, Bjoern Schiessle
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your opinion) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

function startLoading() {
	OC.msg.startSaving('#theming_settings_msg');
	$('#theming_settings_loading').show();
}

function setThemingValue(setting, value) {
	startLoading();
	$.post(
		OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
	).done(function(response) {
		hideUndoButton(setting, value);
		preview(setting, value, response.data.serverCssUrl);
	}).fail(function(response) {
		OC.msg.finishedSaving('#theming_settings_msg', response);
		$('#theming_settings_loading').hide();
	});

}

function preview(setting, value, serverCssUrl) {
	OC.msg.startAction('#theming_settings_msg', t('theming', 'Loading preview…'));
	var stylesheetsLoaded = 1;
	var reloadStylesheets = function(cssFile) {
		var queryString = '?reload=' + new Date().getTime();
		var url = cssFile + queryString;
		var old = $('link[href*="' + cssFile.replace("/","\/") + '"]');
		var stylesheet = $("<link/>", {
			rel: "stylesheet",
			type: "text/css",
			href: url
		});
		stylesheet.load(function () {
			$(old).remove();
			stylesheetsLoaded--;
			if(stylesheetsLoaded === 0) {
				$('#theming_settings_loading').hide();
				var response = { status: 'success', data: {message: t('theming', 'Saved')}};
				OC.msg.finishedSaving('#theming_settings_msg', response);
			}
		});
		stylesheet.appendTo("head");
	};

	if (serverCssUrl !== undefined) {
		stylesheetsLoaded++;

		reloadStylesheets(serverCssUrl);
	}
	reloadStylesheets(OC.generateUrl('/apps/theming/styles'));

	// Preview images
	var timestamp = new Date().getTime();
	if (setting === 'logoMime') {
		var previewImageLogo = document.getElementById('theming-preview-logo');
		if (value !== '') {
			previewImageLogo.src = OC.generateUrl('/apps/theming/logo') + "?v" + timestamp;
		} else {
			previewImageLogo.src = OC.getRootPath() + '/core/img/logo.svg?v' + timestamp;
		}
	}

	if (setting === 'name') {
		window.document.title = t('core', 'Admin') + " - " + value;
	}

	hideUndoButton(setting, value);

}

function hideUndoButton(setting, value) {
	var themingDefaults = {
		name: 'Nextcloud',
		slogan: t('lib', 'a safe home for all your data'),
		url: 'https://nextcloud.com',
		color: '#0082c9',
		logoMime: '',
		backgroundMime: ''
	};

	if (value === themingDefaults[setting] || value === '') {
		$('.theme-undo[data-setting=' + setting + ']').hide();
	} else {
		$('.theme-undo[data-setting=' + setting + ']').show();
	}

	if(setting === 'backgroundMime' && value !== 'backgroundColor')  {
		$('.theme-remove-bg').show();
	}
	if(setting === 'backgroundMime' && value === 'backgroundColor')  {
		$('.theme-remove-bg').hide();
		$('.theme-undo[data-setting=backgroundMime]').show();
	}
}

$(document).ready(function () {
	$('#theming [data-toggle="tooltip"]').tooltip();

	$('#theming .theme-undo').each(function() {
		var setting = $(this).data('setting');
		var value = $('#theming-'+setting).val();
		if(setting === 'logoMime' || setting === 'backgroundMime') {
			var value = $('#current-'+setting).val();
		}
		hideUndoButton(setting, value);
	});

	var uploadParamsLogo = {
		pasteZone: null,
		dropZone: null,
		done: function (e, response) {
			preview('logoMime', response.result.data.name);
			OC.msg.finishedSaving('#theming_settings_msg', response.result);
			$('label#uploadlogo').addClass('icon-upload').removeClass('icon-loading-small');
			$('.theme-undo[data-setting=logoMime]').show();
		},
		submit: function(e, response) {
			startLoading();
			$('label#uploadlogo').removeClass('icon-upload').addClass('icon-loading-small');
		},
		fail: function (e, response){
			OC.msg.finishedError('#theming_settings_msg', response._response.jqXHR.responseJSON.data.message);
			$('label#uploadlogo').addClass('icon-upload').removeClass('icon-loading-small');
			$('#theming_settings_loading').hide();
		}
	};
	var uploadParamsLogin = {
		pasteZone: null,
		dropZone: null,
		done: function (e, response) {
			preview('backgroundMime', response.result.data.name);
			OC.msg.finishedSaving('#theming_settings_msg', response.result);
			$('label#upload-login-background').addClass('icon-upload').removeClass('icon-loading-small');
			$('.theme-undo[data-setting=backgroundMime]').show();
		},
		submit: function(e, response) {
			startLoading();
			$('label#upload-login-background').removeClass('icon-upload').addClass('icon-loading-small');
		},
		fail: function (e, response){
			$('label#upload-login-background').removeClass('icon-loading-small').addClass('icon-upload');
			OC.msg.finishedError('#theming_settings_msg', response._response.jqXHR.responseJSON.data.message);
			$('#theming_settings_loading').hide();
		}
	};

	$('#uploadlogo').fileupload(uploadParamsLogo);
	$('#upload-login-background').fileupload(uploadParamsLogin);
	// clicking preview should also trigger file upload dialog
	$('#theming-preview-logo').on('click', function(e) {
		e.stopPropagation();
		$('#uploadlogo').click();
	});
	$('#theming-preview').on('click', function() {
		$('#upload-login-background').click();
	});

	function checkName () {
		var length = $('#theming-name').val().length;
		try {
			if (length > 0) {
				return true;
			} else {
				throw t('theming', 'Name cannot be empty');
			}
		} catch (error) {
			$('#theming-name').attr('title', error);
			$('#theming-name').tooltip({placement: 'top', trigger: 'manual'});
			$('#theming-name').tooltip('fixTitle');
			$('#theming-name').tooltip('show');
			$('#theming-name').addClass('error');
		}
		return false;
	}

	$('#theming-name').keyup(function() {
		if (checkName()) {
			$('#theming-name').tooltip('hide');
			$('#theming-name').removeClass('error');
		}
	});

	$('#theming-name').change(function(e) {
		var el = $(this);
		if(checkName()){
			$.when(el.focusout()).then(function() {
				setThemingValue('name', $(this).val());
			});
			if (e.keyCode == 13) {
				setThemingValue('name', $(this).val());
			}
		}
	});

	$('#theming-url').change(function(e) {
		var el = $(this);
		$.when(el.focusout()).then(function() {
			setThemingValue('url', $(this).val());
		});
		if (e.keyCode == 13) {
			setThemingValue('url', $(this).val());
		}
	});

	$('#theming-slogan').change(function(e) {
		var el = $(this);
		$.when(el.focusout()).then(function() {
			setThemingValue('slogan', $(this).val());
		});
		if (e.keyCode == 13) {
			setThemingValue('slogan', $(this).val());
		}
	});

	$('#theming-color').change(function (e) {
		var color = $(this).val();
		if (color.indexOf('#') !== 0) {
			color = '#' + color;
		}
		setThemingValue('color', color);
	});

	$('.theme-undo').click(function (e) {
		var setting = $(this).data('setting');
		startLoading();
		$('.theme-undo[data-setting=' + setting + ']').hide();
		$.post(
			OC.generateUrl('/apps/theming/ajax/undoChanges'), {'setting' : setting}
		).done(function(response) {
			if (setting === 'color') {
				var colorPicker = document.getElementById('theming-color');
				colorPicker.style.backgroundColor = response.data.value;
				colorPicker.value = response.data.value.slice(1).toUpperCase();
			} else if (setting !== 'logoMime' && setting !== 'backgroundMime') {
				var input = document.getElementById('theming-'+setting);
				input.value = response.data.value;
			}
			preview(setting, response.data.value, response.data.serverCssUrl);
		});
	});

	$('.theme-remove-bg').click(function() {
		startLoading();
		$.post(
			OC.generateUrl('/apps/theming/ajax/updateLogo'), {'backgroundColor' : true}
		).done(function(response) {
			preview('backgroundMime', 'backgroundColor');
		}).fail(function(response) {
			OC.msg.finishedSaving('#theming_settings_msg', response);
		});
	});

});
<commit_msg>Use generic image upload in theming JS<commit_after>/**
 * @author Björn Schießle <bjoern@schiessle.org>
 *
 * @copyright Copyright (c) 2016, Bjoern Schiessle
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your opinion) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

function startLoading() {
	OC.msg.startSaving('#theming_settings_msg');
	$('#theming_settings_loading').show();
}

function setThemingValue(setting, value) {
	startLoading();
	$.post(
		OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
	).done(function(response) {
		hideUndoButton(setting, value);
		preview(setting, value, response.data.serverCssUrl);
	}).fail(function(response) {
		OC.msg.finishedSaving('#theming_settings_msg', response);
		$('#theming_settings_loading').hide();
	});
}

function preview(setting, value, serverCssUrl) {
	OC.msg.startAction('#theming_settings_msg', t('theming', 'Loading preview…'));
	var stylesheetsLoaded = 1;
	var reloadStylesheets = function(cssFile) {
		var queryString = '?reload=' + new Date().getTime();
		var url = cssFile + queryString;
		var old = $('link[href*="' + cssFile.replace("/","\/") + '"]');
		var stylesheet = $("<link/>", {
			rel: "stylesheet",
			type: "text/css",
			href: url
		});
		stylesheet.load(function () {
			$(old).remove();
			stylesheetsLoaded--;
			if(stylesheetsLoaded === 0) {
				$('#theming_settings_loading').hide();
				var response = { status: 'success', data: {message: t('theming', 'Saved')}};
				OC.msg.finishedSaving('#theming_settings_msg', response);
			}
		});
		stylesheet.appendTo("head");
	};

	if (serverCssUrl !== undefined) {
		stylesheetsLoaded++;

		reloadStylesheets(serverCssUrl);
	}
	reloadStylesheets(OC.generateUrl('/apps/theming/styles'));

	if (setting === 'name') {
		window.document.title = t('core', 'Admin') + " - " + value;
	}

	hideUndoButton(setting, value);

}

function hideUndoButton(setting, value) {
	var themingDefaults = {
		name: 'Nextcloud',
		slogan: t('lib', 'a safe home for all your data'),
		url: 'https://nextcloud.com',
		color: '#0082c9',
		logoMime: '',
		backgroundMime: ''
	};

	if (value === themingDefaults[setting] || value === '') {
		$('.theme-undo[data-setting=' + setting + ']').hide();
	} else {
		$('.theme-undo[data-setting=' + setting + ']').show();
	}

	if(setting === 'backgroundMime' && value !== 'backgroundColor')  {
		$('.theme-remove-bg').show();
	}
	if(setting === 'backgroundMime' && value === 'backgroundColor')  {
		$('.theme-remove-bg').hide();
		$('.theme-undo[data-setting=backgroundMime]').show();
	}
}

$(document).ready(function () {
	$('#theming [data-toggle="tooltip"]').tooltip();

	$('#theming .theme-undo').each(function() {
		var setting = $(this).data('setting');
		var value = $('#theming-'+setting).val();
		hideUndoButton(setting, value);
	});

	$('.fileupload').fileupload({
		pasteZone: null,
		dropZone: null,
		done: function (e, response) {
			var $form = $(e.target).closest('form');
			var key = $form.data('image-key');

			preview(key + 'Mime', response.result.data.name, response.result.data.serverCssUrl);
			$form.find('.image-preview').css('backgroundImage', response.result.data.url + '?v=' + new Date().getTime());
			OC.msg.finishedSaving('#theming_settings_msg', response.result);
			$form.find('label.button').addClass('icon-upload').removeClass('icon-loading-small');
			$form.find('.theme-undo').show();
		},
		submit: function(e, response) {
			var $form = $(e.target).closest('form');
			var key = $form.data('image-key');
			startLoading();
			$form.find('label.button').removeClass('icon-upload').addClass('icon-loading-small');
		},
		fail: function (e, response){
			var $form = $(e.target).closest('form');
			OC.msg.finishedError('#theming_settings_msg', response._response.jqXHR.responseJSON.data.message);
			$form.find('label.button').addClass('icon-upload').removeClass('icon-loading-small');
			$('#theming_settings_loading').hide();
		}
	});

	// clicking preview should also trigger file upload dialog
	$('#theming-preview-logo').on('click', function(e) {
		e.stopPropagation();
		$('#uploadlogo').click();
	});
	$('#theming-preview').on('click', function() {
		$('#upload-login-background').click();
	});

	function checkName () {
		var length = $('#theming-name').val().length;
		try {
			if (length > 0) {
				return true;
			} else {
				throw t('theming', 'Name cannot be empty');
			}
		} catch (error) {
			$('#theming-name').attr('title', error);
			$('#theming-name').tooltip({placement: 'top', trigger: 'manual'});
			$('#theming-name').tooltip('fixTitle');
			$('#theming-name').tooltip('show');
			$('#theming-name').addClass('error');
		}
		return false;
	}

	$('#theming-name').keyup(function() {
		if (checkName()) {
			$('#theming-name').tooltip('hide');
			$('#theming-name').removeClass('error');
		}
	});

	$('#theming-name').change(function(e) {
		var el = $(this);
		if(checkName()){
			$.when(el.focusout()).then(function() {
				setThemingValue('name', $(this).val());
			});
			if (e.keyCode == 13) {
				setThemingValue('name', $(this).val());
			}
		}
	});

	$('#theming-url').change(function(e) {
		var el = $(this);
		$.when(el.focusout()).then(function() {
			setThemingValue('url', $(this).val());
		});
		if (e.keyCode == 13) {
			setThemingValue('url', $(this).val());
		}
	});

	$('#theming-slogan').change(function(e) {
		var el = $(this);
		$.when(el.focusout()).then(function() {
			setThemingValue('slogan', $(this).val());
		});
		if (e.keyCode == 13) {
			setThemingValue('slogan', $(this).val());
		}
	});

	$('#theming-color').change(function (e) {
		var color = $(this).val();
		if (color.indexOf('#') !== 0) {
			color = '#' + color;
		}
		setThemingValue('color', color);
	});

	$('.theme-undo').click(function (e) {
		var setting = $(this).data('setting');
		startLoading();
		$('.theme-undo[data-setting=' + setting + ']').hide();
		$.post(
			OC.generateUrl('/apps/theming/ajax/undoChanges'), {'setting' : setting}
		).done(function(response) {
			if (setting === 'color') {
				var colorPicker = document.getElementById('theming-color');
				colorPicker.style.backgroundColor = response.data.value;
				colorPicker.value = response.data.value.slice(1).toUpperCase();
			} else if (setting !== 'logoMime' && setting !== 'backgroundMime') {
				var input = document.getElementById('theming-'+setting);
				input.value = response.data.value;
			}
			preview(setting, response.data.value, response.data.serverCssUrl);
		});
	});

	$('.theme-remove-bg').click(function() {
		startLoading();
		$.post(
			OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : 'backgroundMime', 'value' : 'backgroundColor'}
		).done(function(response) {
			preview('backgroundMime', 'backgroundColor', response.data.serverCssUrl);
		}).fail(function(response) {
			OC.msg.finishedSaving('#theming_settings_msg', response);
			$('#theming_settings_loading').hide();
		});
	});

});
<|endoftext|>


<commit_before>/* jshint undef: true, strict:false, trailing:false, unused:false */
/* global require, exports, console, process, module, L, angular, _, jQuery, $, d3 */

(function() {
	var vApp = angular.module('vitality');

	vApp.config(function($stateProvider) {
		$stateProvider.state('diary', {
			url: '/diary/:entry',
			templateUrl: 'partials/diary.html',
			resolve: {
				who: function(client, utils) {
					var u = utils, d = u.deferred();
					client.store.checkLogin().then(function(x) { d.resolve(x);	}).fail(d.reject);
					return d.promise();
				}
			},
			controller: function($scope, $state, $stateParams, who, utils, dateutils, entities) {
				var u = utils, du = dateutils,
					sa = function(fn) { return u.safeApply($scope, fn); },
					$s = $scope,
					whom, box;

				var	_init = function(entryid) {
					var date = entryIdToDate(entryid),
						dstart = du.midnight(date),
						dend = du.lastMSecOf(date);
					$scope.selected_day = {	date : dstart, dend : dend,	entries: [] };
					if (!box) { return; }
					entities.activities.getByActivityType(box,dstart,dend,['diary-entry-created']).then(function(x)  {
						sa(function() {
							x.sort(function(a,b) { return a.peek('tstart').valueOf() - b.peek('tstart').valueOf(); });
							$scope.selected_day.entries = x;
						});
					});
				},
				entryIdToDate = function(eid) {
					if (eid.toLowerCase().trim() === 'today') { return new Date(); }
					var re = /([0-9]{2})\-([0-9]{2})\-([0-9]{4})/;
					var d = re.exec(eid);
					if (d && d.length > 0) {
						var td = new Date();
						td.setDate(parseInt(d[1]));
						td.setMonth(parseInt(d[2])-1);
						td.setYear(parseInt(d[3]));
						td = du.midnight(td);
						return td;
					}
					return new Date();
				},
				dateToEntryId = function(date) { return d3.time.format('%d-%m-%Y')(date); },
				selectDay = function(day) {
					console.log('select Day', day);
					$state.go('diary', { entry: dateToEntryId(day) } );
				};

			$s.selectNextDay = function() {
				selectDay(du.dayAfter($s.selected_day.date));
			};
			$s.selectPrevDay = function() {
				selectDay(du.dayBefore($s.selected_day.date));
			};
			$s.isToday = du.isToday;
			$s.addTextEntry = function() {
				delete $s.entryPopup;
				var now = new Date(), d = u.deferred();
				entities.activities.make1(box, 'diary-entry-created', whom, now, now, undefined, undefined, undefined, undefined, {
					journaltype:'text',
					value:''
				}).then(function(obj) {
					console.log('saving --- ', obj);
					sa(function() { $s.selected_day.entries.push(obj);	});
					obj.save();
					d.resolve(obj);
				}).fail(d.reject);
				return d.promise();
			};
			$s.addPhraseEntry = function(p) {
				delete $s.entryPopup;
				var now = new Date(), d = u.deferred();
				entities.activities.make1(box, 'diary-entry-created', whom, now, now, undefined, undefined, undefined, undefined, {
					journaltype:'phrase',
					phrase:p.text,
					range:p.range,
					value:p.value
				}).then(function(obj) {
					console.log('saving --- ', obj);
					obj.save();
					sa(function() { $s.selected_day.entries.push(obj);	});
					d.resolve(obj);
				}).fail(d.reject);
				return d.promise();
			};
			$s.toggleEntryPopup = function() {
				console.log('entrypopup >> ', $s.entryPopup);
				$s.entryPopup = !$s.entryPopup;
			};

			if (!$stateParams.entry || $stateParams.entry.trim().length === 0) { $state.go('diary', { entry:'today' });	}
			$s.dow_names = dateutils.weekday;
			$s.month_name = dateutils.months;
			$s.$watch('box', function() { box = $scope.box;	_init($stateParams.entry); });
		}
	});
	}).directive('diaryEntry', function() {
		return {
			restrict:'E',
			replace:true,
			scope: {'entry' : '=', 'entries':'='},
			templateUrl:'partials/diary-entry.html',
			controller:function($scope, $timeout, utils)  {
				var u = utils,
					sa = function(fn) { return u.safeApply($scope, fn); },
					$s = $scope,
					timeout;
				$s.deleteMe = function(entry) {
					entry.destroy();
					$s.entries = $s.entries.filter(function(x) { return x !== entry; });
				};
				$s.$watch('entry', function() {
					var entry = $s.entry;
					entry.entryvalue = (entry !== undefined ? entry.peek('value') : '');
				});
				$s.$watch('entry.entryvalue', function(x) {
					// console.log('setting value -- ', x, $s.entryvalue);
					if (timeout) {
						console.log('clearing .. ');
						$timeout.cancel(timeout);
					}
					timeout = $timeout(function() {
						console.log('... saving');
						$s.entry.set('value', x);
						$s.entry.save();
						timeout = undefined;
					}, 1000);

				});
				$s.toTimeString = function(d) {
					// console.error('tts >> ', d, d3.time.format('%I:%M %p')(d).toLowerCase());
					return d3.time.format('%I:%M %p')(d).toLowerCase();
				};
			}
		};
	}).directive('autoExpandingTextarea', function () {
		return {
			restrict:'E',
			replace:true,
			scope:{model:"="},
			template:'
<textarea ng-model="model" class=\'auto-expanding-textarea\'></textarea>
',
			controller:function($scope, utils)  {
				var u = utils,
				sa = function(fn) { return u.safeApply($scope, fn); },
				$s = $scope;
				console.log("model is ", $scope.model);
			},
			link:function($scope, $element) {
				var el = $element.find('textarea')[0], div = $element[0], last_length = 0;
				var minheight = $(el).css('min-height') && parseInt($(el).css('min-height').slice(0,-2)) || 0;
				var twerk = function(e) {
					console.log('size of div before : ', $(div).height());
					console.log('setting height -- ');
					$(div).height($(div).height() + 50);
					$(el).height(minheight);
					$(el).height(el.scrollHeight + 20);
					$(div).height('auto');
				};
			    $(el).keyup(twerk);
			    $scope.$watch('model', function(x) {
			    	twerk();
			    });
			}
		};
	});
})();<commit_msg>now things are working properly<commit_after>/* jshint undef: true, strict:false, trailing:false, unused:false */
/* global require, exports, console, process, module, L, angular, _, jQuery, $, d3 */

(function() {
	var vApp = angular.module('vitality');

	vApp.config(function($stateProvider) {
		$stateProvider.state('diary', {
			url: '/diary/:entry',
			templateUrl: 'partials/diary.html',
			resolve: {
				who: function(client, utils) {
					var u = utils, d = u.deferred();
					client.store.checkLogin().then(function(x) { d.resolve(x);	}).fail(d.reject);
					return d.promise();
				}
			},
			controller: function($scope, $state, $stateParams, who, utils, dateutils, entities) {
				var u = utils, du = dateutils,
					sa = function(fn) { return u.safeApply($scope, fn); },
					$s = $scope,
					whom, box;

				var	_init = function(entryid) {
					var date = entryIdToDate(entryid),
						dstart = du.midnight(date),
						dend = du.lastMSecOf(date);
					$scope.selected_day = {	date : dstart, dend : dend,	entries: [] };
					if (!box) { return; }
					entities.activities.getByActivityType(box,dstart,dend,['diary-entry-created']).then(function(x)  {
						sa(function() {
							x.sort(function(a,b) { return a.peek('tstart').valueOf() - b.peek('tstart').valueOf(); });
							$scope.selected_day.entries = x;
						});
					});
				},
				entryIdToDate = function(eid) {
					if (eid.toLowerCase().trim() === 'today') { return new Date(); }
					var re = /([0-9]{2})\-([0-9]{2})\-([0-9]{4})/;
					var d = re.exec(eid);
					if (d && d.length > 0) {
						var td = new Date();
						td.setDate(parseInt(d[1]));
						td.setMonth(parseInt(d[2])-1);
						td.setYear(parseInt(d[3]));
						td = du.midnight(td);
						return td;
					}
					return new Date();
				},
				dateToEntryId = function(date) { return d3.time.format('%d-%m-%Y')(date); },
				selectDay = function(day) {
					console.log('select Day', day);
					$state.go('diary', { entry: dateToEntryId(day) } );
				};

			$s.selectNextDay = function() {
				selectDay(du.dayAfter($s.selected_day.date));
			};
			$s.selectPrevDay = function() {
				selectDay(du.dayBefore($s.selected_day.date));
			};
			$s.isToday = du.isToday;
			$s.addTextEntry = function() {
				delete $s.entryPopup;
				var now = new Date(), d = u.deferred();
				entities.activities.make1(box, 'diary-entry-created', whom, now, now, undefined, undefined, undefined, undefined, {
					journaltype:'text',
					value:''
				}).then(function(obj) {
					console.log('saving --- ', obj);
					sa(function() { $s.selected_day.entries.push(obj);	});
					obj.save();
					d.resolve(obj);
				}).fail(d.reject);
				return d.promise();
			};
			$s.addPhraseEntry = function(p) {
				delete $s.entryPopup;
				var now = new Date(), d = u.deferred();
				entities.activities.make1(box, 'diary-entry-created', whom, now, now, undefined, undefined, undefined, undefined, {
					journaltype:'phrase',
					phrase:p.text,
					range:p.range,
					value:p.value
				}).then(function(obj) {
					console.log('saving --- ', obj);
					obj.save();
					sa(function() { $s.selected_day.entries.push(obj);	});
					d.resolve(obj);
				}).fail(d.reject);
				return d.promise();
			};
			$s.toggleEntryPopup = function() {
				console.log('entrypopup >> ', $s.entryPopup);
				$s.entryPopup = !$s.entryPopup;
			};

			if (!$stateParams.entry || $stateParams.entry.trim().length === 0) { $state.go('diary', { entry:'today' });	}
			$s.dow_names = dateutils.weekday;
			$s.month_name = dateutils.months;
			$s.$watch('box', function() { box = $scope.box;	_init($stateParams.entry); });
		}
	});
	}).directive('diaryEntry', function() {
		return {
			restrict:'E',
			replace:true,
			scope: {'entry' : '=', 'entries':'='},
			templateUrl:'partials/diary-entry.html',
			controller:function($scope, $timeout, utils)  {
				var u = utils,
					sa = function(fn) { return u.safeApply($scope, fn); },
					$s = $scope,
					timeout;
				$s.deleteMe = function(entry) {
					entry.destroy();
					$s.entries = $s.entries.filter(function(x) { return x !== entry; });
				};
				$s.$watch('entry', function() {
					var entry = $s.entry;
					entry.entryvalue = (entry !== undefined ? entry.peek('value') : '');
				});
				$s.$watch('entry.entryvalue', function(x) {
					// console.log('setting value -- ', x, $s.entryvalue);
					if (timeout) {
						console.log('clearing .. ');
						$timeout.cancel(timeout);
					}
					timeout = $timeout(function() {
						console.log('... saving');
						$s.entry.set('value', x);
						$s.entry.save();
						timeout = undefined;
					}, 1000);

				});
				$s.toTimeString = function(d) {
					// console.error('tts >> ', d, d3.time.format('%I:%M %p')(d).toLowerCase());
					return d3.time.format('%I:%M %p')(d).toLowerCase();
				};
			}
		};
	}).directive('autoExpandingTextarea', function () {
		return {
			restrict:'E',
			replace:true,
			scope:{model:"="},
			template:'
<textarea ng-model="model" class=\'auto-expanding-textarea\'></textarea>
',
			controller:function($scope, utils)  {
				var u = utils,
				sa = function(fn) { return u.safeApply($scope, fn); },
				$s = $scope;
				console.log("model is ", $scope.model);
			},
			link:function($scope, $element) {
				var el = $element.find('textarea')[0], div = $element[0], last_length = 0;
				var minheight = $(el).css('min-height') && parseInt($(el).css('min-height').slice(0,-2)) || 0;
				var twerk = function(e) {
					$(div).height($(div).height() + 50);
					$(el).height(minheight);
					$(el).height(el.scrollHeight + 20);
					$(div).height('auto');
				};
			    $(el).keyup(twerk);
			    $scope.$watch('model', function(x) {
			    	twerk();
			    });
			}
		};
	});
})();<|endoftext|>


<commit_before>
import {createStructuredSelector} from 'reselect'
import React, {PropTypes, Component} from 'react'
import {connect} from './connect'

import * as actions from '../actions'

import urlParser from '../util/url'
import navigation from '../util/navigation'

import staticTabData from '../constants/static-tab-data'

import querystring from 'querystring'
import ospath from 'path'

const injectPath = ospath.resolve(__dirname, '..', 'inject', 'browser.js')
// const DONT_SHOW_WEBVIEWS = process.env.ITCH_DONT_SHOW_WEBVIEWS === '1'
const SHOW_DEVTOOLS = parseInt(process.env.DEVTOOLS, 10) > 1
const WILL_NAVIGATE_GRACE_PERIOD = 3000

import BrowserBar from './browser-bar'

import GameBrowserContext from './game-browser-context'

import {transformUrl} from '../util/navigation'

// updated when switching accounts
let currentSession = null

export class BrowserMeat extends Component {
  constructor () {
    super()
    this.state = {
      browserState: {
        canGoBack: false,
        canGoForward: false,
        loading: true,
        url: ''
      }
    }

    this.goBack = ::this.goBack
    this.goForward = ::this.goForward
    this.reload = ::this.reload
    this.stop = ::this.stop
    this.openDevTools = ::this.openDevTools
    this.loadURL = ::this.loadURL
  }

  updateBrowserState (props = {}) {
    const {webview} = this
    if (!webview) {
      return
    }
    if (!webview.partition || webview.partition === '') {
      console.warn(`${this.props.tabId}: webview has empty partition`)
    }

    const browserState = {
      ...this.state.browserState,
      canGoBack: webview.canGoBack(),
      canGoForward: webview.canGoForward(),
      ...props
    }

    this.setState({browserState})
  }

  domReady () {
    const {url} = this.props
    const {webview} = this

    const webContents = webview.getWebContents()
    if (!webContents || webContents.isDestroyed()) return

    if (SHOW_DEVTOOLS) {
      webContents.openDevTools({detach: true})
    }

    this.updateBrowserState({loading: false})

    if (currentSession !== webContents.session) {
      this.setupItchInternal(webContents.session)
    }

    if (url && url !== 'about:blank') {
      this.loadURL(url)
    }
  }

  didStartLoading () {
    this.updateBrowserState({loading: true})
  }

  didStopLoading () {
    this.updateBrowserState({loading: false})
  }

  pageTitleUpdated (e) {
    const {tabId, tabDataFetched} = this.props
    tabDataFetched(tabId, {webTitle: e.title})
  }

  pageFaviconUpdated (e) {
    const {tabId, tabDataFetched} = this.props
    tabDataFetched(tabId, {webFavicon: e.favicons[0]})
  }

  didNavigate (e) {
    const {tabId} = this.props
    const {url} = e
    this.updateBrowserState({url})
    this.analyzePage(tabId, url)
  }

  willNavigate (e) {
    if (!this.isFrozen()) {
      return
    }

    const {navigate} = this.props
    const {url} = e

    // sometimes we get double will-navigate events because life is fun?!
    if (this.lastNavigationUrl === url && e.timeStamp - this.lastNavigationTimeStamp < WILL_NAVIGATE_GRACE_PERIOD) {
      this.with((wv) => {
        wv.stop()
        wv.loadURL(this.props.url)
      })
      return
    }
    this.lastNavigationUrl = url
    this.lastNavigationTimeStamp = e.timeStamp

    navigate(`url/${url}`)

    // our own little preventDefault
    // cf. https://github.com/electron/electron/issues/1378
    this.with((wv) => {
      wv.stop()
      wv.loadURL(this.props.url)
    })
  }

  newWindow (e) {
    const {navigate} = this.props
    const {url} = e
    navigate('url/' + url)
  }

  isFrozen (e) {
    const {tabId} = this.props
    const frozen = staticTabData[tabId] || !tabId
    return frozen
  }

  setupItchInternal (session) {
    currentSession = session

    // requests to 'itch-internal' are used to communicate between web content & the app
    let internalFilter = {
      urls: ['https://itch-internal/*']
    }

    session.webRequest.onBeforeRequest(internalFilter, (details, callback) => {
      callback({cancel: true})

      let parsed = urlParser.parse(details.url)
      const {pathname, query} = parsed
      const params = querystring.parse(query)
      const {tabId} = params

      switch (pathname) {
        case '/open-devtools':
          const {webview} = this
          if (webview && webview.getWebContents() && !webview.getWebContents().isDestroyed()) {
            webview.getWebContents().openDevTools({detach: true})
          }
          break
        case '/analyze-page':
          this.analyzePage(tabId, params.url)
          break
        case '/evolve-tab':
          const {evolveTab} = this.props
          evolveTab(tabId, params.path)
          break
      }
    })
  }

  analyzePage (tabId, url) {
    const {evolveTab} = this.props

    const xhr = new window.XMLHttpRequest()
    xhr.responseType = 'document'
    xhr.onreadystatechange = () => {
      if (xhr.readyState !== 4) {
        return
      }
      const meta = xhr.responseXML.querySelector('meta[name="itch:path"]')
      if (meta) {
        const newPath = meta.content
        evolveTab(tabId, newPath)
      }
    }
    xhr.open('GET', url)

    // itch.io pages don't have CORS, but this code doesn't run in
    // a webview so CSP doesn't apply to us.
    xhr.send()
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.url) {
      const {webview} = this
      if (!webview) {
        return
      }
      if (webview.src === '' || webview.src === 'about:blank') {
        // we didn't have a proper url but now do
        this.loadURL(nextProps.url)
      }
    }
  }

  componentDidMount () {
    const {webviewShell} = this.refs

    // cf. https://github.com/electron/electron/issues/6046
    webviewShell.innerHTML = '<webview style="display: flex; flex: 1 1;"/>'
    const wv = webviewShell.querySelector('webview')
    this.webview = wv

    const {meId} = this.props
    const partition = `persist:itchio-${meId}`

    wv.partition = partition
    wv.plugins = true
    wv.preload = injectPath

    const callbackSetup = () => {
      wv.addEventListener('did-start-loading', ::this.didStartLoading)
      wv.addEventListener('did-stop-loading', ::this.didStopLoading)
      wv.addEventListener('will-navigate', ::this.willNavigate)
      wv.addEventListener('did-navigate', ::this.didNavigate)
      wv.addEventListener('page-title-updated', ::this.pageTitleUpdated)
      wv.addEventListener('page-favicon-updated', ::this.pageFaviconUpdated)
      wv.addEventListener('new-window', ::this.newWindow)
      this.domReady()

      // otherwise, back button is active and brings us back to 'about:blank'
      wv.clearHistory()
      wv.removeEventListener('dom-ready', callbackSetup)
    }
    wv.addEventListener('dom-ready', callbackSetup)

    const {tabId} = this.props
    wv.addEventListener('dom-ready', () => {
      wv.executeJavaScript(`window.__itchInit && window.__itchInit(${JSON.stringify(tabId)})`)
      this.didStopLoading()
    })

    wv.src = 'about:blank'
  }

  render () {
    const {tabData, tabPath, controls} = this.props
    const {browserState} = this.state

    const {goBack, goForward, stop, reload, openDevTools, loadURL} = this
    const controlProps = {tabPath, tabData, browserState, goBack, goForward, stop, reload, openDevTools, loadURL}

    let context = ''
    if (controls === 'game') {
      context = <GameBrowserContext {...controlProps}/>
    }

    return

      {this.isFrozen()
        ? ''
        : <BrowserBar {...controlProps}/>
      }

        {context}

  }

  with (cb, opts = {insist: false}) {
    const {webview} = this
    if (!webview) return

    const webContents = webview.getWebContents()
    if (!webContents) {
      return
    }

    if (webContents.isDestroyed()) return

    cb(webview, webContents)
  }

  openDevTools () {
    this.with((wv, wc) => wc.openDevTools({detach: true}))
  }

  stop () {
    this.with((wv) => wv.stop())
  }

  reload () {
    this.with((wv) => {
      wv.reload()
    })
    const {tabId, tabReloaded} = this.props
    tabReloaded(tabId)
  }

  goBack () {
    this.with((wv) => wv.goBack())
  }

  goForward () {
    this.with((wv) => wv.goForward())
  }

  async loadURL (input) {
    const {navigate} = this.props
    const url = await transformUrl(input)

    if (navigation.isAppSupported(url) && this.isFrozen()) {
      navigate(`url/${url}`)
    } else {
      const browserState = {...this.state.browserState, url}
      this.setState({browserState})

      const {webview} = this
      if (webview) {
        webview.loadURL(url)
      }
    }
  }
}

BrowserMeat.propTypes = {
  url: PropTypes.string.isRequired,
  tabPath: PropTypes.string,
  tabData: PropTypes.object,
  tabId: PropTypes.string.isRequired,
  className: PropTypes.string,
  meId: PropTypes.any,
  navigate: PropTypes.any,

  tabReloaded: PropTypes.func.isRequired,
  evolveTab: PropTypes.func.isRequired,

  controls: PropTypes.oneOf(['generic', 'game', 'user'])
}

const mapStateToProps = createStructuredSelector({
  meId: (state) => (state.session.credentials.me || {id: 'anonymous'}).id
})

const mapDispatchToProps = (dispatch) => ({
  navigate: (id, data) => dispatch(actions.navigate(id, data)),
  evolveTab: (id, path) => dispatch(actions.evolveTab({id, path})),
  tabDataFetched: (id, data) => dispatch(actions.tabDataFetched({id, data, timestamp: +new Date()})),
  tabReloaded: (id) => dispatch(actions.tabReloaded({id}))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BrowserMeat)
<commit_msg>ignore non-document pages when scraping<commit_after>
import {createStructuredSelector} from 'reselect'
import React, {PropTypes, Component} from 'react'
import {connect} from './connect'

import * as actions from '../actions'

import urlParser from '../util/url'
import navigation from '../util/navigation'

import staticTabData from '../constants/static-tab-data'

import querystring from 'querystring'
import ospath from 'path'

const injectPath = ospath.resolve(__dirname, '..', 'inject', 'browser.js')
// const DONT_SHOW_WEBVIEWS = process.env.ITCH_DONT_SHOW_WEBVIEWS === '1'
const SHOW_DEVTOOLS = parseInt(process.env.DEVTOOLS, 10) > 1
const WILL_NAVIGATE_GRACE_PERIOD = 3000

import BrowserBar from './browser-bar'

import GameBrowserContext from './game-browser-context'

import {transformUrl} from '../util/navigation'

// updated when switching accounts
let currentSession = null

export class BrowserMeat extends Component {
  constructor () {
    super()
    this.state = {
      browserState: {
        canGoBack: false,
        canGoForward: false,
        loading: true,
        url: ''
      }
    }

    this.goBack = ::this.goBack
    this.goForward = ::this.goForward
    this.reload = ::this.reload
    this.stop = ::this.stop
    this.openDevTools = ::this.openDevTools
    this.loadURL = ::this.loadURL
  }

  updateBrowserState (props = {}) {
    const {webview} = this
    if (!webview) {
      return
    }
    if (!webview.partition || webview.partition === '') {
      console.warn(`${this.props.tabId}: webview has empty partition`)
    }

    const browserState = {
      ...this.state.browserState,
      canGoBack: webview.canGoBack(),
      canGoForward: webview.canGoForward(),
      ...props
    }

    this.setState({browserState})
  }

  domReady () {
    const {url} = this.props
    const {webview} = this

    const webContents = webview.getWebContents()
    if (!webContents || webContents.isDestroyed()) return

    if (SHOW_DEVTOOLS) {
      webContents.openDevTools({detach: true})
    }

    this.updateBrowserState({loading: false})

    if (currentSession !== webContents.session) {
      this.setupItchInternal(webContents.session)
    }

    if (url && url !== 'about:blank') {
      this.loadURL(url)
    }
  }

  didStartLoading () {
    this.updateBrowserState({loading: true})
  }

  didStopLoading () {
    this.updateBrowserState({loading: false})
  }

  pageTitleUpdated (e) {
    const {tabId, tabDataFetched} = this.props
    tabDataFetched(tabId, {webTitle: e.title})
  }

  pageFaviconUpdated (e) {
    const {tabId, tabDataFetched} = this.props
    tabDataFetched(tabId, {webFavicon: e.favicons[0]})
  }

  didNavigate (e) {
    const {tabId} = this.props
    const {url} = e
    this.updateBrowserState({url})
    this.analyzePage(tabId, url)
  }

  willNavigate (e) {
    if (!this.isFrozen()) {
      return
    }

    const {navigate} = this.props
    const {url} = e

    // sometimes we get double will-navigate events because life is fun?!
    if (this.lastNavigationUrl === url && e.timeStamp - this.lastNavigationTimeStamp < WILL_NAVIGATE_GRACE_PERIOD) {
      this.with((wv) => {
        wv.stop()
        wv.loadURL(this.props.url)
      })
      return
    }
    this.lastNavigationUrl = url
    this.lastNavigationTimeStamp = e.timeStamp

    navigate(`url/${url}`)

    // our own little preventDefault
    // cf. https://github.com/electron/electron/issues/1378
    this.with((wv) => {
      wv.stop()
      wv.loadURL(this.props.url)
    })
  }

  newWindow (e) {
    const {navigate} = this.props
    const {url} = e
    navigate('url/' + url)
  }

  isFrozen (e) {
    const {tabId} = this.props
    const frozen = staticTabData[tabId] || !tabId
    return frozen
  }

  setupItchInternal (session) {
    currentSession = session

    // requests to 'itch-internal' are used to communicate between web content & the app
    let internalFilter = {
      urls: ['https://itch-internal/*']
    }

    session.webRequest.onBeforeRequest(internalFilter, (details, callback) => {
      callback({cancel: true})

      let parsed = urlParser.parse(details.url)
      const {pathname, query} = parsed
      const params = querystring.parse(query)
      const {tabId} = params

      switch (pathname) {
        case '/open-devtools':
          const {webview} = this
          if (webview && webview.getWebContents() && !webview.getWebContents().isDestroyed()) {
            webview.getWebContents().openDevTools({detach: true})
          }
          break
        case '/analyze-page':
          this.analyzePage(tabId, params.url)
          break
        case '/evolve-tab':
          const {evolveTab} = this.props
          evolveTab(tabId, params.path)
          break
      }
    })
  }

  analyzePage (tabId, url) {
    const {evolveTab} = this.props

    const xhr = new window.XMLHttpRequest()
    xhr.responseType = 'document'
    xhr.onreadystatechange = () => {
      if (xhr.readyState !== 4) {
        return
      }
      if (!xhr.responseXML) {
        return
      }
      const meta = xhr.responseXML.querySelector('meta[name="itch:path"]')
      if (meta) {
        const newPath = meta.content
        evolveTab(tabId, newPath)
      }
    }
    xhr.open('GET', url)

    // itch.io pages don't have CORS, but this code doesn't run in
    // a webview so CSP doesn't apply to us.
    xhr.send()
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.url) {
      const {webview} = this
      if (!webview) {
        return
      }
      if (webview.src === '' || webview.src === 'about:blank') {
        // we didn't have a proper url but now do
        this.loadURL(nextProps.url)
      }
    }
  }

  componentDidMount () {
    const {webviewShell} = this.refs

    // cf. https://github.com/electron/electron/issues/6046
    webviewShell.innerHTML = '<webview style="display: flex; flex: 1 1;"/>'
    const wv = webviewShell.querySelector('webview')
    this.webview = wv

    const {meId} = this.props
    const partition = `persist:itchio-${meId}`

    wv.partition = partition
    wv.plugins = true
    wv.preload = injectPath

    const callbackSetup = () => {
      wv.addEventListener('did-start-loading', ::this.didStartLoading)
      wv.addEventListener('did-stop-loading', ::this.didStopLoading)
      wv.addEventListener('will-navigate', ::this.willNavigate)
      wv.addEventListener('did-navigate', ::this.didNavigate)
      wv.addEventListener('page-title-updated', ::this.pageTitleUpdated)
      wv.addEventListener('page-favicon-updated', ::this.pageFaviconUpdated)
      wv.addEventListener('new-window', ::this.newWindow)
      this.domReady()

      // otherwise, back button is active and brings us back to 'about:blank'
      wv.clearHistory()
      wv.removeEventListener('dom-ready', callbackSetup)
    }
    wv.addEventListener('dom-ready', callbackSetup)

    const {tabId} = this.props
    wv.addEventListener('dom-ready', () => {
      wv.executeJavaScript(`window.__itchInit && window.__itchInit(${JSON.stringify(tabId)})`)
      this.didStopLoading()
    })

    wv.src = 'about:blank'
  }

  render () {
    const {tabData, tabPath, controls} = this.props
    const {browserState} = this.state

    const {goBack, goForward, stop, reload, openDevTools, loadURL} = this
    const controlProps = {tabPath, tabData, browserState, goBack, goForward, stop, reload, openDevTools, loadURL}

    let context = ''
    if (controls === 'game') {
      context = <GameBrowserContext {...controlProps}/>
    }

    return

      {this.isFrozen()
        ? ''
        : <BrowserBar {...controlProps}/>
      }

        {context}

  }

  with (cb, opts = {insist: false}) {
    const {webview} = this
    if (!webview) return

    const webContents = webview.getWebContents()
    if (!webContents) {
      return
    }

    if (webContents.isDestroyed()) return

    cb(webview, webContents)
  }

  openDevTools () {
    this.with((wv, wc) => wc.openDevTools({detach: true}))
  }

  stop () {
    this.with((wv) => wv.stop())
  }

  reload () {
    this.with((wv) => {
      wv.reload()
    })
    const {tabId, tabReloaded} = this.props
    tabReloaded(tabId)
  }

  goBack () {
    this.with((wv) => wv.goBack())
  }

  goForward () {
    this.with((wv) => wv.goForward())
  }

  async loadURL (input) {
    const {navigate} = this.props
    const url = await transformUrl(input)

    if (navigation.isAppSupported(url) && this.isFrozen()) {
      navigate(`url/${url}`)
    } else {
      const browserState = {...this.state.browserState, url}
      this.setState({browserState})

      const {webview} = this
      if (webview) {
        webview.loadURL(url)
      }
    }
  }
}

BrowserMeat.propTypes = {
  url: PropTypes.string.isRequired,
  tabPath: PropTypes.string,
  tabData: PropTypes.object,
  tabId: PropTypes.string.isRequired,
  className: PropTypes.string,
  meId: PropTypes.any,
  navigate: PropTypes.any,

  tabReloaded: PropTypes.func.isRequired,
  evolveTab: PropTypes.func.isRequired,

  controls: PropTypes.oneOf(['generic', 'game', 'user'])
}

const mapStateToProps = createStructuredSelector({
  meId: (state) => (state.session.credentials.me || {id: 'anonymous'}).id
})

const mapDispatchToProps = (dispatch) => ({
  navigate: (id, data) => dispatch(actions.navigate(id, data)),
  evolveTab: (id, path) => dispatch(actions.evolveTab({id, path})),
  tabDataFetched: (id, data) => dispatch(actions.tabDataFetched({id, data, timestamp: +new Date()})),
  tabReloaded: (id) => dispatch(actions.tabReloaded({id}))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BrowserMeat)
<|endoftext|>


<commit_before>import Dispatcher from './dispatcher'

export default class Action extends Dispatcher {
  constructor(url) {
    super()
    this.ws_ = new WebSocket(url)
    this.ws_.onopen    = this.onopen.bind(this)
    this.ws_.onmessage = this.onmessage.bind(this)
    this.ws_.onerror   = this.onerror.bind(this)
    this.ws_.onclose   = this.onclose.bind(this)
    this.activeUnitID_ = null
    document.addEventListener('keypress', e => {
      switch (e.keyCode) {
      case 113: // Q
        this.ability('Q', this.activeUnitID_)
        break
      case 119: // W
        this.ability('W', this.activeUnitID_)
        break
      case 101: // E
        this.ability('E', this.activeUnitID_)
        break
      case 114: // R
        this.ability('R', this.activeUnitID_)
        break
      case 32: // Space
        this.interrupt()
        break
      }
    })
  }
  onopen(e) {
    console.log('open', e)
  }
  onmessage(e) {
    console.log('message', e)
    const frame = JSON.parse(e.data)
    switch (frame.Type) {
      case 'Sync':
        this.dispatch('sync', frame.Data)
        break
      case 'Message':
        this.dispatch('message', frame.Data)
        break
      case 'Chat':
        this.dispatch('chat', frame.Data)
        break
      case 'Stage':
        this.dispatch('stage', frame.Data)
        break
      case 'Player':
        this.dispatch('player', frame.Data)
        break
      case 'UnitJoin':
        this.dispatch('unitJoin', frame.Data)
        break
      case 'UnitLeave':
        this.dispatch('unitLeave', frame.Data)
        break
      case 'UnitAttach':
        this.dispatch('unitAttach', frame.Data)
        break
      case 'UnitDetach':
        this.dispatch('unitDetach', frame.Data)
        break
      case 'UnitActivating':
        this.dispatch('unitActivating', frame.Data)
        break
      case 'UnitActivated':
        this.dispatch('unitActivated', frame.Data)
        break
      case 'UnitCooldown':
        this.dispatch('unitCooldown', frame.Data)
        break
      case 'UnitResource':
        this.dispatch('unitResource', frame.Data)
        break
      case 'Damage':
        this.dispatch('damage', frame.Data)
        break
      case 'Healing':
        this.dispatch('healing', frame.Data)
        break
    }
  }
  onclose(e) {
    console.log('close', e)
  }
  onerror(e) {
    console.log('error', e)
  }
  write(type, data) {
    const json = JSON.stringify({
      Type: type,
      Data: data
    })
    console.log(json)
    this.ws_.send(json)
  }
  activateUnit(id) {
    this.activeUnitID_ = id
  }
  deactivateUnit(id) {
    this.activeUnitID_ = null
  }
  profile(userName) {
    this.write('Profile', {UserName: userName})
  }
  chat(message) {
    this.write('Chat', {Message: message})
  }
  stage(stageID) {
    this.write('Stage', {StageID: stageID})
  }
  join(className) {
    this.write('Join', {ClassName: className})
  }
  leave() {
    this.write('Leave', {})
  }
  ability(abilityName, objectUnitID) {
    this.write('Ability', {
      AbilityName: abilityName,
      ObjectUnitID: objectUnitID
    })
  }
  interrupt() {
    this.write('Interrupt', {})
  }
}
<commit_msg>Show a message when connection is lost<commit_after>import Dispatcher from './dispatcher'

export default class Action extends Dispatcher {
  constructor(url) {
    super()
    this.ws_ = new WebSocket(url)
    this.ws_.onopen    = this.onopen.bind(this)
    this.ws_.onmessage = this.onmessage.bind(this)
    this.ws_.onerror   = this.onerror.bind(this)
    this.ws_.onclose   = this.onclose.bind(this)
    this.activeUnitID_ = null
    document.addEventListener('keypress', e => {
      switch (e.keyCode) {
      case 113: // Q
        this.ability('Q', this.activeUnitID_)
        break
      case 119: // W
        this.ability('W', this.activeUnitID_)
        break
      case 101: // E
        this.ability('E', this.activeUnitID_)
        break
      case 114: // R
        this.ability('R', this.activeUnitID_)
        break
      case 32: // Space
        this.interrupt()
        break
      }
    })
  }
  onopen(e) {
    console.log('open', e)
  }
  onmessage(e) {
    console.log('message', e)
    const frame = JSON.parse(e.data)
    switch (frame.Type) {
      case 'Sync':
        this.dispatch('sync', frame.Data)
        break
      case 'Message':
        this.dispatch('message', frame.Data)
        break
      case 'Chat':
        this.dispatch('chat', frame.Data)
        break
      case 'Stage':
        this.dispatch('stage', frame.Data)
        break
      case 'Player':
        this.dispatch('player', frame.Data)
        break
      case 'UnitJoin':
        this.dispatch('unitJoin', frame.Data)
        break
      case 'UnitLeave':
        this.dispatch('unitLeave', frame.Data)
        break
      case 'UnitAttach':
        this.dispatch('unitAttach', frame.Data)
        break
      case 'UnitDetach':
        this.dispatch('unitDetach', frame.Data)
        break
      case 'UnitActivating':
        this.dispatch('unitActivating', frame.Data)
        break
      case 'UnitActivated':
        this.dispatch('unitActivated', frame.Data)
        break
      case 'UnitCooldown':
        this.dispatch('unitCooldown', frame.Data)
        break
      case 'UnitResource':
        this.dispatch('unitResource', frame.Data)
        break
      case 'Damage':
        this.dispatch('damage', frame.Data)
        break
      case 'Healing':
        this.dispatch('healing', frame.Data)
        break
    }
  }
  onclose(e) {
    console.log('close', e)
    this.dispatch('message', {
      Message: 'ERROR: Lost connection'
    })
  }
  onerror(e) {
    console.log('error', e)
  }
  write(type, data) {
    const json = JSON.stringify({
      Type: type,
      Data: data
    })
    console.log(json)
    this.ws_.send(json)
  }
  activateUnit(id) {
    this.activeUnitID_ = id
  }
  deactivateUnit(id) {
    this.activeUnitID_ = null
  }
  profile(userName) {
    this.write('Profile', {UserName: userName})
  }
  chat(message) {
    this.write('Chat', {Message: message})
  }
  stage(stageID) {
    this.write('Stage', {StageID: stageID})
  }
  join(className) {
    this.write('Join', {ClassName: className})
  }
  leave() {
    this.write('Leave', {})
  }
  ability(abilityName, objectUnitID) {
    this.write('Ability', {
      AbilityName: abilityName,
      ObjectUnitID: objectUnitID
    })
  }
  interrupt() {
    this.write('Interrupt', {})
  }
}
<|endoftext|>


<commit_before>const api = (endpoint) => {
  if (! endpoint) {
    return AppSettings.API_URL;
  }

  return AppSettings.API_URL + endpoint;
};

const AppSettings = {
  // constants
  APP_NAME: 'TrackIt',

  API_URL: 'http://trackit.dev/',
  API_TOKEN: 'api_token',

  ROLES: {
    GUEST:          1,
    CUSTOMER:       2,
    STUDENT:        3,
    TEACHER:        4,
    ADMINISTRATOR:  5,
  },

  ROLE_IDS: {
    1: 'GUEST',
    2: 'CUSTOMER',
    3: 'STUDENT',
    4: 'TEACHER',
    5: 'ADMINISTRATOR',
  },

  ROLE_ICON_IDS: {
    1: 'fa fa-user',
    2: 'fa fa-user',
    3: 'fa fa-graduation-cap',
    4: 'fa fa-university',
    5: 'fa fa-user-plus',
  },

  PROJECT_ROLES: {
    STAKEHOLDER:    1,
    STUDENT:        2,
    TEACHER:        3,
    SUPERVISOR:     4,
  },

  PROJECT_ROLE_IDS: {
    1:  'STAKEHOLDER',
    2:  'STUDENT',
    3:  'TEACHER',
    4:  'SUPERVISOR',
  },

  PROJECT_STATUSES: {
    NOT_COMPLETED:  1,
    COMPLETED:      2,
    PUBLISHED:      3,
  },

  PROJECT_STATUS_IDS: {
    1:  'NOT COMPLETED',
    2:  'COMPLETED',
    3:  'PUBLISHED',
  },

  PROPOSAL_STATUSES: {
    NOT_REVIEWED:   1,
    UNDER_REVIEW:   2,
    NOT_APPROVED:   3,
    APPROVED:       4,
    ARCHIVED:       5,
  },

  PROPOSAL_STATUS_IDS: {
    1:  'NOT REVIEWED',
    2:  'UNDER REVIEW',
    3:  'NOT APPROVED',
    4:  'APPROVED',
    5:  'ARCHIVED',
  },

  VALIDATION: {
    REGISTRATION: {
      // All normal characters
      USERNAME:     '^[a-zA-ZåäöÅÄÖ0-9]+$',

      // Must start with normal character, max 30 characters
      DISPLAYNAME:  '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´"]{1}[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s-]*$',

      // Must contain one uppercase, one lowercase. Minimum 8 characters.
      PASSWORD:     '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$',
    },

    PROPOSAL: {
      TITLE:        '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s]*$',
    },

  },

  VALIDATION: {
    REGISTRATION: {
      // All normal characters
      USERNAME:     '^[a-zA-ZåäöÅÄÖ0-9]+$',

      // Must start with normal character, max 30 characters
      DISPLAYNAME:  '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´"]{1}[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s-]*$',

      // Must contain one uppercase, one lowercase. Minimum 8 characters.
      PASSWORD:     '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$',
    },

    PROPOSAL: {
      TITLE:        '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s]*$',
    },

  },

  // global helpers
  api: api,

  getProposalStatus: (status_id) => {
    return AppSettings.PROPOSAL_STATUS_IDS[status_id];
  },

  getProjectStatus: (status_id) => {
    return AppSettings.PROJECT_STATUS_IDS[status_id];
  },

  getProjectRole: (role_id) => {
    return AppSettings.PROJECT_ROLE_IDS[role_id];
  },

  getRoleIcon: (icon_id) => {
    return AppSettings.ROLE_ICON_IDS[icon_id];
  },

};

export default AppSettings;
<commit_msg>Added method to get role icon in config<commit_after>const api = (endpoint) => {
  if (! endpoint) {
    return AppSettings.API_URL;
  }

  return AppSettings.API_URL + endpoint;
};

const AppSettings = {
  // constants
  APP_NAME: 'TrackIt',

  API_URL: 'http://trackit.dev/',
  API_TOKEN: 'api_token',

  ROLES: {
    GUEST:          1,
    CUSTOMER:       2,
    STUDENT:        3,
    TEACHER:        4,
    ADMINISTRATOR:  5,
  },

  ROLE_IDS: {
    1: 'GUEST',
    2: 'CUSTOMER',
    3: 'STUDENT',
    4: 'TEACHER',
    5: 'ADMINISTRATOR',
  },

  ROLE_ICON_IDS: {
    1: 'fa fa-user',
    2: 'fa fa-user',
    3: 'fa fa-graduation-cap',
    4: 'fa fa-university',
    5: 'fa fa-user-plus',
  },

  PROJECT_ROLES: {
    STAKEHOLDER:    1,
    STUDENT:        2,
    TEACHER:        3,
    SUPERVISOR:     4,
  },

  PROJECT_ROLE_IDS: {
    1:  'STAKEHOLDER',
    2:  'STUDENT',
    3:  'TEACHER',
    4:  'SUPERVISOR',
  },

  PROJECT_STATUSES: {
    NOT_COMPLETED:  1,
    COMPLETED:      2,
    PUBLISHED:      3,
  },

  PROJECT_STATUS_IDS: {
    1:  'NOT COMPLETED',
    2:  'COMPLETED',
    3:  'PUBLISHED',
  },

  PROPOSAL_STATUSES: {
    NOT_REVIEWED:   1,
    UNDER_REVIEW:   2,
    NOT_APPROVED:   3,
    APPROVED:       4,
    ARCHIVED:       5,
  },

  PROPOSAL_STATUS_IDS: {
    1:  'NOT REVIEWED',
    2:  'UNDER REVIEW',
    3:  'NOT APPROVED',
    4:  'APPROVED',
    5:  'ARCHIVED',
  },

  VALIDATION: {
    REGISTRATION: {
      // All normal characters
      USERNAME:     '^[a-zA-ZåäöÅÄÖ0-9]+$',

      // Must start with normal character, max 30 characters
      DISPLAYNAME:  '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´"]{1}[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s-]*$',

      // Must contain one uppercase, one lowercase. Minimum 8 characters.
      PASSWORD:     '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$',
    },

    PROPOSAL: {
      TITLE:        '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s]*$',
    },

  },

  VALIDATION: {
    REGISTRATION: {
      // All normal characters
      USERNAME:     '^[a-zA-ZåäöÅÄÖ0-9]+$',

      // Must start with normal character, max 30 characters
      DISPLAYNAME:  '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´"]{1}[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s-]*$',

      // Must contain one uppercase, one lowercase. Minimum 8 characters.
      PASSWORD:     '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$',
    },

    PROPOSAL: {
      TITLE:        '^[a-zA-ZåäöÅÄÖ0-9!"#¤%&/()=?`´\\s]*$',
    },

  },

  // global helpers
  api: api,

  getRole: (role_id) => {
    return AppSettings.ROLE_IDS[role_id];
  },

  getProposalStatus: (status_id) => {
    return AppSettings.PROPOSAL_STATUS_IDS[status_id];
  },

  getProjectStatus: (status_id) => {
    return AppSettings.PROJECT_STATUS_IDS[status_id];
  },

  getProjectRole: (role_id) => {
    return AppSettings.PROJECT_ROLE_IDS[role_id];
  },

  getRoleIcon: (icon_id) => {
    return AppSettings.ROLE_ICON_IDS[icon_id];
  },

};

export default AppSettings;
<|endoftext|>


<commit_before>export const githubFileUrlRoot = 'https://github.com/blockstack/blockstack/blob/master'

export const socialLinks = {
  twitter: 'https://www.twitter.com/blockstack',
  youtube: 'https://www.youtube.com/channel/UC3J2iHnyt2JtOvtGVf_jpHQ',
  slack: 'http://chat.blockstack.org',
  meetup: 'http://www.meetup.com/topics/blockstack/',
  reddit: 'https://www.reddit.com/r/blockstack',
  forum: 'https://forum.blockstack.org',
  blog: 'http://blog.blockstack.org',
  github: 'https://github.com/blockstack',
  branding: 'https://projects.invisionapp.com/boards/HE2VVROFSGB27/'
}

export const installationLinks = {
  portalMacOS: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.9.0/Blockstack-v0.9.app.zip',
  portalDevelopers: 'https://github.com/blockstack/blockstack-browser/releases',
  portalDevelopersMacOS: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.23.0/Blockstack-for-macOS-v0.23.0.dmg',
  portalDevelopersWindows: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.23.0/Blockstack-for-win10-v0.23.0.msi',
  portalDevelopersLinux: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.23.0/Blockstack-for-Linux-v0.23.0.sh',
  portalDevelopersWeb: 'https://browser.blockstack.org',
  cliDevelopers: 'https://github.com/blockstack/blockstack-core',
}

export const jobs = [
  {
    'title': 'Frontend Engineer',
    'url': 'https://jobs.lever.co/blockstack/431188cd-63a3-4314-8d30-55a4d91722af?lever-origin=applied&lever-source%5B%5D=Blockstack.org'
  },
  {
    'title': 'Backend Engineer',
    'url': 'https://jobs.lever.co/blockstack/cf85b3ac-0e7c-4070-a8a4-af687a3fbac9?lever-origin=applied&lever-source%5B%5D=blockstack.org'
  },
  {
    'title': 'Technical Product Manager',
    'url': 'https://jobs.lever.co/blockstack/f6a98e76-b1f6-4284-9c39-1bda11e7160a?lever-origin=applied&lever-source%5B%5D=Blockstack.com'
  },
]

export const featuredApps = [
  {
    name: 'Coins',
    icon: '/images/logos/app-icon-coins-512x512.png',
    description: `Coins is a decentralized and encrypted cryptocurrency portfolio app. Analyze your performance with flexible, customizable reporting. Track your holdings, watch your favorite tokens and optionally send pricing alerts.`
  },
  {
    name: 'Graphite',
    icon: '/images/logos/app-icon-graphite-512x512.png',
    description: `Graphite gives you ownership over your data and files. Write, create, connect—all without giving up your privacy.`
  },
  {
    name: 'Casa',
    icon: '/images/logos/app-icon-casa-512x512.png',
    description: `Casa is the world's first open source homesharing protocol and decentralized network. Book rooms without platform fees or trusted intermediaries and securely transact with encrypted chat and multi-party escrow.`
  },
  {
    name: 'Afia',
    icon: '/images/logos/app-icon-afia-512x512.png',
    description: 'Afia gives individuals the power to own and manage their personal health records. Keep your health data safe on your personal encrypted cloud storage and securely share your data with healthcare providers as you see fit.'
  },
  {
    name: 'DotPodcast',
    icon: '/images/logos/app-icon-dotpodcast-512x512.png',
    description: 'DotPodcast helps creators get paid, protects against censorship, and allows listeners to enjoy podcasts without privacy-invading tracking.'
  },
]

export const teamMembers = [
  {
    blockstackId: 'muneeb',
    name: 'Muneeb Ali',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb',
    bio: 'Muneeb Ali is a distributed systems PhD from Princeton and gives guest lectures on cloud computing there. He went through Y Combinator and was awarded a J. William Fulbright Fellowship.',
  },
  {
    blockstackId: 'ryan',
    name: 'Ryan Shea',
    avatar: 'https://s3.amazonaws.com/kd4/ryan',
    bio: 'Ryan Shea studied engineering at Princeton, went through Y Combinator, and was inducted into the Forbes 30 Under 30. He has authored several popular open source libraries in cryptography and blockchains.',
  },
  {
    blockstackId: 'judecn',
    name: 'Jude Nelson',
    avatar: 'https://s3.amazonaws.com/kd4/judecn',
    bio: 'Jude Nelson did PhD-level research in distributed systems at Princeton and worked as a core member of PlanetLab, which received the ACM Test of Time Award for enabling planetary scale experimentation and deployment.',
  },
  {
    blockstackId: 'ablankstein',
    name: 'Aaron Blankstein',
    avatar: '/images/avatars/aaron-blankstein.jpg',
    bio: 'Aaron joined the Blockstack engineering team from an academic background, finishing his PhD in 2017. His doctoral research spanned a range of topics, mostly focusing on web application performance and security. He’s worked in information flow tracking systems, caching algorithms, compilers, and applied cryptography. 10+ years of Emacs usage.',
  },
  {
    blockstackId: 'larry',
    name: 'Larry Salibra',
    avatar: 'https://s3.amazonaws.com/kd4/larry',
    bio: 'Larry Salibra co-founded Pay4Bugs, a crowdsourced security analysis and software testing platform, and is a founding member of the Bitcoin Association of Hong Kong. His work exposed private data leakage in Chinese Tinder clone Tantan, and how Apple censors apps in China.'
  },
  {
    blockstackId: 'ginaabrams',
    name: 'Gina Abrams',
    avatar: '/images/avatars/gina-abrams.jpg',
    bio: 'Gina Abrams studied Psychology and Social Sciences at Binghamton University. Previously she spent two years in agency technical recruiting, during which time she assisted numerous NYC start-ups in mission critical DevOps, SRE and Security hires.',
  },
  {
    blockstackId: 'guylepage3',
    name: 'Guy Lepage',
    avatar: 'https://s3.amazonaws.com/kd4/guylepage3',
    bio: 'Guy Lepage is an award-winning designer, who has been recognized by the Lotus Awards and the New York Festivals for branding, and is a finalist for the INDEX award 2017. He has worked on many brands from Toyota to Disney and was a startup founder of a digital ad agency earlier.'
  },
  {
    blockstackId: 'pstanley',
    name: 'Patrick Stanley',
    avatar: '/images/avatars/patrick-stanley.jpg',
    bio: 'Patrick Stanley studied Economics and Psychology at Johns Hopkins, and was the first employee at data-driven lending startup Earnest which sold to Navient for $155m in 2017. In a growth role there, he helped it scale from from 1 to 200 employees and go from $0 to $2B in originations within 3 years.'
  },
  {
    blockstackId: 'jack_zampolin',
    name: 'Jack Zampolin',
    avatar: '/images/avatars/jack-zampolin.jpg',
    bio: 'Jack came to Blockstack from InfluxData where he engaged with and grew the developer community as a Developer Evangelist. A self taught programmer, he is an active contributor to Open Source projects including Blockstack, InfluxDB, and Helm.',
  },
  {
    blockstackId: 'yukan',
    name: 'Ken Liao',
    avatar: '/images/avatars/ken-liao.jpg',
    bio: 'Ken Liao studied Computer Engineering at the University of Waterloo and co-founded the fashion brand marketplace, PR-A-PO. He designed and built mobile apps which were featured as Best New App on the Apple App Store.',
  },
  {
    blockstackId: 'sebastiandunkel',
    name: 'Sebastian Dunkel',
    avatar: '/images/avatars/sebastian-dunkel.jpg',
    bio: 'Sebastian received his MA in Linguistics and Communications from the University of Heidelberg, Germany, and the University of Brighton, UK. Before joining the Blockstack team, he worked in localization and product support for Apple, Vimeo, and Master & Dynamic.',
  },
  {
    blockstackId: '',
    name: 'Shannon Voight',
    avatar: '/images/avatars/shannon-voight.jpg',
    bio: 'Shannon drives event and marketing programs focusing on Blockstack\'s growth and expansion. Prior to joining Blockstack in 2017, Shannon spent over 11 years honing her skills as event and program producer, talent manager and brand strategist at Condé Nast and Time Inc. ',
  },
  {
    blockstackId: 'xan',
    name: 'Xan Ditkoff',
    avatar: '/images/avatars/xan-ditkoff.jpg',
    bio: 'Xan came to Blockstack with backgrounds in private equity, investment banking, and the military. He is passionate about driving the creation, growth, and expansion of application ecosystems through his growth role and with the Signature Fund.',
  },
  {
    blockstackId: 'clintnelson',
    name: 'Clint Nelsen',
    avatar: '/images/avatars/clint-nelsen.jpg',
    bio: 'Clint is part of the growth team at Blockstack and previously was the founder of Startup Weekend.  Clint grew Startup Weekend to a team of 80 employees and community of 8,000 local organizers producing 3,000+ hackathons spanning 600 cities and 147 countries.  Clint is a published author and regularly serves as a lecturer on entrepreneurship & startups.',
  },
  {
    blockstackId: 'hologram',
    name: 'Chase Wackerfuss',
    avatar: '/images/avatars/chase-wackerfuss.jpg',
    bio: 'Chase joined Blockstack\'s engineering team after being an active contributor to the community. A self taught programmer and UI designer, his focus is on cultivating a user-friendly front end developer experience and helping scale client-side applications.',
  },
  {
    blockstackId: 'mfreed',
    name: 'Michael Freedman, Technical Advisor',
    avatar: '/images/avatars/mike-freedman.jpg',
    bio: 'Michael Freedman is a distributed systems Professor at Princeton. He was awarded the Presidential Early Career Award (PECASE), the Sloan Fellowship, and his research has led to commercial products and deployed systems reaching millions of users daily.',
  },
  {
    blockstackId: 'jpsingh',
    name: 'JP Singh, Technical Advisor',
    avatar: '/images/avatars/jp-singh.jpg',
    bio: 'JP Singh received his PhD from from Stanford University and is a leading authority on scalable infrastructure and applications. He is the director of the Princeton CTO Program, serves on the board of 8x8, Inc., and has been a consultant to Intel, Microsoft, and the U.S. Government.',
  },
]

export const blogAuthors = {
  'larry.id': { blockstackId: 'larry',
    name: 'Larry Salibra',
    avatar: 'https://s3.amazonaws.com/kd4/larry',
    twitter: 'larrysalibra',
    github: 'larrysalibra' },
  'judecn.id' :{ blockstackId: 'judecn',
    name: 'Jude Nelson',
    avatar: 'https://s3.amazonaws.com/kd4/judecn',
    twitter: 'judecnelson',
    github: 'jcnelson' },
  'guylepage3.id': { blockstackId: 'guylepage3',
    name: 'Guy Lepage',
    avatar: 'https://s3.amazonaws.com/kd4/guylepage3',
    twitter: 'guylepage3',
    github: 'guylepage3' },
  'muneeb.id': { blockstackId: 'muneeb',
    name: 'Muneeb Ali',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb',
    twitter: 'muneeb',
    github: 'muneeb-ali' },
  'ryan.id': { blockstackId: 'ryan',
    name: 'Ryan Shea',
    avatar: 'https://s3.amazonaws.com/kd4/ryan',
    twitter: 'ryaneshea',
    github: 'shea256' },
  'blockstack.id': { blockstackId: 'blockstack',
    name: 'Muneeb & Ryan',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb-and-ryan.jpg',
    twitter: 'blockstack',
    github: 'blockstack' },
  'founders.id': { blockstackId: 'blockstack',
    name: 'Ryan & Muneeb',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb-and-ryan.jpg',
    twitter: 'blockstack',
    github: 'blockstack' },
  'mfreed.id': { 'blockstackId': 'mfreed',
    name: 'Mike Freedman',
    avatar: '/images/avatars/mike-freedman.jpg',
    twitter: 'michaelfreedman',
    github: 'mfreed' },
  'stanley1.id': {
    blockstackId: 'stanley1',
    name: 'Patrick Stanley',
    avatar: 'https://s3.amazonaws.com/kd4/stanley1',
    twitter: 'patrickwstanley',
    github: 'pstan26' },
  'yukan.id': { blockstackId: 'yukan',
    name: 'Ken Liao',
    avatar: 'https://blockstack.org/images/avatars/ken-liao.jpg',
    twitter: 'yukanl',
    github: 'yknl' }
}
<commit_msg>update download links to 0.25.0<commit_after>export const githubFileUrlRoot = 'https://github.com/blockstack/blockstack/blob/master'

export const socialLinks = {
  twitter: 'https://www.twitter.com/blockstack',
  youtube: 'https://www.youtube.com/channel/UC3J2iHnyt2JtOvtGVf_jpHQ',
  slack: 'http://chat.blockstack.org',
  meetup: 'http://www.meetup.com/topics/blockstack/',
  reddit: 'https://www.reddit.com/r/blockstack',
  forum: 'https://forum.blockstack.org',
  blog: 'http://blog.blockstack.org',
  github: 'https://github.com/blockstack',
  branding: 'https://projects.invisionapp.com/boards/HE2VVROFSGB27/'
}

export const installationLinks = {
  portalMacOS: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.9.0/Blockstack-v0.9.app.zip',
  portalDevelopers: 'https://github.com/blockstack/blockstack-browser/releases',
  portalDevelopersMacOS: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.25.0/Blockstack-for-macOS-v0.25.0.dmg',
  portalDevelopersWindows: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.25.0/Blockstack-for-win10-v0.25.0.msi',
  portalDevelopersLinux: 'https://github.com/blockstack/blockstack-browser/releases/download/v0.25.0/Blockstack-for-Linux-v0.25.0.sh',
  portalDevelopersWeb: 'https://browser.blockstack.org',
  cliDevelopers: 'https://github.com/blockstack/blockstack-core',
}

export const jobs = [
  {
    'title': 'Frontend Engineer',
    'url': 'https://jobs.lever.co/blockstack/431188cd-63a3-4314-8d30-55a4d91722af?lever-origin=applied&lever-source%5B%5D=Blockstack.org'
  },
  {
    'title': 'Backend Engineer',
    'url': 'https://jobs.lever.co/blockstack/cf85b3ac-0e7c-4070-a8a4-af687a3fbac9?lever-origin=applied&lever-source%5B%5D=blockstack.org'
  },
  {
    'title': 'Technical Product Manager',
    'url': 'https://jobs.lever.co/blockstack/f6a98e76-b1f6-4284-9c39-1bda11e7160a?lever-origin=applied&lever-source%5B%5D=Blockstack.com'
  },
]

export const featuredApps = [
  {
    name: 'Coins',
    icon: '/images/logos/app-icon-coins-512x512.png',
    description: `Coins is a decentralized and encrypted cryptocurrency portfolio app. Analyze your performance with flexible, customizable reporting. Track your holdings, watch your favorite tokens and optionally send pricing alerts.`
  },
  {
    name: 'Graphite',
    icon: '/images/logos/app-icon-graphite-512x512.png',
    description: `Graphite gives you ownership over your data and files. Write, create, connect—all without giving up your privacy.`
  },
  {
    name: 'Casa',
    icon: '/images/logos/app-icon-casa-512x512.png',
    description: `Casa is the world's first open source homesharing protocol and decentralized network. Book rooms without platform fees or trusted intermediaries and securely transact with encrypted chat and multi-party escrow.`
  },
  {
    name: 'Afia',
    icon: '/images/logos/app-icon-afia-512x512.png',
    description: 'Afia gives individuals the power to own and manage their personal health records. Keep your health data safe on your personal encrypted cloud storage and securely share your data with healthcare providers as you see fit.'
  },
  {
    name: 'DotPodcast',
    icon: '/images/logos/app-icon-dotpodcast-512x512.png',
    description: 'DotPodcast helps creators get paid, protects against censorship, and allows listeners to enjoy podcasts without privacy-invading tracking.'
  },
]

export const teamMembers = [
  {
    blockstackId: 'muneeb',
    name: 'Muneeb Ali',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb',
    bio: 'Muneeb Ali is a distributed systems PhD from Princeton and gives guest lectures on cloud computing there. He went through Y Combinator and was awarded a J. William Fulbright Fellowship.',
  },
  {
    blockstackId: 'ryan',
    name: 'Ryan Shea',
    avatar: 'https://s3.amazonaws.com/kd4/ryan',
    bio: 'Ryan Shea studied engineering at Princeton, went through Y Combinator, and was inducted into the Forbes 30 Under 30. He has authored several popular open source libraries in cryptography and blockchains.',
  },
  {
    blockstackId: 'judecn',
    name: 'Jude Nelson',
    avatar: 'https://s3.amazonaws.com/kd4/judecn',
    bio: 'Jude Nelson did PhD-level research in distributed systems at Princeton and worked as a core member of PlanetLab, which received the ACM Test of Time Award for enabling planetary scale experimentation and deployment.',
  },
  {
    blockstackId: 'ablankstein',
    name: 'Aaron Blankstein',
    avatar: '/images/avatars/aaron-blankstein.jpg',
    bio: 'Aaron joined the Blockstack engineering team from an academic background, finishing his PhD in 2017. His doctoral research spanned a range of topics, mostly focusing on web application performance and security. He’s worked in information flow tracking systems, caching algorithms, compilers, and applied cryptography. 10+ years of Emacs usage.',
  },
  {
    blockstackId: 'larry',
    name: 'Larry Salibra',
    avatar: 'https://s3.amazonaws.com/kd4/larry',
    bio: 'Larry Salibra co-founded Pay4Bugs, a crowdsourced security analysis and software testing platform, and is a founding member of the Bitcoin Association of Hong Kong. His work exposed private data leakage in Chinese Tinder clone Tantan, and how Apple censors apps in China.'
  },
  {
    blockstackId: 'ginaabrams',
    name: 'Gina Abrams',
    avatar: '/images/avatars/gina-abrams.jpg',
    bio: 'Gina Abrams studied Psychology and Social Sciences at Binghamton University. Previously she spent two years in agency technical recruiting, during which time she assisted numerous NYC start-ups in mission critical DevOps, SRE and Security hires.',
  },
  {
    blockstackId: 'guylepage3',
    name: 'Guy Lepage',
    avatar: 'https://s3.amazonaws.com/kd4/guylepage3',
    bio: 'Guy Lepage is an award-winning designer, who has been recognized by the Lotus Awards and the New York Festivals for branding, and is a finalist for the INDEX award 2017. He has worked on many brands from Toyota to Disney and was a startup founder of a digital ad agency earlier.'
  },
  {
    blockstackId: 'pstanley',
    name: 'Patrick Stanley',
    avatar: '/images/avatars/patrick-stanley.jpg',
    bio: 'Patrick Stanley studied Economics and Psychology at Johns Hopkins, and was the first employee at data-driven lending startup Earnest which sold to Navient for $155m in 2017. In a growth role there, he helped it scale from from 1 to 200 employees and go from $0 to $2B in originations within 3 years.'
  },
  {
    blockstackId: 'jack_zampolin',
    name: 'Jack Zampolin',
    avatar: '/images/avatars/jack-zampolin.jpg',
    bio: 'Jack came to Blockstack from InfluxData where he engaged with and grew the developer community as a Developer Evangelist. A self taught programmer, he is an active contributor to Open Source projects including Blockstack, InfluxDB, and Helm.',
  },
  {
    blockstackId: 'yukan',
    name: 'Ken Liao',
    avatar: '/images/avatars/ken-liao.jpg',
    bio: 'Ken Liao studied Computer Engineering at the University of Waterloo and co-founded the fashion brand marketplace, PR-A-PO. He designed and built mobile apps which were featured as Best New App on the Apple App Store.',
  },
  {
    blockstackId: 'sebastiandunkel',
    name: 'Sebastian Dunkel',
    avatar: '/images/avatars/sebastian-dunkel.jpg',
    bio: 'Sebastian received his MA in Linguistics and Communications from the University of Heidelberg, Germany, and the University of Brighton, UK. Before joining the Blockstack team, he worked in localization and product support for Apple, Vimeo, and Master & Dynamic.',
  },
  {
    blockstackId: '',
    name: 'Shannon Voight',
    avatar: '/images/avatars/shannon-voight.jpg',
    bio: 'Shannon drives event and marketing programs focusing on Blockstack\'s growth and expansion. Prior to joining Blockstack in 2017, Shannon spent over 11 years honing her skills as event and program producer, talent manager and brand strategist at Condé Nast and Time Inc. ',
  },
  {
    blockstackId: 'xan',
    name: 'Xan Ditkoff',
    avatar: '/images/avatars/xan-ditkoff.jpg',
    bio: 'Xan came to Blockstack with backgrounds in private equity, investment banking, and the military. He is passionate about driving the creation, growth, and expansion of application ecosystems through his growth role and with the Signature Fund.',
  },
  {
    blockstackId: 'clintnelson',
    name: 'Clint Nelsen',
    avatar: '/images/avatars/clint-nelsen.jpg',
    bio: 'Clint is part of the growth team at Blockstack and previously was the founder of Startup Weekend.  Clint grew Startup Weekend to a team of 80 employees and community of 8,000 local organizers producing 3,000+ hackathons spanning 600 cities and 147 countries.  Clint is a published author and regularly serves as a lecturer on entrepreneurship & startups.',
  },
  {
    blockstackId: 'hologram',
    name: 'Chase Wackerfuss',
    avatar: '/images/avatars/chase-wackerfuss.jpg',
    bio: 'Chase joined Blockstack\'s engineering team after being an active contributor to the community. A self taught programmer and UI designer, his focus is on cultivating a user-friendly front end developer experience and helping scale client-side applications.',
  },
  {
    blockstackId: 'mfreed',
    name: 'Michael Freedman, Technical Advisor',
    avatar: '/images/avatars/mike-freedman.jpg',
    bio: 'Michael Freedman is a distributed systems Professor at Princeton. He was awarded the Presidential Early Career Award (PECASE), the Sloan Fellowship, and his research has led to commercial products and deployed systems reaching millions of users daily.',
  },
  {
    blockstackId: 'jpsingh',
    name: 'JP Singh, Technical Advisor',
    avatar: '/images/avatars/jp-singh.jpg',
    bio: 'JP Singh received his PhD from from Stanford University and is a leading authority on scalable infrastructure and applications. He is the director of the Princeton CTO Program, serves on the board of 8x8, Inc., and has been a consultant to Intel, Microsoft, and the U.S. Government.',
  },
]

export const blogAuthors = {
  'larry.id': { blockstackId: 'larry',
    name: 'Larry Salibra',
    avatar: 'https://s3.amazonaws.com/kd4/larry',
    twitter: 'larrysalibra',
    github: 'larrysalibra' },
  'judecn.id' :{ blockstackId: 'judecn',
    name: 'Jude Nelson',
    avatar: 'https://s3.amazonaws.com/kd4/judecn',
    twitter: 'judecnelson',
    github: 'jcnelson' },
  'guylepage3.id': { blockstackId: 'guylepage3',
    name: 'Guy Lepage',
    avatar: 'https://s3.amazonaws.com/kd4/guylepage3',
    twitter: 'guylepage3',
    github: 'guylepage3' },
  'muneeb.id': { blockstackId: 'muneeb',
    name: 'Muneeb Ali',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb',
    twitter: 'muneeb',
    github: 'muneeb-ali' },
  'ryan.id': { blockstackId: 'ryan',
    name: 'Ryan Shea',
    avatar: 'https://s3.amazonaws.com/kd4/ryan',
    twitter: 'ryaneshea',
    github: 'shea256' },
  'blockstack.id': { blockstackId: 'blockstack',
    name: 'Muneeb & Ryan',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb-and-ryan.jpg',
    twitter: 'blockstack',
    github: 'blockstack' },
  'founders.id': { blockstackId: 'blockstack',
    name: 'Ryan & Muneeb',
    avatar: 'https://s3.amazonaws.com/kd4/muneeb-and-ryan.jpg',
    twitter: 'blockstack',
    github: 'blockstack' },
  'mfreed.id': { 'blockstackId': 'mfreed',
    name: 'Mike Freedman',
    avatar: '/images/avatars/mike-freedman.jpg',
    twitter: 'michaelfreedman',
    github: 'mfreed' },
  'stanley1.id': {
    blockstackId: 'stanley1',
    name: 'Patrick Stanley',
    avatar: 'https://s3.amazonaws.com/kd4/stanley1',
    twitter: 'patrickwstanley',
    github: 'pstan26' },
  'yukan.id': { blockstackId: 'yukan',
    name: 'Ken Liao',
    avatar: 'https://blockstack.org/images/avatars/ken-liao.jpg',
    twitter: 'yukanl',
    github: 'yknl' }
}
<|endoftext|>


<commit_before>/**
 * Script to parse and write data for a single season
 * This is supposed to be called in child processes via the main parse script
 */

const fs = require('fs-extra')
const path = require('path')
const utils = require('./utils')
const fct = require('flusight-csv-tools')
const moize = require('moize')

// Directory with CSVs
const DATA_DIR = './data'

// Directory for output JSONs
const OUT_DIR = './src/assets/data'

// Look for seasons in the data directory
const SEASONS = utils.getSubDirectories(DATA_DIR)

// Take input season from command line argument
const SEASON_ID = parseInt(process.argv[2])
const SEASON = `${SEASON_ID}-${SEASON_ID + 1}`
const MODELS_DIR = utils.getSubDirectories(path.join(DATA_DIR, SEASON))

/**
 * Return season name for writing files
 */
function getSeasonName () {
  if (SEASONS.indexOf(SEASON) === SEASONS.length - 1) {
    return 'latest'
  } else {
    return SEASON
  }
}

async function writeSeasonFile (data) {
  await utils.writeJSON(path.join(OUT_DIR, `season-${getSeasonName()}.json`), data)
}

async function writeScoresFile (data) {
  await utils.writeJSON(path.join(OUT_DIR, `scores-${getSeasonName()}.json`), data)
}

async function writeDistsFile (data, regionId) {
  let distsDir = path.join(OUT_DIR, 'distributions')
  await fs.ensureDir(distsDir)

  let outputFile
  if (regionId === 'nat') {
    // We only use latest identifier in the latest AND nat data
    outputFile = path.join(distsDir, `season-${getSeasonName()}-nat.json`)
  } else {
    outputFile = path.join(distsDir, `season-${SEASON}-${regionId}.json`)
  }

  await utils.writeJSON(outputFile, data)
}

/**
 * Return region data with nulls filled in for missing epiweeks
 */
function parseRegionActual (seasonData, regionId) {
  let regionSubset = seasonData[regionId]

  let epiweeks = fct.utils.epiweek.seasonEpiweeks(SEASON_ID)
  return epiweeks.map(ew => {
    let ewData = regionSubset.find(({ epiweek }) => epiweek === ew)
    // Rename keys to match the expectations of flusight
    // and extend by filling in missing epiweeks
    if (ewData) {
      return {
        week: ewData.epiweek,
        actual: ewData.wili,
        lagData: ewData.lagData.map(({ lag, wili }) => { return { lag, value: wili } })
      }
    } else {
      return {
        week: ew,
        actual: null,
        lagData: []
      }
    }
  })
}

/**
 * Return fct csv object for given specifications using the cache
 */
const getCsv = moize(function (modelPath, epiweek) {
  let modelId = path.basename(modelPath)
  return new fct.Csv(path.join(modelPath, epiweek + '.csv'), epiweek, modelId)
})

/**
 * Return csv score using cache
 */
const getCsvScore = moize(async function (csv) {
  return await fct.score.score(csv)
}, { isPromise: true })

/**
 * Return formatted point data for the region using the cvs object
 */
function parsePointData (csv, regionId) {
  let seasonEpiweeks = fct.utils.epiweek.seasonEpiweeks(SEASON_ID)

  function getTargetData (target) {
    let cis = [90, 50]
    let point = csv.getPoint(target, regionId)
    let ranges = cis.map(c => csv.getConfidenceRange(target, regionId, c))
    let low = ranges.map(r => r[0])
    let high = ranges.map(r => r[1])

    if (['peak-wk', 'onset-wk'].indexOf(target) > -1) {
      // Return indices for time based targets
      point = seasonEpiweeks.indexOf(point)
      high = high.map(d => seasonEpiweeks.indexOf(d))
      low = low.map(d => seasonEpiweeks.indexOf(d))
    }

    return { point, low, high }
  }

  return {
    series: ['1-ahead', '2-ahead', '3-ahead', '4-ahead'].map(getTargetData),
    peakValue: getTargetData('peak'),
    peakTime: getTargetData('peak-wk'),
    onsetTime: getTargetData('onset-wk')
  }
}

/**
 * Return bin data for the given region using the csv object
 */
function parseBinData (csv, regionId) {
  function getTargetData (target) {
    let bins = csv.getBins(target, regionId)

    // There are different types of bins that we need to consider
    if (bins.length === 131) {
      // These are regular, new style, wili bins with last one being
      // [13, 100] which we skip
      return { bins: fct.utils.bins.sliceSumBins(bins.slice(0, -1), 5).map(b => b[2]) }
    } else if (bins.length === 27) {
      // These are old style wili bins with last one being [13, 100] which
      // we skip
      return { bins: bins.slice(0, -1).map(b => b[2]) }
    } else if ((bins.length === 33) && (target === 'peak-wk')) {
      return { bins: bins.map(b => b[2]) }
    } else if ((bins.length === 34) && (target === 'onset-wk')) {
      // We skip the none bin
      return { bins: bins.slice(0, -1).map(b => b[2]) }
    } else {
      throw new Error('Unknown bin length in parseBinData')
    }
  }

  return {
    series: ['1-ahead', '2-ahead', '3-ahead', '4-ahead'].map(getTargetData),
    peakValue: getTargetData('peak'),
    peakTime: getTargetData('peak-wk'),
    onsetTime: getTargetData('onset-wk')
  }
}

/**
 * Return formatted data from the csv for a region
 * Each value (point, bin, score) is in the following structure
 * - series: [{}, {}, {}, {}]
 * - peakValue: {}
 * - peakTime: {}
 * - onsetTime: {}
 */
async function parseCsv (csv, regionId) {
  return {
    pointData: parsePointData(csv, regionId),
    binData: parseBinData(csv, regionId),
    scoreData: (await getCsvScore(csv))[regionId]
  }
}

/**
 * Return formatted data for the complete model with given region
 */
async function parseModelDir (modelPath, regionId) {
  let modelId = path.basename(modelPath)
  let availableEpiweeks = utils.getWeekFiles(modelPath)
  let modelMeta = utils.getModelMeta(modelPath)

  let pointPredictions = []
  let binPredictions = []
  let scores = []

  for (let epiweek of fct.utils.epiweek.seasonEpiweeks(SEASON_ID)) {
    if (availableEpiweeks.indexOf(epiweek) === -1) {
      // Prediction not available for this week, return null
      pointPredictions.push(null)
      binPredictions.push(null)
    } else {
      let { pointData, binData, scoreData } = await parseCsv(getCsv(modelPath, epiweek), regionId)
      pointPredictions.push(pointData)
      binPredictions.push(binData)
      scores.push(scoreData)
    }
  }

  return {
    pointData: {
      id: modelId,
      meta: modelMeta,
      predictions: pointPredictions
    },
    distsData: {
      id: modelId,
      predictions: binPredictions
    },
    scoresData: {
      id: modelId,
      scores: utils.aggregateScores(scores)
    }
  }
}

/**
 * Generate data files for the provided seasonData and using the
 * submission files in dataDir
 */
async function generateFiles (seasonData) {
  // Output to be written in file season-{season}.json
  let seasonOut = {
    seasonId: SEASON, // NOTE: This id is full xxxx-yyyy type id
    regions: []
  }

  // Output to be written in file scores-{season}.json
  let scoresOut = {
    seasonId: SEASON,
    regions: []
  }

  // Output to be written in file distributions/season-{season}-{region}.json
  let distsOut = []

  let regionPointData, regionDistsData, regionScoresData

  for (let regionId of fct.meta.regionIds) {
    regionPointData = []
    regionDistsData = []
    regionScoresData = []

    for (let model of MODELS_DIR) {
      let modelPath = path.join(DATA_DIR, SEASON, model)
      let { pointData, distsData, scoresData } = await parseModelDir(modelPath, regionId)
      regionPointData.push(pointData)
      regionDistsData.push(distsData)
      regionScoresData.push(scoresData)
    }

    seasonOut.regions.push({
      id: regionId,
      actual: parseRegionActual(seasonData, regionId),
      models: regionPointData,
      baseline: await fct.truth.getBaseline(regionId, SEASON_ID)
    })

    distsOut.push({
      seasonId: SEASON,
      regionId: regionId,
      models: regionDistsData
    })

    scoresOut.regions.push({
      id: regionId,
      models: regionScoresData
    })
  }

  await Promise.all([
    writeSeasonFile(seasonOut),
    writeScoresFile(scoresOut),
    ...distsOut.map(d => writeDistsFile(d, d.regionId))
  ])
  console.log(` Data files for season ${SEASON} written.`)
}

// Entry point
fct.truth.getSeasonDataAllLags(SEASON_ID)
  .then(sd => generateFiles(sd))
  .then(() => { console.log('All done') })
  .catch(e => {
    console.log(e)
    process.exit(1)
  })
<commit_msg>Handle 2014-2015 season in backend script<commit_after>/**
 * Script to parse and write data for a single season
 * This is supposed to be called in child processes via the main parse script
 */

const fs = require('fs-extra')
const path = require('path')
const utils = require('./utils')
const fct = require('flusight-csv-tools')
const moize = require('moize')

// Directory with CSVs
const DATA_DIR = './data'

// Directory for output JSONs
const OUT_DIR = './src/assets/data'

// Look for seasons in the data directory
const SEASONS = utils.getSubDirectories(DATA_DIR)

// Take input season from command line argument
const SEASON_ID = parseInt(process.argv[2])
const SEASON = `${SEASON_ID}-${SEASON_ID + 1}`
const MODELS_DIR = utils.getSubDirectories(path.join(DATA_DIR, SEASON))

/**
 * Return season name for writing files
 */
function getSeasonName () {
  if (SEASONS.indexOf(SEASON) === SEASONS.length - 1) {
    return 'latest'
  } else {
    return SEASON
  }
}

async function writeSeasonFile (data) {
  await utils.writeJSON(path.join(OUT_DIR, `season-${getSeasonName()}.json`), data)
}

async function writeScoresFile (data) {
  await utils.writeJSON(path.join(OUT_DIR, `scores-${getSeasonName()}.json`), data)
}

async function writeDistsFile (data, regionId) {
  let distsDir = path.join(OUT_DIR, 'distributions')
  await fs.ensureDir(distsDir)

  let outputFile
  if (regionId === 'nat') {
    // We only use latest identifier in the latest AND nat data
    outputFile = path.join(distsDir, `season-${getSeasonName()}-nat.json`)
  } else {
    outputFile = path.join(distsDir, `season-${SEASON}-${regionId}.json`)
  }

  await utils.writeJSON(outputFile, data)
}

/**
 * Return region data with nulls filled in for missing epiweeks
 */
function parseRegionActual (seasonData, regionId) {
  let regionSubset = seasonData[regionId]

  let epiweeks = fct.utils.epiweek.seasonEpiweeks(SEASON_ID)
  return epiweeks.map(ew => {
    let ewData = regionSubset.find(({ epiweek }) => epiweek === ew)
    // Rename keys to match the expectations of flusight
    // and extend by filling in missing epiweeks
    if (ewData) {
      return {
        week: ewData.epiweek,
        actual: ewData.wili,
        lagData: ewData.lagData.map(({ lag, wili }) => { return { lag, value: wili } })
      }
    } else {
      return {
        week: ew,
        actual: null,
        lagData: []
      }
    }
  })
}

/**
 * Return fct csv object for given specifications using the cache
 */
const getCsv = moize(function (modelPath, epiweek) {
  let modelId = path.basename(modelPath)
  return new fct.Csv(path.join(modelPath, epiweek + '.csv'), epiweek, modelId)
})

/**
 * Return csv score using cache
 */
const getCsvScore = moize(async function (csv) {
  return await fct.score.score(csv)
}, { isPromise: true })

/**
 * Return formatted point data for the region using the cvs object
 */
function parsePointData (csv, regionId) {
  let seasonEpiweeks = fct.utils.epiweek.seasonEpiweeks(SEASON_ID)

  function getTargetData (target) {
    let cis = [90, 50]
    let point = csv.getPoint(target, regionId)
    let ranges = cis.map(c => csv.getConfidenceRange(target, regionId, c))
    let low = ranges.map(r => r[0])
    let high = ranges.map(r => r[1])

    if (['peak-wk', 'onset-wk'].indexOf(target) > -1) {
      // Return indices for time based targets
      point = seasonEpiweeks.indexOf(point)
      high = high.map(d => seasonEpiweeks.indexOf(d))
      low = low.map(d => seasonEpiweeks.indexOf(d))
    }

    return { point, low, high }
  }

  return {
    series: ['1-ahead', '2-ahead', '3-ahead', '4-ahead'].map(getTargetData),
    peakValue: getTargetData('peak'),
    peakTime: getTargetData('peak-wk'),
    onsetTime: getTargetData('onset-wk')
  }
}

/**
 * Return bin data for the given region using the csv object
 */
function parseBinData (csv, regionId) {
  function getTargetData (target) {
    let bins = csv.getBins(target, regionId)

    // There are different types of bins that we need to consider
    if (bins.length === 131) {
      // These are regular, new style, wili bins with last one being
      // [13, 100] which we skip
      return { bins: fct.utils.bins.sliceSumBins(bins.slice(0, -1), 5).map(b => b[2]) }
    } else if (bins.length === 27) {
      // These are old style wili bins with last one being [13, 100] which
      // we skip
      return { bins: bins.slice(0, -1).map(b => b[2]) }
    } else if (target === 'peak-wk') {
      return { bins: bins.map(b => b[2]) }
    } else if (target === 'onset-wk') {
      // We skip the none bin
      return { bins: bins.slice(0, -1).map(b => b[2]) }
    } else {
      throw new Error('Unknown bin size ${bins.length} in parseBinData for ${target}, ${region}')
    }
  }

  return {
    series: ['1-ahead', '2-ahead', '3-ahead', '4-ahead'].map(getTargetData),
    peakValue: getTargetData('peak'),
    peakTime: getTargetData('peak-wk'),
    onsetTime: getTargetData('onset-wk')
  }
}

/**
 * Return formatted data from the csv for a region
 * Each value (point, bin, score) is in the following structure
 * - series: [{}, {}, {}, {}]
 * - peakValue: {}
 * - peakTime: {}
 * - onsetTime: {}
 */
async function parseCsv (csv, regionId) {
  return {
    pointData: parsePointData(csv, regionId),
    binData: parseBinData(csv, regionId),
    scoreData: (await getCsvScore(csv))[regionId]
  }
}

/**
 * Return formatted data for the complete model with given region
 */
async function parseModelDir (modelPath, regionId) {
  let modelId = path.basename(modelPath)
  let availableEpiweeks = utils.getWeekFiles(modelPath)
  let modelMeta = utils.getModelMeta(modelPath)

  let pointPredictions = []
  let binPredictions = []
  let scores = []

  for (let epiweek of fct.utils.epiweek.seasonEpiweeks(SEASON_ID)) {
    if (availableEpiweeks.indexOf(epiweek) === -1) {
      // Prediction not available for this week, return null
      pointPredictions.push(null)
      binPredictions.push(null)
    } else {
      let { pointData, binData, scoreData } = await parseCsv(getCsv(modelPath, epiweek), regionId)
      pointPredictions.push(pointData)
      binPredictions.push(binData)
      scores.push(scoreData)
    }
  }

  return {
    pointData: {
      id: modelId,
      meta: modelMeta,
      predictions: pointPredictions
    },
    distsData: {
      id: modelId,
      predictions: binPredictions
    },
    scoresData: {
      id: modelId,
      scores: utils.aggregateScores(scores)
    }
  }
}

/**
 * Generate data files for the provided seasonData and using the
 * submission files in dataDir
 */
async function generateFiles (seasonData) {
  // Output to be written in file season-{season}.json
  let seasonOut = {
    seasonId: SEASON, // NOTE: This id is full xxxx-yyyy type id
    regions: []
  }

  // Output to be written in file scores-{season}.json
  let scoresOut = {
    seasonId: SEASON,
    regions: []
  }

  // Output to be written in file distributions/season-{season}-{region}.json
  let distsOut = []

  let regionPointData, regionDistsData, regionScoresData

  for (let regionId of fct.meta.regionIds) {
    regionPointData = []
    regionDistsData = []
    regionScoresData = []

    for (let model of MODELS_DIR) {
      let modelPath = path.join(DATA_DIR, SEASON, model)
      let { pointData, distsData, scoresData } = await parseModelDir(modelPath, regionId)
      regionPointData.push(pointData)
      regionDistsData.push(distsData)
      regionScoresData.push(scoresData)
    }

    seasonOut.regions.push({
      id: regionId,
      actual: parseRegionActual(seasonData, regionId),
      models: regionPointData,
      baseline: await fct.truth.getBaseline(regionId, SEASON_ID)
    })

    distsOut.push({
      seasonId: SEASON,
      regionId: regionId,
      models: regionDistsData
    })

    scoresOut.regions.push({
      id: regionId,
      models: regionScoresData
    })
  }

  await Promise.all([
    writeSeasonFile(seasonOut),
    writeScoresFile(scoresOut),
    ...distsOut.map(d => writeDistsFile(d, d.regionId))
  ])
  console.log(` Data files for season ${SEASON} written.`)
}

// Entry point
fct.truth.getSeasonDataAllLags(SEASON_ID)
  .then(sd => generateFiles(sd))
  .then(() => { console.log('All done') })
  .catch(e => {
    console.log(e)
    process.exit(1)
  })
<|endoftext|>