<commit_before>/////////
// APP //
/////////

var App = function(){
	this.users = null;
	this.selectedUserIdx = null;
	this.selectedUser = null;
	this.projects = null;
	this.selectedProjectIdx = null;
	this.selectedProject = null;
	this.selectedFile = null;
	this.butler = new Butler();

	this.butler_version();
	this.butler_login();

	// TODO: notify user if copy of butler is out of date
	// and/or tell butler to update itself
};

App.prototype.login = function(key, user, rememberMe){
	$("#btnLogin").prop("disabled",true);

	$.ajax({
		url: "https://itch.io/api/1/"+key+"/my-games"
	}).done(function(data){

		// error handling

		if(data.errors){
			// error when submitting API key

			alert(data.errors.toString());
			$("#btnLogin").prop("disabled",false);

			return;
		}

		if(data.games.length <= 0){
			// the user has nothing associated with their key

			alert("Error: couldn't find any projects associated with this key. gui-butler can only push builds to existing projects; you need to use itch.io to actually create them.");
			$("#btnLogin").prop("disabled",false);

			return;
		}

		// success!

		if(rememberMe){
			// save login details to local storage
			// TODO: also save the index of currently selected user/project; it gets kind of annoying to have to re-select if you're opening/closing frequently
			window.localStorage.setItem("key", key);
			window.localStorage.setItem("rememberMe", "1");
		}else{
			// clear login details from local storage
			window.localStorage.setItem("key", "");
			window.localStorage.setItem("rememberMe", "0");
		}

		// find all usernames associated with key by stripping URLs
		var users={};
		for(var i = 0; i < data.games.length; ++i){
			var username = data.games[i].url.split("https://")[1].split(".itch.io/")[0];
			if(!users[username]){
				users[username] = {
					username: username,
					projects:[]
				}
			}
			users[username].projects.push(data.games[i]);
		}

		// add users to internal list + dropdown
		this.users = [];
		var s="";
		var i = 0;
		for(var u in users){
			this.users.push(users[u]);
			s += "<option value=\"" + (i++) + "\">" + users[u].username + "</option>";
		}
		$("#userSelect").html(s);

		// update section display
		$("section#login").slideUp();
		$("section#logout").slideDown();

		// auto-select the first user in the dropdown
		// (provides a bit of feedback + removes the need for a null entry)
		$("#userSelect").val("0").change();

		$("section#selectUser").slideDown();
	}.bind(this)).fail(function(){
		$("#btnLogin").prop("disabled",false);

		// couldn't get a response
		alert("Error: itch.io failed to respond. Check your internet connection etc.");
	}.bind(this));
};

App.prototype.logout = function(){
	$("section#login").slideDown();

	$("#btnLogin").prop("disabled",false);

	$("#selectedFile").html("no .zip selected…");

	$("section#logout").slideUp();
	$("section#selectProject").slideUp();
	$("section#selectBuild").slideUp();
	$("section#selectUser").slideUp();
	$("section#pushBuild").slideUp();
};
App.prototype.selectUser = function(idx){
	this.selectedUserIdx = parseInt(idx,10);
	this.selectedUser = this.users[this.selectedUserIdx];

	// update link
	$("#userPreview").attr("href", "https://"+this.selectedUser.username+".itch.io");

	// add the users games to the dropdown
	this.projects = this.selectedUser.projects;
	s="";
	for(var i = 0; i < this.projects.length; ++i){
		var g = this.projects[i];
		s += "<option value=\"" + i + "\">" + g.title + "</option>";
	}
	$("#projectSelect").html(s);

	// auto-select the first game in the dropdown
	// (provides a bit of feedback + removes the need for a null entry)
	$("#projectSelect").val("0").change();

	$("#selectProject").slideDown();
};
App.prototype.selectProject = function(idx){
	this.selectedProjectIdx = parseInt(idx,10);
	this.selectedProject = this.projects[this.selectedProjectIdx];

	// get rid of the old cover, then set then new one
	$("#projectCover").attr("src", "");
	$("#projectCover").attr("src", this.selectedProject.cover_url);

	// update text
	$("#projectTitle").text(this.selectedProject.title);
	$("#projectText").text(this.selectedProject.short_text);

	// update link
	$("#projectUrl").attr("href", this.selectedProject.url);

	$("#selectBuild").slideDown();
};
App.prototype.selectFile = function(){
	// prompt user to select a .zip archive
	remote.dialog.showOpenDialog(
	{
		title: "Select build archive",
		filters: [{name: "Build Archive", extensions:["zip"]}],
		properties: ["openFile"]
	},
	function(filenames){
		if(filenames && filenames.length == 1){
			this.selectedFile = filenames[0];
			$("#selectedFile").text(this.selectedFile);
			$("section#pushBuild").slideDown();

			// if there aren't already any channels set
			// search the filename for "win","osx","linux" and check their boxes
			// also search for "web","32","64" and add them to other
			if(this.getChannels().length == 0){
				// only use the file and not the whole path to reduce false positives
				var fileEnd = this.selectedFile.toLowerCase().split(/[\/\\]+/).pop();
				if(fileEnd.indexOf("win") !== -1){
					$("#channelWin").trigger("click");
				}
				if(fileEnd.indexOf("osx") !== -1){
					$("#channelOsx").trigger("click");
				}
				if(fileEnd.indexOf("linux") !== -1){
					$("#channelLinux").trigger("click");
				}
				if(fileEnd.indexOf("web") !== -1){
					$("#channelOther").val("web-");
				}
				if(fileEnd.indexOf("32") !== -1){
					$("#channelOther").val($("#channelOther").val()+"32-");
				}
				if(fileEnd.indexOf("64") !== -1){
					$("#channelOther").val($("#channelOther").val()+"64-");
				}
				if($("#channelOther").val().substr(-1) == "-"){
					$("#channelOther").val($("#channelOther").val().slice(0,-1));
					$("#channelOther").trigger("change");
				}
			}

		}else if($("#selectedFile").text().length <= 0){
			// canceled selection
			$("section#pushBuild").slideUp();
		}
	}.bind(this));
};
App.prototype.getChannels = function(){
	var channels = "";

	// add preset channels
	if($("#channelWin")[0].checked){
		channels += "win-";
	}if($("#channelOsx")[0].checked){
		channels += "osx-";
	}if($("#channelLinux")[0].checked){
		channels += "linux-";
	}

	// add custom channels
	channels += $("#channelOther").val().toLowerCase();

	// remove the last dash
	if(channels.substr(-1) == "-"){
		channels = channels.slice(0,-1);
	}

	return channels;
};
App.prototype.getProjectShortUrl = function(){
	// the URL for butler is the same as the one for the project's the itch page
	return this.selectedProject.url.split(".itch.io/")[1];
};
App.prototype.getProjectUrl = function(){
	// butler URLs are user/project-url:channels-in-a-dash-separated-list
	return this.selectedUser.username+"/"+this.getProjectShortUrl()+":"+this.getChannels();
};

App.prototype.validate = function(){
	var s=this.getProjectUrl();
	$("#targetPreview").val(s);

	if(this.getChannels() <= 0){
		$("#btnPush").prop("disabled",true);
		$("#btnCheckStatus").prop("disabled",true);
	}else{
		$("#btnPush").prop("disabled",false);
		$("#btnCheckStatus").prop("disabled",false);
	}
}

// calling butler
App.prototype.butler_push = function(file, url){
	this.butler.call(["push", file, url], true, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_status = function(url){
	this.butler.call(["status", url], false, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_login = function(json){
	this.butler.call(["login"], true, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_logout = function(json){
	// TODO: should probably replace the --assume-yes with something to handle the prompt message and let user decide
	// no message types associated with logout, so we call it synchronously
	this.butler.call(["logout","--assume-yes"], false, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_version = function(){
	this.butler.call(["-V"], false, null, function(stderr){
		$("#version").html(stderr.toString());
	});
};

// receiving messages from butler
App.prototype.onMessage = function(data){
	//console.log(data.toString());
	var messages = data.toString().split("}\n");

	for(var i = 0; i < messages.length; ++i){
		var json = messages[i];
		if(json == ""){
			continue;
		}
		try{
			json = JSON.parse(json+"}");
		}catch(e){
			json = {
				type:"log",
				message:json,
				level:"info"
			};
			console.error("JSON parse failed; assuming response was an info log\n",e);
		}
		this["json_"+json.type](json);
	}
};
App.prototype.json_log = function(json){
	// general purpose logging
	// TODO: provide a way to show debug logs in addition to info
	if(json.level == "info"){
		$("#output").append(json.message+"\n");
		$("#output").scrollTop($("#output")[0].scrollHeight);
	}
};
App.prototype.json_progress = function(json){
	// update on upload progress
	// TODO: format ETA
	$("#progressBar div").width(json.percentage+"%");
	$("#eta").val(json.eta);

	if(json.progress == 1){
		// complete
		$("#progressBar").removeClass("active");
	}
};
App.prototype.json_login = function(json){
	// attempting to login
	shell.openExternal(json.uri);
};
App.prototype.json_result = function(json){
	// finished logging in
	if(json.value.status == "success"){
		$("section#login").slideDown();
	}else{
		// TODO: idk; haven't actually tested the fail case yet
		alert(json);
	}
};
App.prototype.json_error = function(json){
	alert(json.message);
};<commit_msg>user and project dropdown selection saved to localStorage<commit_after>/////////
// APP //
/////////

var App = function(){
	this.users = null;
	this.selectedUserIdx = null;
	this.selectedUser = null;
	this.projects = null;
	this.selectedProjectIdx = null;
	this.selectedProject = null;
	this.selectedFile = null;
	this.butler = new Butler();

	this.butler_version();
	this.butler_login();

	// TODO: notify user if copy of butler is out of date
	// and/or tell butler to update itself
};

App.prototype.login = function(key, user, rememberMe){
	$("#btnLogin").prop("disabled",true);

	$.ajax({
		url: "https://itch.io/api/1/"+key+"/my-games"
	}).done(function(data){

		// error handling

		if(data.errors){
			// error when submitting API key

			alert(data.errors.toString());
			$("#btnLogin").prop("disabled",false);

			return;
		}

		if(data.games.length <= 0){
			// the user has nothing associated with their key

			alert("Error: couldn't find any projects associated with this key. gui-butler can only push builds to existing projects; you need to use itch.io to actually create them.");
			$("#btnLogin").prop("disabled",false);

			return;
		}

		// success!

		if(rememberMe){
			// save login details to local storage
			window.localStorage.setItem("key", key);
			window.localStorage.setItem("rememberMe", "1");
		}else{
			// clear login details from local storage
			window.localStorage.setItem("key", "");
			window.localStorage.setItem("rememberMe", "0");
			window.localStorage.setItem("userIdx", "0");
			window.localStorage.setItem("projectIdx", "0");
		}

		// find all usernames associated with key by stripping URLs
		var users={};
		for(var i = 0; i < data.games.length; ++i){
			var username = data.games[i].url.split("https://")[1].split(".itch.io/")[0];
			if(!users[username]){
				users[username] = {
					username: username,
					projects:[]
				}
			}
			users[username].projects.push(data.games[i]);
		}

		// add users to internal list + dropdown
		this.users = [];
		var s="";
		var i = 0;
		for(var u in users){
			this.users.push(users[u]);
			s += "<option value=\"" + (i++) + "\">" + users[u].username + "</option>";
		}
		$("#userSelect").html(s);

		// update section display
		$("section#login").slideUp();
		$("section#logout").slideDown();

		// auto-select the first user in the dropdown
		// (provides a bit of feedback + removes the need for a null entry)
		$("#userSelect").val(window.localStorage.getItem("userIdx")).change();

		$("section#selectUser").slideDown();
	}.bind(this)).fail(function(){
		$("#btnLogin").prop("disabled",false);

		// couldn't get a response
		alert("Error: itch.io failed to respond. Check your internet connection etc.");
	}.bind(this));
};

App.prototype.logout = function(){
	$("section#login").slideDown();

	$("#btnLogin").prop("disabled",false);

	$("#selectedFile").html("no .zip selected…");

	$("section#logout").slideUp();
	$("section#selectProject").slideUp();
	$("section#selectBuild").slideUp();
	$("section#selectUser").slideUp();
	$("section#pushBuild").slideUp();
};
App.prototype.selectUser = function(idx){
	this.selectedUserIdx = parseInt(idx,10);
	this.selectedUser = this.users[this.selectedUserIdx];

	// update link
	$("#userPreview").attr("href", "https://"+this.selectedUser.username+".itch.io");

	// add the users games to the dropdown
	this.projects = this.selectedUser.projects;
	s="";
	for(var i = 0; i < this.projects.length; ++i){
		var g = this.projects[i];
		s += "<option value=\"" + i + "\">" + g.title + "</option>";
	}
	$("#projectSelect").html(s);

	// save selection to localStorage
	if(window.localStorage.getItem("rememberMe") == 1){
		window.localStorage.setItem("userIdx", idx);
	}

	// auto-select the first game in the dropdown
	// (provides a bit of feedback + removes the need for a null entry)
	$("#projectSelect").val(window.localStorage.getItem("projectIdx")).change();

	$("#selectProject").slideDown();
};
App.prototype.selectProject = function(idx){
	this.selectedProjectIdx = parseInt(idx,10);
	this.selectedProject = this.projects[this.selectedProjectIdx];

	// get rid of the old cover, then set then new one
	$("#projectCover").attr("src", "");
	$("#projectCover").attr("src", this.selectedProject.cover_url);

	// update text
	$("#projectTitle").text(this.selectedProject.title);
	$("#projectText").text(this.selectedProject.short_text);

	// update link
	$("#projectUrl").attr("href", this.selectedProject.url);

	// save selection to localStorage
	if(window.localStorage.getItem("rememberMe") == 1){
		window.localStorage.setItem("projectIdx", idx);
	}

	$("#selectBuild").slideDown();
};
App.prototype.selectFile = function(){
	// prompt user to select a .zip archive
	remote.dialog.showOpenDialog(
	{
		title: "Select build archive",
		filters: [{name: "Build Archive", extensions:["zip"]}],
		properties: ["openFile"]
	},
	function(filenames){
		if(filenames && filenames.length == 1){
			this.selectedFile = filenames[0];
			$("#selectedFile").text(this.selectedFile);
			$("section#pushBuild").slideDown();

			// if there aren't already any channels set
			// search the filename for "win","osx","linux" and check their boxes
			// also search for "web","32","64" and add them to other
			if(this.getChannels().length == 0){
				// only use the file and not the whole path to reduce false positives
				var fileEnd = this.selectedFile.toLowerCase().split(/[\/\\]+/).pop();
				if(fileEnd.indexOf("win") !== -1){
					$("#channelWin").trigger("click");
				}
				if(fileEnd.indexOf("osx") !== -1){
					$("#channelOsx").trigger("click");
				}
				if(fileEnd.indexOf("linux") !== -1){
					$("#channelLinux").trigger("click");
				}
				if(fileEnd.indexOf("web") !== -1){
					$("#channelOther").val("web-");
				}
				if(fileEnd.indexOf("32") !== -1){
					$("#channelOther").val($("#channelOther").val()+"32-");
				}
				if(fileEnd.indexOf("64") !== -1){
					$("#channelOther").val($("#channelOther").val()+"64-");
				}
				if($("#channelOther").val().substr(-1) == "-"){
					$("#channelOther").val($("#channelOther").val().slice(0,-1));
					$("#channelOther").trigger("change");
				}
			}

		}else if($("#selectedFile").text().length <= 0){
			// canceled selection
			$("section#pushBuild").slideUp();
		}
	}.bind(this));
};
App.prototype.getChannels = function(){
	var channels = "";

	// add preset channels
	if($("#channelWin")[0].checked){
		channels += "win-";
	}if($("#channelOsx")[0].checked){
		channels += "osx-";
	}if($("#channelLinux")[0].checked){
		channels += "linux-";
	}

	// add custom channels
	channels += $("#channelOther").val().toLowerCase();

	// remove the last dash
	if(channels.substr(-1) == "-"){
		channels = channels.slice(0,-1);
	}

	return channels;
};
App.prototype.getProjectShortUrl = function(){
	// the URL for butler is the same as the one for the project's the itch page
	return this.selectedProject.url.split(".itch.io/")[1];
};
App.prototype.getProjectUrl = function(){
	// butler URLs are user/project-url:channels-in-a-dash-separated-list
	return this.selectedUser.username+"/"+this.getProjectShortUrl()+":"+this.getChannels();
};

App.prototype.validate = function(){
	var s=this.getProjectUrl();
	$("#targetPreview").val(s);

	if(this.getChannels() <= 0){
		$("#btnPush").prop("disabled",true);
		$("#btnCheckStatus").prop("disabled",true);
	}else{
		$("#btnPush").prop("disabled",false);
		$("#btnCheckStatus").prop("disabled",false);
	}
}

// calling butler
App.prototype.butler_push = function(file, url){
	this.butler.call(["push", file, url], true, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_status = function(url){
	this.butler.call(["status", url], false, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_login = function(json){
	this.butler.call(["login"], true, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_logout = function(json){
	// TODO: should probably replace the --assume-yes with something to handle the prompt message and let user decide
	// no message types associated with logout, so we call it synchronously
	this.butler.call(["logout","--assume-yes"], false, this.onMessage.bind(this), this.onMessage.bind(this));
};
App.prototype.butler_version = function(){
	this.butler.call(["-V"], false, null, function(stderr){
		$("#version").html(stderr.toString());
	});
};

// receiving messages from butler
App.prototype.onMessage = function(data){
	//console.log(data.toString());
	var messages = data.toString().split("}\n");

	for(var i = 0; i < messages.length; ++i){
		var json = messages[i];
		if(json == ""){
			continue;
		}
		try{
			json = JSON.parse(json+"}");
		}catch(e){
			json = {
				type:"log",
				message:json,
				level:"info"
			};
			console.error("JSON parse failed; assuming response was an info log\n",e);
		}
		this["json_"+json.type](json);
	}
};
App.prototype.json_log = function(json){
	// general purpose logging
	// TODO: provide a way to show debug logs in addition to info
	if(json.level == "info"){
		$("#output").append(json.message+"\n");
		$("#output").scrollTop($("#output")[0].scrollHeight);
	}
};
App.prototype.json_progress = function(json){
	// update on upload progress
	// TODO: format ETA
	$("#progressBar div").width(json.percentage+"%");
	$("#eta").val(json.eta);

	if(json.progress == 1){
		// complete
		$("#progressBar").removeClass("active");
	}
};
App.prototype.json_login = function(json){
	// attempting to login
	shell.openExternal(json.uri);
};
App.prototype.json_result = function(json){
	// finished logging in
	if(json.value.status == "success"){
		$("section#login").slideDown();
	}else{
		// TODO: idk; haven't actually tested the fail case yet
		alert(json);
	}
};
App.prototype.json_error = function(json){
	alert(json.message);
};<|endoftext|>


<commit_before>import * as fs from 'fs';

import * as cgd from '../io/cgd';
import * as periodic from '../pgraphs/periodic';
import * as symmetries from '../pgraphs/symmetries';

const checkGraph = (graph, writeInfo) => {
  if (!periodic.isLocallyStable(graph)) {
    writeInfo('Error - next-nearest neighbor collisions');
    return false;
  }

  if (symmetries.isLadder(graph)) {
    writeInfo('Error - non-crystallographic (ladder)');
    return false;
  }

  if (periodic.hasSecondOrderCollisions(graph)) {
    writeInfo('Error - second-order collisions');
    return false;
  }

  return true;
};

const makeCsLookup = data => {
  const result = {};

  for (const { symbol, vertices } of data) {
    if (symbol.match(/-[bcz][*0-9]*$/))
      continue;

    const cs = vertices.map(v => v.coordinationSequence);
    cs.sort(cmpLex);

    if (result[cs] == null)
      result[cs] = { rcsr: [], cgd: [] };
    result[cs].rcsr.push(symbol);
  }

  return result;
};

const cmp = (x, y) => (x > y) - (x < y);
const cmpLex = (xs, ys) => xs.map((x, i) => cmp(x, ys[i])).find(x => x) || 0;

const [inputPath, data2dPath, data3dPath] = process.argv.slice(2, 5);

const data2d = JSON.parse(fs.readFileSync(data2dPath, { encoding: 'utf8' }));
const data3d = JSON.parse(fs.readFileSync(data3dPath, { encoding: 'utf8' }));

const input = fs.readFileSync(inputPath, { encoding: 'utf8' });

const lookup2d = makeCsLookup(data2d);
const lookup3d = makeCsLookup(data3d);

for (const { graph, name } of cgd.structures(input)) {
  const ok = checkGraph(graph, msg => console.log(`${name}: ${msg}`));
  if (!ok)
    continue;

  const G = symmetries.minimalImageWithOrbits(graph).graph;
  const syms = symmetries.symmetries(G).symmetries;
  const nodeOrbits = symmetries.nodeOrbits(G, syms);

  const cs = nodeOrbits.map(
    ([v]) => periodic.coordinationSeq(G, v, 10).slice(1)
  );
  cs.sort(cmpLex);

  const lookup = G.dim == 2 ? lookup2d : lookup3d;
  if (lookup[cs] == null)
    lookup[cs] = { rcsr: [], cgd: [] };

  const entry = lookup[cs];

  if (entry.cgd.indexOf(name) >= 0)
    console.log(`${name}: duplicate`);
  else if (entry.rcsr.indexOf(name) >= 0)
    console.log(`${name}: ok!`);
  else {
    if (entry.rcsr.length == 0)
      console.log(`${name}: does not match anything`);
    else
      console.log(`${name}: mismatch (found ${entry.rcsr})`);

    for (const seq of cs)
      console.log('    ' + seq);
  }

  entry.cgd.push(name);
}

const missing2d = Object.keys(lookup2d)
      .map(k => lookup2d[k])
      .filter(e => e.cgd.length == 0)
      .map(e => e.rcsr);

if (missing2d.length)
  console.log(`RCSR layers not matched in file: ${missing2d}`);

const missing3d = Object.keys(lookup3d)
      .map(k => lookup3d[k])
      .filter(e => e.cgd.length == 0)
      .map(e => e.rcsr);

if (missing3d.length)
  console.log(`RCSR nets not matched in file: ${missing3d}`);
<commit_msg>checkSystreFile: add CS from RCSR to mismatched outputs<commit_after>import * as fs from 'fs';

import * as cgd from '../io/cgd';
import * as periodic from '../pgraphs/periodic';
import * as symmetries from '../pgraphs/symmetries';

const checkGraph = (graph, writeInfo) => {
  if (!periodic.isLocallyStable(graph)) {
    writeInfo('Error - next-nearest neighbor collisions');
    return false;
  }

  if (symmetries.isLadder(graph)) {
    writeInfo('Error - non-crystallographic (ladder)');
    return false;
  }

  if (periodic.hasSecondOrderCollisions(graph)) {
    writeInfo('Error - second-order collisions');
    return false;
  }

  return true;
};

const makeCsLookup = data => {
  const result = {};

  for (const { symbol, vertices } of data) {
    if (symbol.match(/-[bcz][*0-9]*$/))
      continue;

    const cs = vertices.map(v => v.coordinationSequence);
    cs.sort(cmpLex);

    if (result[cs] == null)
      result[cs] = { rcsr: [], cgd: [] };
    result[cs].rcsr.push(symbol);
    result[symbol] = cs;
  }

  return result;
};

const cmp = (x, y) => (x > y) - (x < y);
const cmpLex = (xs, ys) => xs.map((x, i) => cmp(x, ys[i])).find(x => x) || 0;

const [inputPath, data2dPath, data3dPath] = process.argv.slice(2, 5);

const data2d = JSON.parse(fs.readFileSync(data2dPath, { encoding: 'utf8' }));
const data3d = JSON.parse(fs.readFileSync(data3dPath, { encoding: 'utf8' }));

const input = fs.readFileSync(inputPath, { encoding: 'utf8' });

const lookup2d = makeCsLookup(data2d);
const lookup3d = makeCsLookup(data3d);

for (const { graph, name } of cgd.structures(input)) {
  const ok = checkGraph(graph, msg => console.log(`${name}: ${msg}`));
  if (!ok)
    continue;

  const G = symmetries.minimalImageWithOrbits(graph).graph;
  const syms = symmetries.symmetries(G).symmetries;
  const nodeOrbits = symmetries.nodeOrbits(G, syms);

  const cs = nodeOrbits.map(
    ([v]) => periodic.coordinationSeq(G, v, 10).slice(1)
  );
  cs.sort(cmpLex);

  const lookup = G.dim == 2 ? lookup2d : lookup3d;
  if (lookup[cs] == null)
    lookup[cs] = { rcsr: [], cgd: [] };

  const entry = lookup[cs];

  if (entry.cgd.indexOf(name) >= 0)
    console.log(`${name}: duplicate`);
  else if (entry.rcsr.indexOf(name) >= 0)
    console.log(`${name}: ok!`);
  else {
    if (entry.rcsr.length == 0)
      console.log(`${name}: does not match anything`);
    else
      console.log(`${name}: mismatch (found ${entry.rcsr})`);

    for (const seq of cs)
      console.log('    ' + seq);

    if (lookup[name]) {
      console.log(`  RCSR entry for '${name}' has:`);
      for (const seq of lookup[name])
        console.log('    ' + seq);
    }
  }

  entry.cgd.push(name);
}

const missing2d = Object.keys(lookup2d)
      .map(k => lookup2d[k])
      .filter(e => e.cgd.length == 0)
      .map(e => e.rcsr);

if (missing2d.length)
  console.log(`RCSR layers not matched in file: ${missing2d}`);

const missing3d = Object.keys(lookup3d)
      .map(k => lookup3d[k])
      .filter(e => e.cgd.length == 0)
      .map(e => e.rcsr);

if (missing3d.length)
  console.log(`RCSR nets not matched in file: ${missing3d}`);
<|endoftext|>


<commit_before>import React, {
    Component
} from 'react';
//import logo from './logo.svg';
import './App.css';
import { PageRouter } from './components/PageRouter';

class App extends Component {
    render() {
        return(

                <PageRouter />

        );
    }
}
export default App;
<commit_msg>Add user to localStorage<commit_after>import React, {
    Component
} from 'react';
//import logo from './logo.svg';
import './App.css';
import { PageRouter } from './components/PageRouter';
import * as firebase from 'firebase';

class App extends Component {
    state = {
        currentUser: null
    }

    componentWillMount() {
        firebase.auth().onAuthStateChanged(  user => {
            if (user === null) {
                localStorage.removeItem('user', null);
            } else {
                localStorage.setItem('user', user.uid);
            }

            this.setState({
                currentUser: user
            });

        } );
    }

    render() {
        return(

                <PageRouter />

        );
    }
}
export default App;
<|endoftext|>


<commit_before>
var Match = require ('../match');

/**
 * Charset recognizer for UTF-8
 */
module.exports = function() {
    this.name = function() {
        return 'UTF-8';
    };
    this.match = function(det) {

        var hasBOM = false,
            numValid = 0,
            numInvalid = 0,
            input = det.fRawInput,
            i,
            trailBytes = 0,
            confidence;

        if (det.fRawLength >= 3 &&
            (input[0] & 0xFF) == 0xef && (input[1] & 0xFF) == 0xbb && (input[2] & 0xFF) == 0xbf) {
            hasBOM = true;
        }

        // Scan for multi-byte sequences
        for (i=0; i<det.fRawLength; i++) {
            var b = input[i];
            if ((b & 0x80) == 0) {
                continue;   // ASCII
            }

            // Hi bit on char found.  Figure out how long the sequence should be
            if ((b & 0x0e0) == 0x0c0) {
                trailBytes = 1;
            } else if ((b & 0x0f0) == 0x0e0) {
                trailBytes = 2;
            } else if ((b & 0x0f8) == 0xf0) {
                trailBytes = 3;
            } else {
                numInvalid++;
                if (numInvalid > 5) {
                    break;
                }
                trailBytes = 0;
            }

            // Verify that we've got the right number of trail bytes in the sequence
            for (;;) {
                i++;
                if (i >= det.fRawLength) {
                    break;
                }
                b = input[i];
                if ((b & 0xc0) != 0x080) {
                    numInvalid++;
                    break;
                }
                if (--trailBytes == 0) {
                    numValid++;
                    break;
                }
            }
        }

        // Cook up some sort of confidence score, based on presense of a BOM
        //    and the existence of valid and/or invalid multi-byte sequences.
        confidence = 0;
        if (hasBOM && numInvalid == 0) {
            confidence = 100;
        } else if (hasBOM && numValid > numInvalid * 10) {
            confidence = 80;
        } else if (numValid > 3 && numInvalid == 0) {
            confidence = 100;
        } else if (numValid > 0 && numInvalid == 0) {
            confidence = 80;
        } else if (numValid == 0 && numInvalid == 0) {
            // Plain ASCII.
            confidence = 10;
        } else if (numValid > numInvalid * 10) {
            // Probably corruput utf-8 data.  Valid sequences aren't likely by chance.
            confidence = 25;
        }
        return confidence == 0 ? null : new Match(det, this, confidence);
    };
};
<commit_msg>cleanup utf8<commit_after>
var Match = require ('../match');

/**
 * Charset recognizer for UTF-8
 */
module.exports = function() {
    this.name = function() {
        return 'UTF-8';
    };
    this.match = function(det) {

        var hasBOM = false,
            numValid = 0,
            numInvalid = 0,
            input = det.fRawInput,
            trailBytes = 0,
            confidence;

        if (det.fRawLength >= 3 &&
            (input[0] & 0xFF) == 0xef && (input[1] & 0xFF) == 0xbb && (input[2] & 0xFF) == 0xbf) {
            hasBOM = true;
        }

        // Scan for multi-byte sequences
        for (var i = 0; i < det.fRawLength; i++) {
            var b = input[i];
            if ((b & 0x80) == 0)
                continue; // ASCII

            // Hi bit on char found.  Figure out how long the sequence should be
            if ((b & 0x0e0) == 0x0c0) {
                trailBytes = 1;
            } else if ((b & 0x0f0) == 0x0e0) {
                trailBytes = 2;
            } else if ((b & 0x0f8) == 0xf0) {
                trailBytes = 3;
            } else {
                numInvalid++;
                if (numInvalid > 5) {
                    break;
                }
                trailBytes = 0;
            }

            // Verify that we've got the right number of trail bytes in the sequence
            for (;;) {
                i++;
                if (i >= det.fRawLength) {
                    break;
                }
                b = input[i];
                if ((b & 0xc0) != 0x080) {
                    numInvalid++;
                    break;
                }
                if (--trailBytes == 0) {
                    numValid++;
                    break;
                }
            }
        }

        // Cook up some sort of confidence score, based on presense of a BOM
        //    and the existence of valid and/or invalid multi-byte sequences.
        confidence = 0;
        if (hasBOM && numInvalid == 0)
            confidence = 100;
        else if (hasBOM && numValid > numInvalid * 10)
            confidence = 80;
        else if (numValid > 3 && numInvalid == 0)
            confidence = 100;
        else if (numValid > 0 && numInvalid == 0)
            confidence = 80;
        else if (numValid == 0 && numInvalid == 0)
            // Plain ASCII.
            confidence = 10;
        else if (numValid > numInvalid * 10)
            // Probably corruput utf-8 data.  Valid sequences aren't likely by chance.
            confidence = 25;
        else
            return null

        return new Match(det, this, confidence);
    };
};
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import { ScrollView, TouchableOpacity, Text, StyleSheet } from 'react-native';

class Articles extends Component {
  componentWillMount() {
    this.props.loadArticles();
  }

  renderArticle(article) {
    const { handleNewsPress } = this.props;

    return (
      <TouchableOpacity
        style={styles.newsRow}
        key={article.newsId}
        onPress={() => { handleNewsPress(article); }}
      >
        <Text>{article.title}</Text>
      </TouchableOpacity>
    );
  }

  render() {
    const { articles } = this.props;

    return (
      <ScrollView style={styles.container}>
        {Object.keys(articles).map(index => this.renderArticle(articles[index]))}
      </ScrollView>
    );
  }
}

Articles.propTypes = {
  articles: PropTypes.object.isRequired,
  loadArticles: PropTypes.func.isRequired,
  handleNewsPress: PropTypes.func.isRequired,
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  newsRow: {
    padding: 20,
    borderStyle: 'solid',
    borderBottomWidth: 1,
    borderBottomColor: '#ccc'
  },
  title: {
    fontSize: 24,
    fontWeight: '500',
    color: '#ffffff',
    marginBottom: 30
  }
});

export default Articles;
<commit_msg>display image in the news row<commit_after>import React, { PropTypes, Component } from 'react';
import { Image, ScrollView, TouchableOpacity, Text, StyleSheet } from 'react-native';

class Articles extends Component {
  componentWillMount() {
    this.props.loadArticles();
  }

  renderArticle(article) {
    const { handleNewsPress } = this.props;

    return (
      <TouchableOpacity
        style={styles.newsRow}
        key={article.newsId}
        onPress={() => { handleNewsPress(article); }}
      >
        <Text style={styles.title}>{article.title}</Text>
        {!!article.hasCoverPhoto &&
          <Image
            resizeMode="cover"
            source={{ uri: article.coverSrc.m.src }}
            style={styles.img}
          />
        }
      </TouchableOpacity>
    );
  }

  render() {
    const { articles } = this.props;

    return (
      <ScrollView style={styles.container}>
        {Object.keys(articles).map(index => this.renderArticle(articles[index]))}
      </ScrollView>
    );
  }
}

Articles.propTypes = {
  articles: PropTypes.object.isRequired,
  loadArticles: PropTypes.func.isRequired,
  handleNewsPress: PropTypes.func.isRequired,
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  newsRow: {
    flex: 1,
    height: 100,
    backgroundColor: '#fff',
    borderBottomColor: '#ddd',
    borderBottomWidth: 1,
    flexDirection: 'row',
    paddingTop: 8,
    paddingLeft: 18,
    paddingRight: 8,
    paddingBottom: 0,
    overflow: 'hidden'
  },
  title: {
    flex: 1,
  },
  img: {
    height: 86,
    width: 86
  }
});

export default Articles;
<|endoftext|>


<commit_before>/* global THREE */
var Utils = require('./Utils.js');

const DEFAULT_OPTIONS = {
    onResetVRSensor: function (lastRotation, lastPosition) {
        console.log('lastRotation: ' + lastRotation);
        console.log('lastPosition: ' + lastPosition);
    }
};

module.exports = function (scene, config, rendererOptions) {
    "use strict";
    config = Utils.combineObjects(DEFAULT_OPTIONS, config || {});

    this.config = config;

    scene = scene || new THREE.Scene();

    this.scene = scene;

    rendererOptions = rendererOptions || {};

    this.renderer = new THREE.WebGLRenderer(rendererOptions);

    var domElement = this.renderer.domElement;

    if (!rendererOptions.canvas) {
        document.body.appendChild(domElement);
        domElement.id = 'webgl-canvas';
    }

    this.renderer.setPixelRatio(window.devicePixelRadio);
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    this.camera.matrixAutoUpdate = true;

    this.vrEffect = new THREE.VREffect(this.renderer, function(error) { throw new Error(error); });

    this.vrControls = new THREE.VRControls(this.camera, function(error) { throw new Error(error); });
    this.vrControlsEnabled = true;

    this.useImmediatePose = false;

    this.stage = new THREE.Object3D();
    this.stage.matrixAutoUpdate = false;

    this.scene.add(this.stage);

    // public methods:

    this.render = function () {
        if (this.vrControlsEnabled) this.vrControls.update(this.useImmediatePose);
        this.vrEffect.render(this.scene, this.camera);
    }.bind(this);

    this.toggleVRControls = function () {
        if (this.vrControlsEnabled) {
            this.vrControlsEnabled = false;
            this.camera.position.set(0, 0, 0);
            this.camera.quaternion.set(0, 0, 0, 1);
            this.camera.updateMatrixWorld();
        } else {
            this.vrControlsEnabled = true;
        }
    }.bind(this);

    this.toggleUseImmediatePose = function () {
        if (this.useImmediatePose) {
            this.useImmediatePose = false;
        } else {
            this.useImmediatePose = true;
            console.log('using immediate pose');
        }
    }.bind(this);

    this.resetVRSensor = ( function () {
        var lastPosition = new THREE.Vector3();
        var euler = new THREE.Euler(0, 0, 0, 'YXZ');
        var onResetVRSensor = config.onResetVRSensor;
        return function () {
            if (this.vrControlsEnabled) {
                this.vrControls.update(true);
                lastPosition.copy(this.camera.position);
                var lastRotation = this.camera.rotation.y;
                this.vrControls.resetPose();
                this.vrControls.update(true);

                // maintain correspondence between virtual / physical poses of stage objects:
                this.stage.children.forEach( function (object) {
                    // maintain rotation of object (relative heading of object w.r.t. HMD):
                    if (object === this.camera) return;
                    euler.setFromQuaternion(object.quaternion);
                    euler.y -= lastRotation;
                    object.quaternion.setFromEuler(euler);
                    // maintain position of object w.r.t. HMD:
                    object.position.sub(lastPosition);
                    object.position.applyAxisAngle(THREE.Object3D.DefaultUp, -lastRotation);
                    object.position.add(this.camera.position);
                    object.updateMatrix();
                }.bind(this) );

                this.stage.updateMatrixWorld(true);

                // this.stage.children.forEach( function (child) {
                //     euler.setFromQuaternion(child.quaternion);
                //     euler.y -= lastRotation;
                //     child.quaternion.setFromAxisAngle(THREE.Object3D.DefaultUp, euler.y);
                //     child.position.sub(lastPosition);
                //     child.position.applyAxisAngle(THREE.Object3D.DefaultUp, -lastRotation);
                //     child.position.add(this.camera.position);
                //     child.updateMatrix();
                // } );

                if (onResetVRSensor) {
                    onResetVRSensor(lastRotation, lastPosition);
                }
            }
        };
    } )().bind(this);

    this.toggleFullscreen = function (options) {
        if (!isFullscreen()) {
            requestFullscreen(options);
            // requestPointerLock();
        } else {
            exitFullscreen();
            // releasePointerLock();
        }
    };

    this.toggleWireframe = ( function () {
        var wireframeMaterial = new THREE.MeshBasicMaterial({color: 0xeeddaa, wireframe: true});
        return function () {
            if (this.scene.overrideMaterial === wireframeMaterial) {
                this.scene.overrideMaterial = null;
            } else {
                this.scene.overrideMaterial = wireframeMaterial;
            }
        };
    } )().bind(this);

    this.toggleNormalMaterial = ( function () {
        var normalMaterial = new THREE.MeshNormalMaterial();
        return function () {
            if (this.scene.overrideMaterial === normalMaterial) {
                this.scene.overrideMaterial = null;
            } else {
                this.scene.overrideMaterial = normalMaterial;
            }
        };
    } )().bind(this);

    this.vrDisplay = null;

    // WebVR setup:

    if (navigator.getVRDisplays) {
        navigator.getVRDisplays().then( function (displays) {
            if (displays.length > 0) {
                this.vrDisplay = displays[0];
                if (this.vrDisplay.stageParameters && this.vrDisplay.stageParameters.sittingToStandingTransform) {
                    this.stage.matrix.fromArray(this.vrDisplay.stageParameters.sittingToStandingTransform);
                    this.stage.matrix.decompose(this.stage.position, this.stage.quaternion, this.stage.scale);
                    this.stage.matrixWorldNeedsUpdate = true;
                }
                if (config.onGotVRDisplay) {
                    config.onGotVRDisplay(this.vrDisplay);
                }
            }
        }.bind(this) );
    } else {
        console.error('WebVR API is not supported');
    }

    this.toggleVR = function () {
        var vrDisplay = this.vrDisplay;
        if (vrDisplay && !vrDisplay.isPresenting && vrDisplay.capabilities.canPresent) {
            this.vrEffect.requestPresent().then( function () {
                if (vrDisplay.capabilities.hasExternalDisplay) {
                    var eyeParams = vrDisplay.getEyeParameters( 'left' );
                    this.renderer.setSize(2*eyeParams.renderWidth, eyeParams.renderHeight);
                }
                // requestPointerLock();
            }.bind(this) );
        } else if (vrDisplay && vrDisplay.isPresenting) {
            this.vrEffect.exitPresent().then( function () {
                console.log('exited VR presentation');
            } );
        } else {
            console.error('there is no capable VRDisplay available');
        }
    }.bind(this);

    // resize, fullscreen/VR listener functions and other useful functions:

    var onResize = function () {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }.bind(this);

    var onFullscreenChange = function () {
        onResize();
    };

    function isFullscreen() {
        return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
    }

    function requestFullscreen(options) {
        if (domElement.requestFullscreen) {
            domElement.requestFullscreen(options);
        } else if (domElement.msRequestFullscreen) {
            domElement.msRequestFullscreen();
        } else if (domElement.mozRequestFullScreen) {
            domElement.mozRequestFullScreen(options);
        } else if (domElement.webkitRequestFullscreen) {
            domElement.webkitRequestFullscreen();
        } else {
            throw 'Fullscreen API is not supported';
        }
    }

    function exitFullscreen() {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        } else {
            console.warn('exitFullscreen is not supported');
        }
    }

    // function requestPointerLock() {
    //     if (domElement.requestPointerLock) {
    //         domElement.requestPointerLock();
    //     } else if (domElement.mozRequestPointerLock) {
    //         domElement.mozRequestPointerLock();
    //     } else if (domElement.webkitRequestPointerLock) {
    //         domElement.webkitRequestPointerLock();
    //     }
    // }

    // function releasePointerLock() {
    //     if (document.exitPointerLock) {
    //         document.exitPointerLock();
    //     } else if (document.mozExitPointerLock) {
    //         document.mozExitPointerLock();
    //     } else if (document.webkitExitPointerLock) {
    //         document.webkitExitPointerLock();
    //     }
    // }

    var beforeUnload = function () {
        // stop VR presenting when exiting the app
        if (this.vrDisplay && this.vrDisplay.isPresenting) {
            this.vrEffect.exitPresent();
        }
    }.bind(this);

    // add standard event listeners

    window.addEventListener('resize', onResize, false);
    document.addEventListener(domElement.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange',
        onFullscreenChange, false);
    window.addEventListener("beforeunload", beforeUnload, false);
};
<commit_msg>fixed renderer resizing after requestPresent<commit_after>/* global THREE */
var Utils = require('./Utils.js');

const DEFAULT_OPTIONS = {
    onResetVRSensor: function (lastRotation, lastPosition) {
        console.log('lastRotation: ' + lastRotation);
        console.log('lastPosition: ' + lastPosition);
    }
};

module.exports = function (scene, config, rendererOptions) {
    "use strict";
    config = Utils.combineObjects(DEFAULT_OPTIONS, config || {});

    this.config = config;

    scene = scene || new THREE.Scene();

    this.scene = scene;

    rendererOptions = rendererOptions || {};

    this.renderer = new THREE.WebGLRenderer(rendererOptions);

    var domElement = this.renderer.domElement;

    if (!rendererOptions.canvas) {
        document.body.appendChild(domElement);
        domElement.id = 'webgl-canvas';
    }

    this.renderer.setPixelRatio(window.devicePixelRadio);
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    this.camera.matrixAutoUpdate = true;

    this.vrEffect = new THREE.VREffect(this.renderer, function(error) { throw new Error(error); });

    this.vrControls = new THREE.VRControls(this.camera, function(error) { throw new Error(error); });
    this.vrControlsEnabled = true;

    this.useImmediatePose = false;

    this.stage = new THREE.Object3D();
    this.stage.matrixAutoUpdate = false;

    this.scene.add(this.stage);

    // public methods:

    this.render = function () {
        if (this.vrControlsEnabled) this.vrControls.update(this.useImmediatePose);
        this.vrEffect.render(this.scene, this.camera);
    }.bind(this);

    this.toggleVRControls = function () {
        if (this.vrControlsEnabled) {
            this.vrControlsEnabled = false;
            this.camera.position.set(0, 0, 0);
            this.camera.quaternion.set(0, 0, 0, 1);
            this.camera.updateMatrixWorld();
        } else {
            this.vrControlsEnabled = true;
        }
    }.bind(this);

    this.toggleUseImmediatePose = function () {
        if (this.useImmediatePose) {
            this.useImmediatePose = false;
        } else {
            this.useImmediatePose = true;
            console.log('using immediate pose');
        }
    }.bind(this);

    this.resetVRSensor = ( function () {
        var lastPosition = new THREE.Vector3();
        var euler = new THREE.Euler(0, 0, 0, 'YXZ');
        var onResetVRSensor = config.onResetVRSensor;
        return function () {
            if (this.vrControlsEnabled) {
                this.vrControls.update(true);
                lastPosition.copy(this.camera.position);
                var lastRotation = this.camera.rotation.y;
                this.vrControls.resetPose();
                this.vrControls.update(true);

                // maintain correspondence between virtual / physical poses of stage objects:
                this.stage.children.forEach( function (object) {
                    // maintain rotation of object (relative heading of object w.r.t. HMD):
                    if (object === this.camera) return;
                    euler.setFromQuaternion(object.quaternion);
                    euler.y -= lastRotation;
                    object.quaternion.setFromEuler(euler);
                    // maintain position of object w.r.t. HMD:
                    object.position.sub(lastPosition);
                    object.position.applyAxisAngle(THREE.Object3D.DefaultUp, -lastRotation);
                    object.position.add(this.camera.position);
                    object.updateMatrix();
                }.bind(this) );

                this.stage.updateMatrixWorld(true);

                // this.stage.children.forEach( function (child) {
                //     euler.setFromQuaternion(child.quaternion);
                //     euler.y -= lastRotation;
                //     child.quaternion.setFromAxisAngle(THREE.Object3D.DefaultUp, euler.y);
                //     child.position.sub(lastPosition);
                //     child.position.applyAxisAngle(THREE.Object3D.DefaultUp, -lastRotation);
                //     child.position.add(this.camera.position);
                //     child.updateMatrix();
                // } );

                if (onResetVRSensor) {
                    onResetVRSensor(lastRotation, lastPosition);
                }
            }
        };
    } )().bind(this);

    this.toggleFullscreen = function (options) {
        if (!isFullscreen()) {
            requestFullscreen(options);
            // requestPointerLock();
        } else {
            exitFullscreen();
            // releasePointerLock();
        }
    };

    this.toggleWireframe = ( function () {
        var wireframeMaterial = new THREE.MeshBasicMaterial({color: 0xeeddaa, wireframe: true});
        return function () {
            if (this.scene.overrideMaterial === wireframeMaterial) {
                this.scene.overrideMaterial = null;
            } else {
                this.scene.overrideMaterial = wireframeMaterial;
            }
        };
    } )().bind(this);

    this.toggleNormalMaterial = ( function () {
        var normalMaterial = new THREE.MeshNormalMaterial();
        return function () {
            if (this.scene.overrideMaterial === normalMaterial) {
                this.scene.overrideMaterial = null;
            } else {
                this.scene.overrideMaterial = normalMaterial;
            }
        };
    } )().bind(this);

    this.vrDisplay = null;

    // WebVR setup:

    if (navigator.getVRDisplays) {
        navigator.getVRDisplays().then( function (displays) {
            if (displays.length > 0) {
                this.vrDisplay = displays[0];
                if (this.vrDisplay.stageParameters && this.vrDisplay.stageParameters.sittingToStandingTransform) {
                    this.stage.matrix.fromArray(this.vrDisplay.stageParameters.sittingToStandingTransform);
                    this.stage.matrix.decompose(this.stage.position, this.stage.quaternion, this.stage.scale);
                    this.stage.matrixWorldNeedsUpdate = true;
                }
                if (config.onGotVRDisplay) {
                    config.onGotVRDisplay(this.vrDisplay);
                }
            }
        }.bind(this) );
    } else {
        console.error('WebVR API is not supported');
    }

    this.toggleVR = function () {
        var vrDisplay = this.vrDisplay;
        if (vrDisplay && !vrDisplay.isPresenting && vrDisplay.capabilities.canPresent) {
            this.vrEffect.requestPresent().then( function () {
                // if (vrDisplay.capabilities.hasExternalDisplay) {
                //     var eyeParams = vrDisplay.getEyeParameters( 'left' );
                //     this.renderer.setSize(2*eyeParams.renderWidth, eyeParams.renderHeight);
                // }
                // requestPointerLock();
            }.bind(this) );
        } else if (vrDisplay && vrDisplay.isPresenting) {
            this.vrEffect.exitPresent().then( function () {
                console.log('exited VR presentation');
            } );
        } else {
            console.error('there is no capable VRDisplay available');
        }
    }.bind(this);

    // resize, fullscreen/VR listener functions and other useful functions:

    var onResize = function () {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }.bind(this);

    var onFullscreenChange = function () {
        onResize();
    };

    function isFullscreen() {
        return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
    }

    function requestFullscreen(options) {
        if (domElement.requestFullscreen) {
            domElement.requestFullscreen(options);
        } else if (domElement.msRequestFullscreen) {
            domElement.msRequestFullscreen();
        } else if (domElement.mozRequestFullScreen) {
            domElement.mozRequestFullScreen(options);
        } else if (domElement.webkitRequestFullscreen) {
            domElement.webkitRequestFullscreen();
        } else {
            throw 'Fullscreen API is not supported';
        }
    }

    function exitFullscreen() {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        } else {
            console.warn('exitFullscreen is not supported');
        }
    }

    // function requestPointerLock() {
    //     if (domElement.requestPointerLock) {
    //         domElement.requestPointerLock();
    //     } else if (domElement.mozRequestPointerLock) {
    //         domElement.mozRequestPointerLock();
    //     } else if (domElement.webkitRequestPointerLock) {
    //         domElement.webkitRequestPointerLock();
    //     }
    // }

    // function releasePointerLock() {
    //     if (document.exitPointerLock) {
    //         document.exitPointerLock();
    //     } else if (document.mozExitPointerLock) {
    //         document.mozExitPointerLock();
    //     } else if (document.webkitExitPointerLock) {
    //         document.webkitExitPointerLock();
    //     }
    // }

    var beforeUnload = function () {
        // stop VR presenting when exiting the app
        if (this.vrDisplay && this.vrDisplay.isPresenting) {
            this.vrEffect.exitPresent();
        }
    }.bind(this);

    // add standard event listeners

    window.addEventListener('resize', onResize, false);
    document.addEventListener(domElement.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange',
        onFullscreenChange, false);
    window.addEventListener("beforeunload", beforeUnload, false);
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

const { join, dirname, basename } = require('path')
const { promises: fs } = require('fs')
const { safeDump } = require('js-yaml')
const clone = require('clone')
const { fileExists } = require('./utils')

const sourceFolder = process.argv[2]
const destFolder = process.argv[3]

if (!sourceFolder || !destFolder) {
  throw new Error(`Missing parameters sourceFolder and destFolder.

  Expected command:
    ${process.argv[0]} ${process.argv[1]} <sourceFolder> <destFolder>
`)
}

async function main () {
  const releasesFile = join(sourceFolder, 'releases.json')
  const releaseFileContent = await fs.readFile(releasesFile, 'utf-8')
  const releases = JSON.parse(releaseFileContent)
  const masterRelease = releases.find(r => r.name === 'master')
  await createDocSources(releases)
  await createEcosystemDataFile(masterRelease.dest)
  console.log('All releases processed successfully')
}

async function createDocSources (releases) {
  const latestRelease = releases.find(r => r.name === 'latest')
  const tocByRelease = await Promise.all(releases.map(getTOCForRelease))
  const indexedToc = releases.reduce((acc, curr, i) => {
    acc[curr.docsPath] = tocByRelease[i]
    return acc
  }, {})
  const versions = releases.map(r => r.docsPath)
  await createDocsDataFile(join(destFolder, 'data', 'docs.yml'), { versions, toc: indexedToc, releases })
  await processDocFiles(indexedToc, latestRelease)
  await createIndexFiles(releases)
}

async function extractTOCFromFile (file, release) {
  const fileContent = await fs.readFile(file, 'utf8')

  // searches for the beginning of the ToC in the file (between '## Documentation' and '\n\n')
  const lines = fileContent.split('## Documentation')[1].split('\n\n')[0].split('\n').filter(Boolean)
  // every line is a ToC entry
  const re = /(master|.)\/docs\/([a-zA-Z-0-9]+\.md)">(.+)<\/b>/
  const toc = lines.map((line) => {
    const match = re.exec(line)
    const fileName = match[2]
    const name = match[3]
    const sourceFile = join(dirname(file), 'docs', fileName)
    const destinationFile = join(destFolder, 'content', 'docs', release.docsPath, fileName)
    const slug = basename(sourceFile, '.md')
    const link = `/docs/${release.docsPath}/${slug}`

    return {
      fileName,
      name,
      sourceFile,
      destinationFile,
      slug,
      link,
      fullVersion: release.fullVersion,
      docsPath: release.docsPath,
      label: release.label
    }
  })

  return toc
}

async function getTOCForRelease (release) {
  const folder = join(sourceFolder, release.dest)
  const files = await fs.readdir(folder)
  const subfolder = files.find(file => file.match(/^fastify-/))
  const indexFile = join(folder, subfolder, 'README.md')

  return extractTOCFromFile(indexFile, release)
}

function createDocsDataFile (destination, docsInfo) {
  const toDump = clone(docsInfo)
  // remove sourceFile and destinationFile keys from toc
  Object.keys(toDump.toc).forEach((version) => {
    toDump.toc[version].forEach((entry, i) => {
      delete toDump.toc[version][i].sourceFile
      delete toDump.toc[version][i].destinationFile
    })
  })

  return fs.writeFile(destination, safeDump(toDump), 'utf8')
}

async function processDocFiles (docs, latestRelease) {
  // merge all docs into a single array adding the version as a key in every object
  const docsArray = Object.keys(docs).reduce((acc, version) => {
    const curr = docs[version]
    return acc.concat(curr.map((item) => {
      item.version = version
      return item
    }))
  }, [])

  for (const item of docsArray) {
    const dir = dirname(item.destinationFile)
    const hasDir = await fileExists(dir)
    if (!hasDir) {
      await fs.mkdir(dir)
    }

    const buffer = await fs.readFile(item.sourceFile, 'utf8')

    let content = buffer.toString()

    // removes doc header from github
    content = content.replace(/
Fastify<\/h1>\n/, '')

    // remap links
    content = remapLinks(content, item)

    // adds frontmatter
    content =
`---
title: ${item.name}
layout: docs_page.html
path: ${item.link}
version: ${item.version}
fullVersion: ${item.fullVersion}
label: ${item.label}
docsPath: ${item.docsPath}
${item.version === 'latest' ? `canonical: "${item.link.replace(/latest/, latestRelease.label)}"` : ''}
${item.version === 'master' ? `github_url: https://github.com/fastify/fastify/blob/master/docs/${item.fileName}` : ''}
---
${content}`

    await fs.writeFile(item.destinationFile, content, 'utf8')
    console.log(`Created doc page ${item.destinationFile}`)
  }
}

function remapLinks (content, item) {
  /*
    Links remapping rules:
    /https:\/\/github.com\/fastify\/fastify\/blob\/master\/docs/ -> /docs/[VERSION]
    [XXXX](Plugins.md) -> [XXXX](/docs/[VERSION]/Plugins)
    [XXXX](/docs/VVVV/Ecosystem.md) -> [XXXX](/ecosystem)
    [XXXX](/docs/VVVV/YYYY.md) -> [XXXX](/docs/VVVV/YYYY)
    [XXXX]('./YYYY' "ZZZZ") -> [XXXX]('/docs/[VERSION]/YYYY' "ZZZZ")
    [XXXX](./YYYY "ZZZZ") -> [XXXX]('/docs/[VERSION]/YYYY' "ZZZZ")
    href="https://github.com/fastify/fastify/blob/master/docs/YYYY.md -> href="/docs/[VERSION]/YYYY
  */
  const ecosystemLinkRx = /\(\/docs\/[\w\d.-]+\/Ecosystem\.md\)/gi
  const docInternalLinkRx = /\(\/docs\/[\w\d.-]+\/[\w\d-]+(.md)/gi
  const pluginsLink = /\(Plugins.md\)/gi
  const relativeLinks = /\((.\/)?(([a-zA-Z0-9\-_]+).md(#[a-z0-9\-_]+)?)\)/gi
  const relativeLinksWithLabel = /\('?(\.\/)([\w\d.-]+)(.md)'?\s+"([\w\d.-]+)"\)/gi
  const hrefAbsoluteLinks = /href="https:\/\/github\.com\/fastify\/fastify\/blob\/master\/docs\/([\w\d.-]+)\.md/gi
  const absoluteLinks = /https:\/\/github.com\/fastify\/fastify\/blob\/master\/docs/gi
  return content
    .replace(hrefAbsoluteLinks, (match, p1) => `href="/docs/${item.version}/${p1}`)
    .replace(absoluteLinks, `/docs/${item.version}`)
    .replace(ecosystemLinkRx, (match) => '(/ecosystem)')
    .replace(pluginsLink, (match) => `(/docs/${item.version}/Plugins)`)
    .replace(relativeLinks, (match, ...parts) => `(/docs/${item.version}/${parts[2]})`)
    .replace(relativeLinksWithLabel, (match, ...parts) => `(/docs/${item.version}/${parts[1]} "${parts[3]}")`)
    .replace(docInternalLinkRx, (match, p1) => match.replace(p1, ''))
}

async function createVersionIndexFile (release) {
  const content = `---
title: Documentation - ${release.name}
layout: docs_version_index.html
path: /docs/${release.docsPath}
version: ${release.name}
fullVersion: ${release.fullVersion}
label: ${release.label}
docsPath: ${release.docsPath}
${release.name === 'latest' ? `canonical: "/docs/${release.label}"` : ''}
github_url: "https://github.com/fastify/website/blob/master/src/website/layouts/docs_version_index.html"
---`

  const dest = join(destFolder, 'content', 'docs', release.docsPath, 'index.md')
  await fs.writeFile(dest, content, 'utf8')
  console.log(`Created doc index page ${dest}`)
}

async function createIndexFiles (releases) {
  // create docs index

  const docsIndexContent = `---
title: Documentation
layout: docs_index.html
path: /docs
github_url: "https://github.com/fastify/website/blob/master/src/website/layouts/docs_index.html"
---`

  const dest = join(destFolder, 'content', 'docs', 'index.md')
  await fs.writeFile(dest, docsIndexContent, 'utf8')
  console.log(`Created docs index at ${dest}`)

  for (const release of releases) {
    await createVersionIndexFile(release)
  }
}

const extractPlugins = (pluginContent) => {
  const lines = pluginContent
    .split('\n')
    .filter(Boolean) // remove empty lines

  // if a line doesn't start with "-" merge it back with the previous item
  const mergedLines = lines.reduce((acc, curr) => {
    if (curr[0] === '-') {
      acc.push(curr)
    } else {
      acc[acc.length - 1] += ' ' + curr
    }
    return acc
  }, [])
  const re = /\[`([-a-zA-Z0-9]+)`\]\(([^)]+)\)(\s*(.+))?/
  const plugins = mergedLines.map((line) => {
    const match = re.exec(line)

    const name = match[1]
    const url = match[2]
    const description = match[3] ? match[3].trim() : ''

    return { name, url, description }
  })
  return plugins
}

async function extractEcosystemFromFile (file) {
  const data = await fs.readFile(file, 'utf8')

  const content = data.toString()
  const corePluginsContent = content
    .split('#### [Core](#core)\n')[1]
    .split('#### [Community](#community)')[0]

  const communityPluginsContent = content
    .split('#### [Core](#core)\n')[1]
    .split('#### [Community](#community)')[1]

  const plugins = {
    corePlugins: extractPlugins(corePluginsContent),
    communityPlugins: extractPlugins(communityPluginsContent)
  }

  return ({ plugins })
}

async function createEcosystemDataFile (masterReleaseDownloadPath) {
  const versionFolder = join(sourceFolder, masterReleaseDownloadPath)
  const destination = join(destFolder, 'data', 'ecosystem.yml')
  const files = await fs.readdir(versionFolder)
  const subfolder = files.find(file => file.match(/^fastify-/))
  const ecosystemFile = join(versionFolder, subfolder, 'docs', 'Ecosystem.md')

  const ecosystem = await extractEcosystemFromFile(ecosystemFile)
  await fs.writeFile(destination, safeDump(ecosystem), 'utf8')
  console.log(`Ecosystem file written: ${destination}`)
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})
<commit_msg>Fix/parsing of plugins (#191)<commit_after>#!/usr/bin/env node

const { join, dirname, basename } = require('path')
const { promises: fs } = require('fs')
const { safeDump } = require('js-yaml')
const clone = require('clone')
const { fileExists } = require('./utils')

const sourceFolder = process.argv[2]
const destFolder = process.argv[3]

if (!sourceFolder || !destFolder) {
  throw new Error(`Missing parameters sourceFolder and destFolder.

  Expected command:
    ${process.argv[0]} ${process.argv[1]} <sourceFolder> <destFolder>
`)
}

async function main () {
  const releasesFile = join(sourceFolder, 'releases.json')
  const releaseFileContent = await fs.readFile(releasesFile, 'utf-8')
  const releases = JSON.parse(releaseFileContent)
  const masterRelease = releases.find(r => r.name === 'master')
  await createDocSources(releases)
  await createEcosystemDataFile(masterRelease.dest)
  console.log('All releases processed successfully')
}

async function createDocSources (releases) {
  const latestRelease = releases.find(r => r.name === 'latest')
  const tocByRelease = await Promise.all(releases.map(getTOCForRelease))
  const indexedToc = releases.reduce((acc, curr, i) => {
    acc[curr.docsPath] = tocByRelease[i]
    return acc
  }, {})
  const versions = releases.map(r => r.docsPath)
  await createDocsDataFile(join(destFolder, 'data', 'docs.yml'), { versions, toc: indexedToc, releases })
  await processDocFiles(indexedToc, latestRelease)
  await createIndexFiles(releases)
}

async function extractTOCFromFile (file, release) {
  const fileContent = await fs.readFile(file, 'utf8')

  // searches for the beginning of the ToC in the file (between '## Documentation' and '\n\n')
  const lines = fileContent.split('## Documentation')[1].split('\n\n')[0].split('\n').filter(Boolean)
  // every line is a ToC entry
  const re = /(master|.)\/docs\/([a-zA-Z-0-9]+\.md)">(.+)<\/b>/
  const toc = lines.map((line) => {
    const match = re.exec(line)
    const fileName = match[2]
    const name = match[3]
    const sourceFile = join(dirname(file), 'docs', fileName)
    const destinationFile = join(destFolder, 'content', 'docs', release.docsPath, fileName)
    const slug = basename(sourceFile, '.md')
    const link = `/docs/${release.docsPath}/${slug}`

    return {
      fileName,
      name,
      sourceFile,
      destinationFile,
      slug,
      link,
      fullVersion: release.fullVersion,
      docsPath: release.docsPath,
      label: release.label
    }
  })

  return toc
}

async function getTOCForRelease (release) {
  const folder = join(sourceFolder, release.dest)
  const files = await fs.readdir(folder)
  const subfolder = files.find(file => file.match(/^fastify-/))
  const indexFile = join(folder, subfolder, 'README.md')

  return extractTOCFromFile(indexFile, release)
}

function createDocsDataFile (destination, docsInfo) {
  const toDump = clone(docsInfo)
  // remove sourceFile and destinationFile keys from toc
  Object.keys(toDump.toc).forEach((version) => {
    toDump.toc[version].forEach((entry, i) => {
      delete toDump.toc[version][i].sourceFile
      delete toDump.toc[version][i].destinationFile
    })
  })

  return fs.writeFile(destination, safeDump(toDump), 'utf8')
}

async function processDocFiles (docs, latestRelease) {
  // merge all docs into a single array adding the version as a key in every object
  const docsArray = Object.keys(docs).reduce((acc, version) => {
    const curr = docs[version]
    return acc.concat(curr.map((item) => {
      item.version = version
      return item
    }))
  }, [])

  for (const item of docsArray) {
    const dir = dirname(item.destinationFile)
    const hasDir = await fileExists(dir)
    if (!hasDir) {
      await fs.mkdir(dir)
    }

    const buffer = await fs.readFile(item.sourceFile, 'utf8')

    let content = buffer.toString()

    // removes doc header from github
    content = content.replace(/
Fastify<\/h1>\n/, '')

    // remap links
    content = remapLinks(content, item)

    // adds frontmatter
    content =
`---
title: ${item.name}
layout: docs_page.html
path: ${item.link}
version: ${item.version}
fullVersion: ${item.fullVersion}
label: ${item.label}
docsPath: ${item.docsPath}
${item.version === 'latest' ? `canonical: "${item.link.replace(/latest/, latestRelease.label)}"` : ''}
${item.version === 'master' ? `github_url: https://github.com/fastify/fastify/blob/master/docs/${item.fileName}` : ''}
---
${content}`

    await fs.writeFile(item.destinationFile, content, 'utf8')
    console.log(`Created doc page ${item.destinationFile}`)
  }
}

function remapLinks (content, item) {
  /*
    Links remapping rules:
    /https:\/\/github.com\/fastify\/fastify\/blob\/master\/docs/ -> /docs/[VERSION]
    [XXXX](Plugins.md) -> [XXXX](/docs/[VERSION]/Plugins)
    [XXXX](/docs/VVVV/Ecosystem.md) -> [XXXX](/ecosystem)
    [XXXX](/docs/VVVV/YYYY.md) -> [XXXX](/docs/VVVV/YYYY)
    [XXXX]('./YYYY' "ZZZZ") -> [XXXX]('/docs/[VERSION]/YYYY' "ZZZZ")
    [XXXX](./YYYY "ZZZZ") -> [XXXX]('/docs/[VERSION]/YYYY' "ZZZZ")
    href="https://github.com/fastify/fastify/blob/master/docs/YYYY.md -> href="/docs/[VERSION]/YYYY
  */
  const ecosystemLinkRx = /\(\/docs\/[\w\d.-]+\/Ecosystem\.md\)/gi
  const docInternalLinkRx = /\(\/docs\/[\w\d.-]+\/[\w\d-]+(.md)/gi
  const pluginsLink = /\(Plugins.md\)/gi
  const relativeLinks = /\((.\/)?(([a-zA-Z0-9\-_]+).md(#[a-z0-9\-_]+)?)\)/gi
  const relativeLinksWithLabel = /\('?(\.\/)([\w\d.-]+)(.md)'?\s+"([\w\d.-]+)"\)/gi
  const hrefAbsoluteLinks = /href="https:\/\/github\.com\/fastify\/fastify\/blob\/master\/docs\/([\w\d.-]+)\.md/gi
  const absoluteLinks = /https:\/\/github.com\/fastify\/fastify\/blob\/master\/docs/gi
  return content
    .replace(hrefAbsoluteLinks, (match, p1) => `href="/docs/${item.version}/${p1}`)
    .replace(absoluteLinks, `/docs/${item.version}`)
    .replace(ecosystemLinkRx, (match) => '(/ecosystem)')
    .replace(pluginsLink, (match) => `(/docs/${item.version}/Plugins)`)
    .replace(relativeLinks, (match, ...parts) => `(/docs/${item.version}/${parts[2]})`)
    .replace(relativeLinksWithLabel, (match, ...parts) => `(/docs/${item.version}/${parts[1]} "${parts[3]}")`)
    .replace(docInternalLinkRx, (match, p1) => match.replace(p1, ''))
}

async function createVersionIndexFile (release) {
  const content = `---
title: Documentation - ${release.name}
layout: docs_version_index.html
path: /docs/${release.docsPath}
version: ${release.name}
fullVersion: ${release.fullVersion}
label: ${release.label}
docsPath: ${release.docsPath}
${release.name === 'latest' ? `canonical: "/docs/${release.label}"` : ''}
github_url: "https://github.com/fastify/website/blob/master/src/website/layouts/docs_version_index.html"
---`

  const dest = join(destFolder, 'content', 'docs', release.docsPath, 'index.md')
  await fs.writeFile(dest, content, 'utf8')
  console.log(`Created doc index page ${dest}`)
}

async function createIndexFiles (releases) {
  // create docs index

  const docsIndexContent = `---
title: Documentation
layout: docs_index.html
path: /docs
github_url: "https://github.com/fastify/website/blob/master/src/website/layouts/docs_index.html"
---`

  const dest = join(destFolder, 'content', 'docs', 'index.md')
  await fs.writeFile(dest, docsIndexContent, 'utf8')
  console.log(`Created docs index at ${dest}`)

  for (const release of releases) {
    await createVersionIndexFile(release)
  }
}

const extractPlugins = (pluginContent) => {
  const lines = pluginContent
    .split('\n')
    .filter(Boolean) // remove empty lines

  // if a line doesn't start with "-" merge it back with the previous item
  const mergedLines = lines.reduce((acc, curr) => {
    if (curr[0] === '-') {
      acc.push(curr)
    } else {
      acc[acc.length - 1] += ' ' + curr
    }
    return acc
  }, [])
  const re = /\[`([-a-zA-Z0-9.]+)`\]\(([^)]+)\)(\s*(.+))?/
  const plugins = mergedLines.map((line) => {
    const match = re.exec(line)

    const name = match[1]
    const url = match[2]
    const description = match[3] ? match[3].trim() : ''

    return { name, url, description }
  })
  return plugins
}

async function extractEcosystemFromFile (file) {
  const data = await fs.readFile(file, 'utf8')

  const content = data.toString()
  const corePluginsContent = content
    .split('#### [Core](#core)\n')[1]
    .split('#### [Community](#community)')[0]

  const communityPluginsContent = content
    .split('#### [Core](#core)\n')[1]
    .split('#### [Community](#community)')[1]

  const plugins = {
    corePlugins: extractPlugins(corePluginsContent),
    communityPlugins: extractPlugins(communityPluginsContent)
  }

  return ({ plugins })
}

async function createEcosystemDataFile (masterReleaseDownloadPath) {
  const versionFolder = join(sourceFolder, masterReleaseDownloadPath)
  const destination = join(destFolder, 'data', 'ecosystem.yml')
  const files = await fs.readdir(versionFolder)
  const subfolder = files.find(file => file.match(/^fastify-/))
  const ecosystemFile = join(versionFolder, subfolder, 'docs', 'Ecosystem.md')

  const ecosystem = await extractEcosystemFromFile(ecosystemFile)
  await fs.writeFile(destination, safeDump(ecosystem), 'utf8')
  console.log(`Ecosystem file written: ${destination}`)
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})
<|endoftext|>


<commit_before>'use strict'

import React from 'react'
import { hot, setConfig } from 'react-hot-loader'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { useObserver } from 'mobx-react'
import classNames from 'classnames'

import RootContext from '../context/RootContext'

import BackgroundAnimation from '../components/BackgroundAnimation'
import JoinChannel from '../components/JoinChannel'
import Spinner from '../components/Spinner'

import ChannelLink from './ChannelLink'

import '../styles/flaticon.css'
import '../styles/ControlPanel.scss'

setConfig({
  pureSFC: true,
  pureRender: true
})

function ControlPanel ({ history }) {
  const { networkStore, uiStore, sessionStore, setAppState } = React.useContext(RootContext)
  const [t] = useTranslation()

  const inputRef = React.useRef()

  const focusInput = React.useCallback(() => {
    if (inputRef.current) inputRef.current.focus()
  }, [])

  React.useLayoutEffect(focusInput)

  const isClosable = history.location ? history.location.pathname !== '/' : true

  const handleClose = React.useCallback(
    force => {
      if (force || isClosable) uiStore.closeControlPanel()
    },
    [isClosable]
  )

  const handleRedirect = React.useCallback(
    url => {
      setAppState({ redirectTo: url })
      handleClose(url !== '/')
    },
    [handleClose]
  )

  const handleJoinChannel = React.useCallback(
    e => {
      e.preventDefault()
      if (!inputRef.current) return
      const channel = inputRef.current.value.trim()
      networkStore.joinChannel(channel).then(() => {
        inputRef.current.value = ''
        handleRedirect(`/channel/${channel}`)
      })
    },
    [handleRedirect]
  )

  const handleCloseChannel = React.useCallback(
    channel => {
      if (uiStore.currentChannelName === channel.channelName) handleRedirect('/')
      networkStore.leaveChannel(channel.channelName)
    },
    [uiStore.currentChannelName]
  )

  function renderJoinChannelInput () {
    return networkStore.isOnline ? (

        <JoinChannel
          onSubmit={handleJoinChannel}
          autoFocus
          theme={{ ...uiStore.theme }}
          inputRef={inputRef}
        />

    ) : !networkStore.starting ? (
      <button
        className='startIpfsButton submitButton'
        style={{ ...uiStore.theme }}
        onClick={() => networkStore.start()}
      >
        {t('controlPanel.startJsIpfs')}
      </button>
    ) : (

        <Spinner />

    )
  }

  function renderChannelsList () {
    return (
      <>

 0,
            hidden: networkStore.channelsAsArray.length === 0
          })}
        >
          {t('controlPanel.channels')}

            {networkStore.channelsAsArray.map(c => (

                <ChannelLink
                  channel={c}
                  theme={{ ...uiStore.theme }}
                  onClick={e => {
                    e.preventDefault()
                    handleRedirect(`/channel/${c.channelName}`)
                  }}
                />
                 handleCloseChannel(c)}>
                  {t('controlPanel.closeChannel')}

            ))}

      </>
    )
  }

  function renderBottomRow () {
    return (

 handleRedirect('/settings')}
          style={{ ...uiStore.theme }}
          key='settingsIcon'
        />
        {/*
 */}

 sessionStore.logout()}
          style={{ ...uiStore.theme }}
          key='disconnectIcon'
        />

    )
  }

  return useObserver(() =>
    uiStore.isControlPanelOpen && sessionStore.isAuthenticated ? (
      <>

            <BackgroundAnimation
              size={320}
              startY={58}
              theme={{ ...uiStore.theme }}
              style={{ alignItems: 'flex-start' }}
            />

Orbit

            {networkStore.networkName}

{sessionStore.username}

          {renderJoinChannelInput()}
          {renderChannelsList()}
          {renderBottomRow()}

      </>
    ) : null
  )
}

ControlPanel.propTypes = {
  history: PropTypes.object.isRequired
}

export default hot(module)(ControlPanel)
<commit_msg>Minor dep fix<commit_after>'use strict'

import React from 'react'
import { hot, setConfig } from 'react-hot-loader'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { useObserver } from 'mobx-react'
import classNames from 'classnames'

import RootContext from '../context/RootContext'

import BackgroundAnimation from '../components/BackgroundAnimation'
import JoinChannel from '../components/JoinChannel'
import Spinner from '../components/Spinner'

import ChannelLink from './ChannelLink'

import '../styles/flaticon.css'
import '../styles/ControlPanel.scss'

setConfig({
  pureSFC: true,
  pureRender: true
})

function ControlPanel ({ history }) {
  const { networkStore, uiStore, sessionStore, setAppState } = React.useContext(RootContext)
  const [t] = useTranslation()

  const inputRef = React.useRef()

  const focusInput = React.useCallback(() => {
    if (inputRef.current) inputRef.current.focus()
  }, [])

  React.useLayoutEffect(focusInput)

  const isClosable = history.location ? history.location.pathname !== '/' : true

  const handleClose = React.useCallback(
    force => {
      if (force || isClosable) uiStore.closeControlPanel()
    },
    [isClosable]
  )

  const handleRedirect = React.useCallback(
    url => {
      setAppState({ redirectTo: url })
      handleClose(url !== '/')
    },
    [handleClose]
  )

  const handleJoinChannel = React.useCallback(
    e => {
      e.preventDefault()
      if (!inputRef.current) return
      const channel = inputRef.current.value.trim()
      networkStore.joinChannel(channel).then(() => {
        inputRef.current.value = ''
        handleRedirect(`/channel/${channel}`)
      })
    },
    [handleRedirect]
  )

  const handleCloseChannel = React.useCallback(
    channel => {
      if (uiStore.currentChannelName === channel.channelName) handleRedirect('/')
      networkStore.leaveChannel(channel.channelName)
    },
    [uiStore.currentChannelName, handleRedirect]
  )

  function renderJoinChannelInput () {
    return networkStore.isOnline ? (

        <JoinChannel
          onSubmit={handleJoinChannel}
          autoFocus
          theme={{ ...uiStore.theme }}
          inputRef={inputRef}
        />

    ) : !networkStore.starting ? (
      <button
        className='startIpfsButton submitButton'
        style={{ ...uiStore.theme }}
        onClick={() => networkStore.start()}
      >
        {t('controlPanel.startJsIpfs')}
      </button>
    ) : (

        <Spinner />

    )
  }

  function renderChannelsList () {
    return (
      <>

 0,
            hidden: networkStore.channelsAsArray.length === 0
          })}
        >
          {t('controlPanel.channels')}

            {networkStore.channelsAsArray.map(c => (

                <ChannelLink
                  channel={c}
                  theme={{ ...uiStore.theme }}
                  onClick={e => {
                    e.preventDefault()
                    handleRedirect(`/channel/${c.channelName}`)
                  }}
                />
                 handleCloseChannel(c)}>
                  {t('controlPanel.closeChannel')}

            ))}

      </>
    )
  }

  function renderBottomRow () {
    return (

 handleRedirect('/settings')}
          style={{ ...uiStore.theme }}
          key='settingsIcon'
        />
        {/*
 */}

 sessionStore.logout()}
          style={{ ...uiStore.theme }}
          key='disconnectIcon'
        />

    )
  }

  return useObserver(() =>
    uiStore.isControlPanelOpen && sessionStore.isAuthenticated ? (
      <>

            <BackgroundAnimation
              size={320}
              startY={58}
              theme={{ ...uiStore.theme }}
              style={{ alignItems: 'flex-start' }}
            />

Orbit

            {networkStore.networkName}

{sessionStore.username}

          {renderJoinChannelInput()}
          {renderChannelsList()}
          {renderBottomRow()}

      </>
    ) : null
  )
}

ControlPanel.propTypes = {
  history: PropTypes.object.isRequired
}

export default hot(module)(ControlPanel)
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { inject, observer } from 'mobx-react'
import './assets/stylesheets/application.css';
import { Header, Footer, Home, Manage, stepOne, stepTwo, stepThree, stepFour, Crowdsale, Invest } from './components/index'
import IncompleteDeploy from './components/IncompleteDeploy'
import { getQueryVariable } from './utils/utils'
import {
  BrowserRouter as Router,
  Route
} from 'react-router-dom'
import AlertContainer from 'react-alert'
import { TOAST } from './utils/constants'
import { toast } from './utils/utils'

@inject('deploymentStore')
@observer
class App extends Component {
  render() {
    const { deploymentStore } = this.props
    var crowdsaleAddr = getQueryVariable("addr");

    return (
      <Router>

          {
            deploymentStore.deploymentStep !== null ? (
              <IncompleteDeploy />
            ) : (

                <Route exact path="/" component={crowdsaleAddr?Crowdsale:Home}/>
                <Route exact path="/crowdsale" component={Crowdsale}/>
                <Route exact path="/invest" component={Invest}/>
                <Route exact path="/manage/:crowdsaleAddress" component={Manage}/>
                <Route path="/1" component={stepOne}/>
                <Route path="/2" component={stepTwo}/>
                <Route path="/3" component={stepThree}/>

            )
          }

          <Route path="/4" component={stepFour}/>

          <AlertContainer ref={a => toast.msg = a} {...TOAST.DEFAULT_OPTIONS} />

      </Router>
    )
  }
}

export default App;
<commit_msg>Don't render IncompleteDeploy in step 4<commit_after>import React, { Component } from 'react';
import { inject, observer } from 'mobx-react'
import './assets/stylesheets/application.css';
import { Header, Footer, Home, Manage, stepOne, stepTwo, stepThree, stepFour, Crowdsale, Invest } from './components/index'
import IncompleteDeploy from './components/IncompleteDeploy'
import { getQueryVariable } from './utils/utils'
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom'
import AlertContainer from 'react-alert'
import { TOAST } from './utils/constants'
import { toast } from './utils/utils'

@inject('deploymentStore')
@observer
class App extends Component {
  render() {
    const { deploymentStore } = this.props
    var crowdsaleAddr = getQueryVariable("addr");

    return (
      <Router>

          <Switch>
            {/* The route to /4 must be first for the incomplete deploy redirect to work */}
            <Route path="/4" component={stepFour}/>

            {
              deploymentStore.deploymentStep !== null ? (
                <IncompleteDeploy />
              ) : (

                  <Route exact path="/" component={crowdsaleAddr ? Crowdsale : Home}/>
                  <Route exact path="/crowdsale" component={Crowdsale}/>
                  <Route exact path="/invest" component={Invest}/>
                  <Route exact path="/manage/:crowdsaleAddress" component={Manage}/>
                  <Route path="/1" component={stepOne}/>
                  <Route path="/2" component={stepTwo}/>
                  <Route path="/3" component={stepThree}/>

              )
            }
          </Switch>

          <AlertContainer ref={a => toast.msg = a} {...TOAST.DEFAULT_OPTIONS} />

      </Router>
    )
  }
}

export default App;
<|endoftext|>