<commit_before>var Deque = require("collections/deque")
var explanations = require("./explanation")

function leftTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c < -0.001;
}

function rightTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c > 0.001;
}

function dist2(p0, p1){
    var dx = p1[0] - p0[0],
        dy = p1[1] - p0[1];
    return dx*dx + dy*dy;
}

// http://stackoverflow.com/questions/9043805
function intersect(p0, p1, p2, p3) {
    var x1=p0[0], y1=p0[1], x2=p1[0], y2=p1[1],
        x3=p2[0], y3=p2[1], x4=p3[0], y4=p3[1];

    var x=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    var y=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    if (isNaN(x)||isNaN(y)) {
        return false;
    } else {
        if (x1>=x2) {
            if (!(x2<=x&&x<=x1)) {return false;}
        } else {
            if (!(x1<=x&&x<=x2)) {return false;}
        }
        if (y1>=y2) {
            if (!(y2<=y&&y<=y1)) {return false;}
        } else {
            if (!(y1<=y&&y<=y2)) {return false;}
        }
        if (x3>=x4) {
            if (!(x4<=x&&x<=x3)) {return false;}
        } else {
            if (!(x3<=x&&x<=x4)) {return false;}
        }
        if (y3>=y4) {
            if (!(y4<=y&&y<=y3)) {return false;}
        } else {
            if (!(y3<=y&&y<=y4)) {return false;}
        }
    }
    return true;
}

function intersectsAny(p0, p1){
    var ret = false;
    g_lines.selectAll(".err").remove();
    for (var i = 0; i < points.length-2; i++){
        if (intersect(p0, p1, points[i], points[i+1])){
            ret = true
            console.log("gotcha")
            var p0 = points[i], p1 = points[i+1];
            g_lines.append("line")
                .attr("x1", p0[0])
                .attr("y1", p0[1])
                .attr("x2", p1[0])
                .attr("y2", p1[1])
                .attr("class", "err")
        }
    }
    return ret;
}

// Proceeding from p0 to p1, what point on the canvas boundary do you hit?
function toBoundary(p0, p1){
    var x = p1[0], y = p1[1],
        dx = x - p0[0],
        dy = y - p0[1];
    if (x===0 || y===0) return p1;

    // left wall
    var k = -x/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0) return [0, h];

    // top wall
    var k = -y/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0) return [w, 0];

    // right wall
    var k = (width-x)/dx;
    var h = y + dy*k;
    if (k > 0 && h <= height) return [0, h];

    // top wall
    var k = (height-y)/dy;
    var w = x + dx*k;
    if (k > 0 && w <= width) return [w, 0];

    console.warn("toBoundary found unsatisfactory result for", p0, p1);
    return p1;
}

// Given two points on the boundary, find the corner where the edges meet
function corner(b0, b1){
    var x = b0[0] < 10 ? b0[0] : b1[0],
        y = b0[1] < 10 ? b0[1] : b1[1]

    return [x,y]
}

var line_gen = d3.svg.line();
function line(){
    return g_lines.select("#path_poly").datum(points).attr("d", line_gen)
}

function point(p, letter){
    var g = g_points.append("g").attr("class", "graph-vertex").translate(p)
    g.append("circle")
        .attr("r", 10)
    if (letter) g.append("text").text(letter).attr("dy", "4px")
}

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

d3.selection.prototype.translate = function(a, b) {
  return arguments.length == 1
      ? this.attr("transform", "translate(" + a + ")")
      : this.attr("transform", "translate(" + a + "," + b + ")")
};

var red = "#FF7777", blue = "#7777FF", purple = "#DA54FF", yellow = "#FFFF84";

var margin = {top: 120, right: 20, bottom: 20, left: 400},
    width = window.innerWidth - margin.left - margin.right,
    height = window.innerHeight - margin.top - margin.bottom;

var svg_deque = d3.select("#deque")
            .attr("width", width + margin.right)
            .attr("height", margin.top - 10)

var svg_polygon = d3.select("#polygon")
            .attr("width", width + margin.right)
            .attr("height", height + margin.bottom)

d3.selectAll("svg").append("rect")
    .attr({width: width-2, x: 1, y: 1, class: "bg"})
    .attr("height", function(d,i){return i ? height : margin.top - 30})

var g_deque = svg_deque.append("g");
svg_deque.selectAll(".cover").data([0,0.5]).enter().append("rect")
    .attr({class: "cover", width: (width+margin.right)/2, height: margin.top})
    .attr("x", function(d){return d*(width+margin.right)});

var g_regions = svg_polygon.append("g"),
    g_lines  = svg_polygon.append("g"),
    g_points = svg_polygon.append("g");

g_lines.append("path").attr("id", "path_poly");
g_lines.append("path").attr("id", "path_hull");

var points = [];
var deque;
var freeze = false;
var state = 0;

var text = d3.select("#text");

function first3(){
    freeze = true;
    state = 1;
    text.html(explanations.okayStop)
    var a = points[0], b = points[1], c = points[2];
}

function revealDeque(){
    state++;
    text.html(explanations.dequeIntro);
    var a = points[0], b = points[1], c = points[2];
    if (leftTurn(a,b,c)){
        deque = new Deque("cbac".split(""))
    }else{
        deque = new Deque("cabc".split(""))
    }
    var items = g_deque.selectAll(".deque-vertex")
        .data(deque.toArray())
        .enter()
        .append("g")
        .attr("transform", function(d,i){return "translate(" + (i*60) + ","+ (margin.top / 2 - 25)+")"})
        .attr("class", "deque-vertex");
    items.append("rect")
        .attr({width: "40px", height: "40px", rx: "8px", ry: "8px"})
    items.append("text")
        .translate(20,20)
        .attr("dy", "5px")
        .text(function(d){ return d});
    svg_deque.selectAll(".cover").transition()
        .duration(1000)
        .attr("x", function(d,i){
            return i ? width+margin.right+10 : -(width+margin.right)/2 -10;
        })
        .remove();
}

function pointC(){
    state++;
    var a = points[0], b = points[1], c = points[2];
    var wasLeftTurn = leftTurn(a,b,c);
    text.html(explanations.pointC(wasLeftTurn));
    svg_polygon.selectAll(".graph-vertex circle")
        .transition()
        .duration(2000)
        .style("fill", function(d,i){
                        if (i == 2) { return purple; }
        })
        .transition()
        .delay(3000)
        .style("fill", function(d,i){
                        if (i == 2) { return purple; }
                        return (wasLeftTurn ^ i) ? blue : red;
        });

    svg_deque.selectAll(".deque-vertex rect")
        .transition()
        .duration(2000)
        .style("fill", function(d,i){
                        if (i == 0 || i == 3) { return purple }
        })
        .transition()
        .delay(3000)
        .style("fill", function(d,i){
                        if (i == 0 || i == 3) { return purple }
                        if (i == 1) { return red }
                        if (i == 2) { return blue }
        });

}

function yellowRegion(){
    state++;
    text.html(explanations.yellowRegion);
    g_regions.append("path")
        .datum(points)
        .attr("d", line_gen)
        .style("fill", yellow)
}

function rbpRegions(){
    state++;
    text.html(explanations.rbpRegions);
    var a = points[0], b = points[1], c = points[2];
    var wasLeftTurn = leftTurn(a,b,c);
    if (wasLeftTurn){
        console.log("It was a left turn")
        var b0 = toBoundary(points[2], points[0]),
            b1 = toBoundary(points[1], points[2]),
            outline = [b0, points[2], b1, corner(b0, b1)];
        console.log(outline)
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .style("fill",blue)
            .style("stroke", "none")
    }
}

svg_polygon.on("click", function(){
    if (freeze) return;
    var pos = d3.mouse(svg_polygon.node());
    if (points.length > 0){
        var prev = points[points.length-1]
        if (intersectsAny(prev, pos)){
            return;
        }
    }
    point(pos, alphabet[points.length]);
    points.push(pos);
    line();
    if (points.length === 3) first3();
})

d3.select("body").on("keydown", function(){
    if (d3.event.keyCode == 32){
        switch (state){
            case 1:
                revealDeque();
            break;
            case 2:
                pointC();
            break;
            case 3:
                rbpRegions();
            break;
            case 4:
                yellowRegion();
            break;
            default:
        }
    }
})
<commit_msg>Improve geometry to actually work in a some cases.<commit_after>var Deque = require("collections/deque")
var explanations = require("./explanation")

function leftTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c < -0.001;
}

function rightTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c > 0.001;
}

function dist2(p0, p1){
    var dx = p1[0] - p0[0],
        dy = p1[1] - p0[1];
    return dx*dx + dy*dy;
}

// http://stackoverflow.com/questions/9043805
function intersect(p0, p1, p2, p3) {
    var x1=p0[0], y1=p0[1], x2=p1[0], y2=p1[1],
        x3=p2[0], y3=p2[1], x4=p3[0], y4=p3[1];

    var x=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    var y=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    if (isNaN(x)||isNaN(y)) {
        return false;
    } else {
        if (x1>=x2) {
            if (!(x2<=x&&x<=x1)) {return false;}
        } else {
            if (!(x1<=x&&x<=x2)) {return false;}
        }
        if (y1>=y2) {
            if (!(y2<=y&&y<=y1)) {return false;}
        } else {
            if (!(y1<=y&&y<=y2)) {return false;}
        }
        if (x3>=x4) {
            if (!(x4<=x&&x<=x3)) {return false;}
        } else {
            if (!(x3<=x&&x<=x4)) {return false;}
        }
        if (y3>=y4) {
            if (!(y4<=y&&y<=y3)) {return false;}
        } else {
            if (!(y3<=y&&y<=y4)) {return false;}
        }
    }
    return true;
}

function intersectsAny(p0, p1){
    var ret = false;
    g_lines.selectAll(".err").remove();
    for (var i = 0; i < points.length-2; i++){
        if (intersect(p0, p1, points[i], points[i+1])){
            ret = true
            console.log("gotcha")
            var p0 = points[i], p1 = points[i+1];
            g_lines.append("line")
                .attr("x1", p0[0])
                .attr("y1", p0[1])
                .attr("x2", p1[0])
                .attr("y2", p1[1])
                .attr("class", "err")
        }
    }
    return ret;
}

// Proceeding from p0 to p1, what point on the canvas boundary do you hit?
function toBoundary(p0, p1){
    var x = p1[0], y = p1[1],
        dx = x - p0[0],
        dy = y - p0[1];
    if (x===0 || y===0) return p1;

    // left wall
    var k = -x/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0) return [0, h];

    // top wall
    var k = -y/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0) return [w, 0];

    // right wall
    var k = (width-x)/dx;
    var h = y + dy*k;
    if (k > 0 && h <= height) return [width, h];

    // top wall
    var k = (height-y)/dy;
    var w = x + dx*k;
    if (k > 0 && w <= width) return [w, height];

    console.warn("toBoundary found unsatisfactory result for", p0, p1);
    return p1;
}

// Given two points on adjacent boundary edges, find the corner where the edges meet
// TODO return an array of points to accomodate b0 = top b1 = bottom
function corner(b0, b1){
    // And this is _after_ refactoring...
    var top = 0, right = 1, bottom = 2, left = 3;
    var s0, s1;

    if (b0[0]===0){
        s0 = left;
    }else if (b0[0]===width){
        s0 = right;
    }else if (b0[1]===0){
        s0 = top;
    }else if (b0[1]===height){
        s0 = bottom;
    }
    if (b1[0]===0){
        s1 = left;
    }else if (b1[0]===width){
        s1 = right;
    }else if (b1[1]===0){
        s1 = top;
    }else if (b1[1]===height){
        s1 = bottom;
    }
    console.log(s0, s1)

    var s2 = Math.min(s0, s1),
        s3 = Math.max(s0, s1)

    if (s2===top && s3==right){
        return [width, 0]
    }else if (s2==right && s3===bottom){
        return [width, height]
    }else if (s2==bottom && s3===left){
        return [0, height]
    }else if (s2==top && s3===left){
        return [0, 0]
    }

    console.warn("toBoundary found unsatisfactory result for", b0, b1);
    return [0,0]
}

var line_gen = d3.svg.line();
function line(){
    return g_lines.select("#path_poly").datum(points).attr("d", line_gen)
}

function point(p, letter){
    var g = g_points.append("g").attr("class", "graph-vertex").translate(p)
    g.append("circle")
        .attr("r", 10)
    if (letter) g.append("text").text(letter).attr("dy", "4px")
}

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

d3.selection.prototype.translate = function(a, b) {
  return arguments.length == 1
      ? this.attr("transform", "translate(" + a + ")")
      : this.attr("transform", "translate(" + a + "," + b + ")")
};

var red = "#FF7777", blue = "#7777FF", purple = "#DA54FF", yellow = "#FFFF84";

var margin = {top: 120, right: 20, bottom: 20, left: 400},
    width = window.innerWidth - margin.left - margin.right,
    height = window.innerHeight - margin.top - margin.bottom;

console.log("width", width, "height", height);

var svg_deque = d3.select("#deque")
            .attr("width", width + margin.right)
            .attr("height", margin.top - 10)

var svg_polygon = d3.select("#polygon")
            .attr("width", width + margin.right)
            .attr("height", height + margin.bottom)

d3.selectAll("svg").append("rect")
    .attr({width: width-2, x: 1, y: 1, class: "bg"})
    .attr("height", function(d,i){return i ? height : margin.top - 30})

var g_deque = svg_deque.append("g");
svg_deque.selectAll(".cover").data([0,0.5]).enter().append("rect")
    .attr({class: "cover", width: (width+margin.right)/2, height: margin.top})
    .attr("x", function(d){return d*(width+margin.right)});

var g_regions = svg_polygon.append("g"),
    g_lines  = svg_polygon.append("g"),
    g_points = svg_polygon.append("g");

g_lines.append("path").attr("id", "path_poly");
g_lines.append("path").attr("id", "path_hull");

var points = [];
var deque;
var freeze = false;
var state = 0;

var text = d3.select("#text");

function first3(){
    freeze = true;
    state = 1;
    text.html(explanations.okayStop)
    var a = points[0], b = points[1], c = points[2];
}

function revealDeque(){
    state++;
    text.html(explanations.dequeIntro);
    var a = points[0], b = points[1], c = points[2];
    if (leftTurn(a,b,c)){
        deque = new Deque("cbac".split(""))
    }else{
        deque = new Deque("cabc".split(""))
    }
    var items = g_deque.selectAll(".deque-vertex")
        .data(deque.toArray())
        .enter()
        .append("g")
        .attr("transform", function(d,i){return "translate(" + (i*60) + ","+ (margin.top / 2 - 25)+")"})
        .attr("class", "deque-vertex");
    items.append("rect")
        .attr({width: "40px", height: "40px", rx: "8px", ry: "8px"})
    items.append("text")
        .translate(20,20)
        .attr("dy", "5px")
        .text(function(d){ return d});
    svg_deque.selectAll(".cover").transition()
        .duration(1000)
        .attr("x", function(d,i){
            return i ? width+margin.right+10 : -(width+margin.right)/2 -10;
        })
        .remove();
}

function pointC(){
    state++;
    var a = points[0], b = points[1], c = points[2];
    var wasLeftTurn = leftTurn(a,b,c);
    text.html(explanations.pointC(wasLeftTurn));
    svg_polygon.selectAll(".graph-vertex circle")
        .transition()
        .duration(2000)
        .style("fill", function(d,i){
                        if (i == 2) { return purple; }
        })
        .transition()
        .delay(3000)
        .style("fill", function(d,i){
                        if (i == 2) { return purple; }
                        return (wasLeftTurn ^ i) ? blue : red;
        });

    svg_deque.selectAll(".deque-vertex rect")
        .transition()
        .duration(2000)
        .style("fill", function(d,i){
                        if (i == 0 || i == 3) { return purple }
        })
        .transition()
        .delay(3000)
        .style("fill", function(d,i){
                        if (i == 0 || i == 3) { return purple }
                        if (i == 1) { return red }
                        if (i == 2) { return blue }
        });

}

function yellowRegion(){
    state++;
    text.html(explanations.yellowRegion);
    g_regions.append("path")
        .datum(points)
        .attr("d", line_gen)
        .style("fill", yellow)
}

function rbpRegions(){
    state++;
    text.html(explanations.rbpRegions);
    var a = points[0], b = points[1], c = points[2];
    var wasLeftTurn = leftTurn(a,b,c);
    if (wasLeftTurn){
        console.log("It was a left turn")
        var b0 = toBoundary(points[2], points[0]),
            b1 = toBoundary(points[1], points[2]),
            outline = [b0, points[2], b1, corner(b0, b1)];
        console.log(outline)
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .style("fill",blue)
            .style("stroke", "none")

            b0 = toBoundary(points[2], points[1]),
            b1 = toBoundary(points[0], points[2]),
            outline = [b0, points[2], b1, corner(b0, b1)]
        console.log(outline)
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .style("fill", red)
            .style("stroke", "none")

            b0 = toBoundary(points[1], points[2]),
            b1 = toBoundary(points[0], points[2]),
            outline = [b0, points[2], b1]
        console.log(outline)
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .style("fill", purple)
            .style("stroke", "none")
    }
}

svg_polygon.on("click", function(){
    if (freeze) return;
    var pos = d3.mouse(svg_polygon.node());
    if (points.length > 0){
        var prev = points[points.length-1]
        if (intersectsAny(prev, pos)){
            return;
        }
    }
    point(pos, alphabet[points.length]);
    points.push(pos);
    line();
    if (points.length === 3) first3();
})

d3.select("body").on("keydown", function(){
    if (d3.event.keyCode == 32){
        switch (state){
            case 1:
                revealDeque();
            break;
            case 2:
                pointC();
            break;
            case 3:
                rbpRegions();
            break;
            case 4:
                yellowRegion();
            break;
            default:
        }
    }
})
<|endoftext|>


<commit_before>
const dotenv = require('dotenv');
const discord = require('discord.js');
const idletcg = require('./idletcg/idletcg.js');
const config = require('./config.js').BooConfig;
const tcgdata = require('./idletcg/' + config.cardData);
const tcgplayeractor = require('./idletcg/tcgplayeractor.js');
const random = require('./lib/random.js');
const time = require('./lib/time.js');

const client = new discord.Client();
const game = new idletcg.IdleTcg();
const commandPrefix = config.commandPrefix;
const scanTime = config.scanTime;

client.on('ready', () => {
	game.load();
	client.user.setGame("idletcg: .help");
	console.log('boo: running');
	setInterval(BooSchedule.updateIdleTcg, scanTime * 1000);
});

client.on('message', message => {
	if (message.channel.name !== config.channel ||
		message.content[0] !== commandPrefix) {
		return;
	}
	let command = message.content.substring(1);

	if (command in BooCommands) {
		BooCommands[command](message);
	}
});

class BooSchedule {

	static updateIdleTcg() {
		client.users.forEach(function(user, id, _) {
			if (!game.hasPlayer(id)) return;
			let player = game.getPlayer(id);
			let playerActor = new tcgplayeractor.TcgPlayerActor(player);

			playerActor.rewardMoney();
			playerActor.rewardIdlePoints();

			let boosterDescript = tcgdata.boosterDescript.charAt(0).toUpperCase() +
				tcgdata.boosterDescript.slice(1);

			let boosterEarned = playerActor.rewardBooster();
			if (boosterEarned) {
				let idleStr = user + ' idled for ';
				let idleTime = time.secondsToHumanTime(player.idle) + ' and found item `';
				let desc = boosterDescript + ': ' + tcgdata.boosters[player.booster] + '`';
				let help = ' ' + config.commandPrefix + 'unpack to open';

				let channel = client.channels.find('name', config.channel);
				channel.send(config.messagePrefix + idleStr + idleTime + desc + help);
			}

			player.idle += scanTime;
			player.username = user.username;
		});

		game.save();
	}

}

class BooCommands {

	static help(context) {
		let embed = new discord.RichEmbed()
			.setAuthor('About boo')
			.setColor(config.embedColour)
			.setDescription('Boo is an idlerpg style trading card game. ' +
				'Stay connected and earn cards, try to collect them all!')
			.addField('Cards', '🗃 There are currently ' + game.cards +
				' cards in circulation')
			.addField('Commands', '```css\n.join\n.unpack\n.profile```')
			.addField('Source code', '🦄 https://github.com/xxami/boo')
			.setFooter('Created by『ｌｉｌａｈ』🎀')
			.setThumbnail(config.embedThumbnail);

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

	static join(context) {
		let user = context.author;
		if (game.hasPlayer(user.id)) {
			context.reply('you\'re already a registered player');
		}
		else {
			game.addPlayer(user.id, user.username);
			game.save();
			context.reply('you\'ve been added to the game; good luck!');
		}
	}

	static unpack(context) {
		let user = context.author;
		if (!game.hasPlayer(user.id)) {
			context.reply('sorry; you\'re not a registered player, see .help!');
			return;
		}

		let player = game.getPlayer(user.id);
		if (player.booster === false) {
			context.reply('sorry; you don\'t have anything to unpack!');
			return;
		}

		let boosterName = tcgdata.boosters[player.booster];
		let playerActor =  new tcgplayeractor.TcgPlayerActor(player);
		let card = playerActor.openBooster();
		let cards =  Object.keys(player.cards).length;
		if (tcgdata.titles[cards] !== undefined) {
			player.title = tcgdata.titles[cards];
		}

		let colour = config.embedColour;
		if (player['colour'] !== undefined) {
			colour = player['colour'];
		}

		let pronoun = 'their';
		if (player['gender'] !== undefined) {
			if (player['gender'] === 'm') {
				pronoun = 'his';
			}
			else {
				pronoun = 'her';
			}
		}

		let avatarURL = config.embedThumbnail;
		if (config.useAvatarsInDropAlerts === true) {
			if (user.avatarURL !== null) {
				avatarURL = user.avatarURL;
			}
		}

		let boosterDescript = tcgdata.boosterDescript.charAt(0).toUpperCase() +
			tcgdata.boosterDescript.slice(1);

		let embed = new discord.RichEmbed()
			.setAuthor(boosterDescript + ': ' + boosterName)
			.setColor(colour)
			.setDescription(user + ' opened ' + pronoun +
				' ' + tcgdata.boosterDescript +
				' and received card `' + card.id +
				'/' + game.cards + '`')
			.addField('#' + card.id + ': ' + card.text, card.description)
			.setThumbnail(avatarURL)
			.attachFile(card.img);

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

	static profile(context) {
		let user = context.author;
		if (!game.hasPlayer(user.id)) {
			context.reply('sorry; you\'re not a registered player, see .help!');
			return;
		}

		let player = game.getPlayer(user.id);

		let colour = config.embedColour;
		if (player['colour'] !== undefined) {
			colour = player['colour'];
		}

		let pronoun = 'their';
		if (player['gender'] !== undefined) {
			if (player['gender'] === 'm') {
				pronoun = 'his';
			}
			else {
				pronoun = 'her';
			}
		}

		let avatarURL = config.embedThumbnail;
		if (config.useAvatarsInDropAlerts === true) {
			if (user.avatarURL !== null) {
				avatarURL = user.avatarURL;
			}
		}

		let cards = Object.keys(player.cards).length;
		let completion = Math.round(((cards * 100) / game.cards) * 10) / 10;

		let embed = new discord.RichEmbed()
			.setAuthor(user.username)
			.setColor(colour)
			.setDescription('`' + player.title + '`')
			.addField('Cards', '🗃 ' + cards + '/' + game.cards +
				' cards found `' + completion + '% complete`')
			.addField('Idle time', '⏰ ' + time.secondsToHumanTime(player.idle))
			.addField('Piggy bank', '💴 ¥' + player.money)
			.setThumbnail(avatarURL);

		if (player.booster !== false) {
			embed.setFooter(user.username + ' hasn\'t opened ' + pronoun +
				' last ' + tcgdata.boosterDescript)
		}

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

}

dotenv.config({path: 'boo.env'});
client.login(process.env.BOO_TOKEN);
<commit_msg>Add .u alias for unpacking.<commit_after>
const dotenv = require('dotenv');
const discord = require('discord.js');
const idletcg = require('./idletcg/idletcg.js');
const config = require('./config.js').BooConfig;
const tcgdata = require('./idletcg/' + config.cardData);
const tcgplayeractor = require('./idletcg/tcgplayeractor.js');
const random = require('./lib/random.js');
const time = require('./lib/time.js');

const client = new discord.Client();
const game = new idletcg.IdleTcg();
const commandPrefix = config.commandPrefix;
const scanTime = config.scanTime;

client.on('ready', () => {
	game.load();
	client.user.setGame("idletcg: .help");
	console.log('boo: running');
	setInterval(BooSchedule.updateIdleTcg, scanTime * 1000);
});

client.on('message', message => {
	if (message.channel.name !== config.channel ||
		message.content[0] !== commandPrefix) {
		return;
	}
	let command = message.content.substring(1);

	if (command in BooCommands) {
		BooCommands[command](message);
	} else if (command in BooAliases) {
		BooAliases[command](message);
	}
});

class BooSchedule {

	static updateIdleTcg() {
		client.users.forEach(function(user, id, _) {
			if (!game.hasPlayer(id)) return;
			let player = game.getPlayer(id);
			let playerActor = new tcgplayeractor.TcgPlayerActor(player);

			playerActor.rewardMoney();
			playerActor.rewardIdlePoints();

			let boosterDescript = tcgdata.boosterDescript.charAt(0).toUpperCase() +
				tcgdata.boosterDescript.slice(1);

			let boosterEarned = playerActor.rewardBooster();
			if (boosterEarned) {
				let idleStr = user + ' idled for ';
				let idleTime = time.secondsToHumanTime(player.idle) + ' and found item `';
				let desc = boosterDescript + ': ' + tcgdata.boosters[player.booster] + '`';
				let help = ' ' + config.commandPrefix + 'unpack to open';

				let channel = client.channels.find('name', config.channel);
				channel.send(config.messagePrefix + idleStr + idleTime + desc + help);
			}

			player.idle += scanTime;
			player.username = user.username;
		});

		game.save();
	}

}

class BooCommands {

	static help(context) {
		let embed = new discord.RichEmbed()
			.setAuthor('About boo')
			.setColor(config.embedColour)
			.setDescription('Boo is an idlerpg style trading card game. ' +
				'Stay connected and earn cards, try to collect them all!')
			.addField('Cards', '🗃 There are currently ' + game.cards +
				' cards in circulation')
			.addField('Commands', '```css\n.join\n.unpack\n.profile```')
			.addField('Source code', '🦄 https://github.com/xxami/boo')
			.setFooter('Created by『ｌｉｌａｈ』🎀')
			.setThumbnail(config.embedThumbnail);

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

	static join(context) {
		let user = context.author;
		if (game.hasPlayer(user.id)) {
			context.reply('you\'re already a registered player');
		}
		else {
			game.addPlayer(user.id, user.username);
			game.save();
			context.reply('you\'ve been added to the game; good luck!');
		}
	}

	static unpack(context) {
		let user = context.author;
		if (!game.hasPlayer(user.id)) {
			context.reply('sorry; you\'re not a registered player, see .help!');
			return;
		}

		let player = game.getPlayer(user.id);
		if (player.booster === false) {
			context.reply('sorry; you don\'t have anything to unpack!');
			return;
		}

		let boosterName = tcgdata.boosters[player.booster];
		let playerActor =  new tcgplayeractor.TcgPlayerActor(player);
		let card = playerActor.openBooster();
		let cards =  Object.keys(player.cards).length;
		if (tcgdata.titles[cards] !== undefined) {
			player.title = tcgdata.titles[cards];
		}

		let colour = config.embedColour;
		if (player['colour'] !== undefined) {
			colour = player['colour'];
		}

		let pronoun = 'their';
		if (player['gender'] !== undefined) {
			if (player['gender'] === 'm') {
				pronoun = 'his';
			}
			else {
				pronoun = 'her';
			}
		}

		let avatarURL = config.embedThumbnail;
		if (config.useAvatarsInDropAlerts === true) {
			if (user.avatarURL !== null) {
				avatarURL = user.avatarURL;
			}
		}

		let boosterDescript = tcgdata.boosterDescript.charAt(0).toUpperCase() +
			tcgdata.boosterDescript.slice(1);

		let embed = new discord.RichEmbed()
			.setAuthor(boosterDescript + ': ' + boosterName)
			.setColor(colour)
			.setDescription(user + ' opened ' + pronoun +
				' ' + tcgdata.boosterDescript +
				' and received card `' + card.id +
				'/' + game.cards + '`')
			.addField('#' + card.id + ': ' + card.text, card.description)
			.setThumbnail(avatarURL)
			.attachFile(card.img);

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

	static profile(context) {
		let user = context.author;
		if (!game.hasPlayer(user.id)) {
			context.reply('sorry; you\'re not a registered player, see .help!');
			return;
		}

		let player = game.getPlayer(user.id);

		let colour = config.embedColour;
		if (player['colour'] !== undefined) {
			colour = player['colour'];
		}

		let pronoun = 'their';
		if (player['gender'] !== undefined) {
			if (player['gender'] === 'm') {
				pronoun = 'his';
			}
			else {
				pronoun = 'her';
			}
		}

		let avatarURL = config.embedThumbnail;
		if (config.useAvatarsInDropAlerts === true) {
			if (user.avatarURL !== null) {
				avatarURL = user.avatarURL;
			}
		}

		let cards = Object.keys(player.cards).length;
		let completion = Math.round(((cards * 100) / game.cards) * 10) / 10;

		let embed = new discord.RichEmbed()
			.setAuthor(user.username)
			.setColor(colour)
			.setDescription('`' + player.title + '`')
			.addField('Cards', '🗃 ' + cards + '/' + game.cards +
				' cards found `' + completion + '% complete`')
			.addField('Idle time', '⏰ ' + time.secondsToHumanTime(player.idle))
			.addField('Piggy bank', '💴 ¥' + player.money)
			.setThumbnail(avatarURL);

		if (player.booster !== false) {
			embed.setFooter(user.username + ' hasn\'t opened ' + pronoun +
				' last ' + tcgdata.boosterDescript)
		}

		let channel = client.channels.find('name', config.channel);
		channel.send({embed});
	}

}

BooAliases = {

	u: BooCommands.unpack

}

dotenv.config({path: 'boo.env'});
client.login(process.env.BOO_TOKEN);
<|endoftext|>


<commit_before>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {
  ElementID,
  Element,
  ElementSearchResultSet,
  Store,
  PluginClient,
} from 'flipper';

import {
  FlexColumn,
  FlexRow,
  FlipperPlugin,
  Toolbar,
  Sidebar,
  Link,
  Glyph,
  DetailSidebar,
  styled,
} from 'flipper';
import Inspector from './Inspector';
import ToolbarIcon from './ToolbarIcon';
import InspectorSidebar from './InspectorSidebar';
import Search from './Search';
import ProxyArchiveClient from './ProxyArchiveClient';

type State = {|
  init: boolean,
  inTargetMode: boolean,
  inAXMode: boolean,
  inAlignmentMode: boolean,
  selectedElement: ?ElementID,
  selectedAXElement: ?ElementID,
  searchResults: ?ElementSearchResultSet,
|};

export type ElementMap = {[key: ElementID]: Element};

export type PersistedState = {|
  rootElement: ?ElementID,
  rootAXElement: ?ElementID,
  elements: ElementMap,
  AXelements: ElementMap,
|};

const BetaBar = styled(Toolbar)({
  display: 'block',
  overflow: 'hidden',
  lineHeight: '15px',
  whiteSpace: 'nowrap',
  textOverflow: 'ellipsis',
});

export default class Layout extends FlipperPlugin<State, void, PersistedState> {
  static exportPersistedState = (
    callClient: (string, ?Object) => Promise<Object>,
    persistedState: ?PersistedState,
    store: ?Store,
  ): Promise<?PersistedState> => {
    const defaultPromise = Promise.resolve(persistedState);
    if (!store) {
      return defaultPromise;
    }
    return callClient('getAllNodes').then(({allNodes}) => allNodes);
  };

  static defaultPersistedState = {
    rootElement: null,
    rootAXElement: null,
    elements: {},
    AXelements: {},
  };

  state = {
    init: false,
    inTargetMode: false,
    inAXMode: false,
    inAlignmentMode: false,
    selectedElement: null,
    selectedAXElement: null,
    searchResults: null,
  };

  init() {
    if (!this.props.persistedState) {
      // If the selected plugin from the previous session was layout, then while importing the flipper trace, the redux store doesn't get updated in the first render, due to which the plugin crashes, as it has no persisted state
      this.props.setPersistedState(this.constructor.defaultPersistedState);
    }
    // persist searchActive state when moving between plugins to prevent multiple
    // TouchOverlayViews since we can't edit the view heirarchy in onDisconnect
    this.client.call('isSearchActive').then(({isSearchActive}) => {
      this.setState({inTargetMode: isSearchActive});
    });

    // disable target mode after
    this.client.subscribe('select', () => {
      if (this.state.inTargetMode) {
        this.onToggleTargetMode();
      }
    });

    this.setState({init: true});
  }

  onToggleTargetMode = () => {
    const inTargetMode = !this.state.inTargetMode;
    this.setState({inTargetMode});
    this.client.send('setSearchActive', {active: inTargetMode});
  };

  onToggleAXMode = () => {
    this.setState({inAXMode: !this.state.inAXMode});
  };

  getClient(): PluginClient {
    return this.props.isArchivedDevice
      ? new ProxyArchiveClient(this.props.persistedState)
      : this.client;
  }
  onToggleAlignmentMode = () => {
    if (this.state.selectedElement) {
      this.client.send('setHighlighted', {
        id: this.state.selectedElement,
        inAlignmentMode: !this.state.inAlignmentMode,
      });
    }
    this.setState({inAlignmentMode: !this.state.inAlignmentMode});
  };

  onDataValueChanged = (path: Array<string>, value: any) => {
    const id = this.state.inAXMode
      ? this.state.selectedAXElement
      : this.state.selectedElement;
    this.client.call('setData', {
      id,
      path,
      value,
      ax: this.state.inAXMode,
    });
  };

  render() {
    const inspectorProps = {
      client: this.getClient(),
      inAlignmentMode: this.state.inAlignmentMode,
      selectedElement: this.state.selectedElement,
      selectedAXElement: this.state.selectedAXElement,
      setPersistedState: this.props.setPersistedState,
      persistedState: this.props.persistedState,
      onDataValueChanged: this.onDataValueChanged,
      searchResults: this.state.searchResults,
    };

    let element;
    if (this.state.inAXMode && this.state.selectedAXElement) {
      element = this.props.persistedState.AXelements[
        this.state.selectedAXElement
      ];
    } else if (this.state.selectedElement) {
      element = this.props.persistedState.elements[this.state.selectedElement];
    }

    const inspector = (
      <Inspector
        {...inspectorProps}
        onSelect={selectedElement => this.setState({selectedElement})}
        showsSidebar={!this.state.inAXMode}
      />
    );

    return (
      <FlexColumn grow={true}>
        {this.state.init && (
          <>
            <Toolbar>
              <ToolbarIcon
                onClick={this.onToggleTargetMode}
                title="Toggle target mode"
                icon="target"
                active={this.state.inTargetMode}
              />
              {this.realClient.query.os === 'Android' && (
                <ToolbarIcon
                  onClick={this.onToggleAXMode}
                  title="Toggle to see the accessibility hierarchy"
                  icon="accessibility"
                  active={this.state.inAXMode}
                />
              )}
              <ToolbarIcon
                onClick={this.onToggleAlignmentMode}
                title="Toggle AlignmentMode to show alignment lines"
                icon="borders"
                active={this.state.inAlignmentMode}
              />
              <Search
                client={this.getClient()}
                setPersistedState={this.props.setPersistedState}
                persistedState={this.props.persistedState}
                onSearchResults={searchResults =>
                  this.setState({searchResults})
                }
                inAXMode={this.state.inAXMode}
              />
            </Toolbar>

            <FlexRow grow={true}>
              {this.state.inAXMode ? (
                <>
                  <Sidebar position="left" maxWidth={Infinity}>
                    {inspector}
                  </Sidebar>
                  <Inspector
                    {...inspectorProps}
                    onSelect={selectedAXElement =>
                      this.setState({selectedAXElement})
                    }
                    showsSidebar={true}
                    ax
                  />
                </>
              ) : (
                inspector
              )}
            </FlexRow>
            <DetailSidebar>
              <InspectorSidebar
                client={this.getClient()}
                realClient={this.realClient}
                element={element}
                onValueChanged={this.onDataValueChanged}
                logger={this.props.logger}
              />
            </DetailSidebar>
          </>
        )}
        {/* TODO: Remove this when rolling out publicly */}
        <BetaBar position="bottom" compact>
          <Glyph name="beta" color="#8157C7" />
          Version 2.0:  Provide feedback about this plugin
          in our
          <Link href="https://fb.workplace.com/groups/246035322947653/">
            feedback group
          </Link>.
        </BetaBar>
      </FlexColumn>
    );
  }
}
<commit_msg>Hide target and alignment mode for archived device<commit_after>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {
  ElementID,
  Element,
  ElementSearchResultSet,
  Store,
  PluginClient,
} from 'flipper';

import {
  FlexColumn,
  FlexRow,
  FlipperPlugin,
  Toolbar,
  Sidebar,
  Link,
  Glyph,
  DetailSidebar,
  styled,
} from 'flipper';
import Inspector from './Inspector';
import ToolbarIcon from './ToolbarIcon';
import InspectorSidebar from './InspectorSidebar';
import Search from './Search';
import ProxyArchiveClient from './ProxyArchiveClient';

type State = {|
  init: boolean,
  inTargetMode: boolean,
  inAXMode: boolean,
  inAlignmentMode: boolean,
  selectedElement: ?ElementID,
  selectedAXElement: ?ElementID,
  searchResults: ?ElementSearchResultSet,
|};

export type ElementMap = {[key: ElementID]: Element};

export type PersistedState = {|
  rootElement: ?ElementID,
  rootAXElement: ?ElementID,
  elements: ElementMap,
  AXelements: ElementMap,
|};

const BetaBar = styled(Toolbar)({
  display: 'block',
  overflow: 'hidden',
  lineHeight: '15px',
  whiteSpace: 'nowrap',
  textOverflow: 'ellipsis',
});

export default class Layout extends FlipperPlugin<State, void, PersistedState> {
  static exportPersistedState = (
    callClient: (string, ?Object) => Promise<Object>,
    persistedState: ?PersistedState,
    store: ?Store,
  ): Promise<?PersistedState> => {
    const defaultPromise = Promise.resolve(persistedState);
    if (!store) {
      return defaultPromise;
    }
    return callClient('getAllNodes').then(({allNodes}) => allNodes);
  };

  static defaultPersistedState = {
    rootElement: null,
    rootAXElement: null,
    elements: {},
    AXelements: {},
  };

  state = {
    init: false,
    inTargetMode: false,
    inAXMode: false,
    inAlignmentMode: false,
    selectedElement: null,
    selectedAXElement: null,
    searchResults: null,
  };

  init() {
    if (!this.props.persistedState) {
      // If the selected plugin from the previous session was layout, then while importing the flipper trace, the redux store doesn't get updated in the first render, due to which the plugin crashes, as it has no persisted state
      this.props.setPersistedState(this.constructor.defaultPersistedState);
    }
    // persist searchActive state when moving between plugins to prevent multiple
    // TouchOverlayViews since we can't edit the view heirarchy in onDisconnect
    this.client.call('isSearchActive').then(({isSearchActive}) => {
      this.setState({inTargetMode: isSearchActive});
    });

    // disable target mode after
    this.client.subscribe('select', () => {
      if (this.state.inTargetMode) {
        this.onToggleTargetMode();
      }
    });

    this.setState({init: true});
  }

  onToggleTargetMode = () => {
    const inTargetMode = !this.state.inTargetMode;
    this.setState({inTargetMode});
    this.client.send('setSearchActive', {active: inTargetMode});
  };

  onToggleAXMode = () => {
    this.setState({inAXMode: !this.state.inAXMode});
  };

  getClient(): PluginClient {
    return this.props.isArchivedDevice
      ? new ProxyArchiveClient(this.props.persistedState)
      : this.client;
  }
  onToggleAlignmentMode = () => {
    if (this.state.selectedElement) {
      this.client.send('setHighlighted', {
        id: this.state.selectedElement,
        inAlignmentMode: !this.state.inAlignmentMode,
      });
    }
    this.setState({inAlignmentMode: !this.state.inAlignmentMode});
  };

  onDataValueChanged = (path: Array<string>, value: any) => {
    const id = this.state.inAXMode
      ? this.state.selectedAXElement
      : this.state.selectedElement;
    this.client.call('setData', {
      id,
      path,
      value,
      ax: this.state.inAXMode,
    });
  };

  render() {
    const inspectorProps = {
      client: this.getClient(),
      inAlignmentMode: this.state.inAlignmentMode,
      selectedElement: this.state.selectedElement,
      selectedAXElement: this.state.selectedAXElement,
      setPersistedState: this.props.setPersistedState,
      persistedState: this.props.persistedState,
      onDataValueChanged: this.onDataValueChanged,
      searchResults: this.state.searchResults,
    };

    let element;
    if (this.state.inAXMode && this.state.selectedAXElement) {
      element = this.props.persistedState.AXelements[
        this.state.selectedAXElement
      ];
    } else if (this.state.selectedElement) {
      element = this.props.persistedState.elements[this.state.selectedElement];
    }

    const inspector = (
      <Inspector
        {...inspectorProps}
        onSelect={selectedElement => this.setState({selectedElement})}
        showsSidebar={!this.state.inAXMode}
      />
    );

    return (
      <FlexColumn grow={true}>
        {this.state.init && (
          <>
            <Toolbar>
              {!this.props.isArchivedDevice && (
                <ToolbarIcon
                  onClick={this.onToggleTargetMode}
                  title="Toggle target mode"
                  icon="target"
                  active={this.state.inTargetMode}
                />
              )}
              {this.realClient.query.os === 'Android' && (
                <ToolbarIcon
                  onClick={this.onToggleAXMode}
                  title="Toggle to see the accessibility hierarchy"
                  icon="accessibility"
                  active={this.state.inAXMode}
                />
              )}
              {!this.props.isArchivedDevice && (
                <ToolbarIcon
                  onClick={this.onToggleAlignmentMode}
                  title="Toggle AlignmentMode to show alignment lines"
                  icon="borders"
                  active={this.state.inAlignmentMode}
                />
              )}

              <Search
                client={this.getClient()}
                setPersistedState={this.props.setPersistedState}
                persistedState={this.props.persistedState}
                onSearchResults={searchResults =>
                  this.setState({searchResults})
                }
                inAXMode={this.state.inAXMode}
              />
            </Toolbar>

            <FlexRow grow={true}>
              {this.state.inAXMode ? (
                <>
                  <Sidebar position="left" maxWidth={Infinity}>
                    {inspector}
                  </Sidebar>
                  <Inspector
                    {...inspectorProps}
                    onSelect={selectedAXElement =>
                      this.setState({selectedAXElement})
                    }
                    showsSidebar={true}
                    ax
                  />
                </>
              ) : (
                inspector
              )}
            </FlexRow>
            <DetailSidebar>
              <InspectorSidebar
                client={this.getClient()}
                realClient={this.realClient}
                element={element}
                onValueChanged={this.onDataValueChanged}
                logger={this.props.logger}
              />
            </DetailSidebar>
          </>
        )}
        {/* TODO: Remove this when rolling out publicly */}
        <BetaBar position="bottom" compact>
          <Glyph name="beta" color="#8157C7" />
          Version 2.0:  Provide feedback about this plugin
          in our
          <Link href="https://fb.workplace.com/groups/246035322947653/">
            feedback group
          </Link>.
        </BetaBar>
      </FlexColumn>
    );
  }
}
<|endoftext|>


<commit_before>
<commit_msg>Update colorsGrid.js<commit_after>function colorGrid (selector, options) {

    /**
     * For boolean value
    */
    function isNotNull(value) {
        return value != null || undefined;
    }

    /*
     * set default value if value null
     * for string value
    */
    function defIfNull(val, def) {
        return val ? val : def;
    }

    /**
     * Convert hex to rgb
    */
    function hexToRGB(pattern) {
        if (/\s*#/.test(pattern)) {
            var hexArray = pattern.split(''),
                hexArrayLen = hexArray.length - 1,
                hexPattern = '0123456789abcdef',
                r1 = hexArray[1],
                r2 = hexArray[2],
                g1 = hexArray[3],
                g2 = hexArray[4],
                b1 = hexArray[5],
                b2 = hexArray[6],
                r, g, b;

            function getIndex(p) {return hexPattern.indexOf(p)}
            // if shortcode
            if (hexArrayLen === 3) {
                r = Math.max( ( (getIndex(r1)+1) * (getIndex(r2)+1) - 1) , 0);
                g = Math.max( ( (getIndex(g1)+1) * (getIndex(g2)+1) - 1) , 0);
                b = Math.max( ( (getIndex(b1)+1) * (getIndex(b2)+1) - 1) , 0);
                return 'rgb(' + r + ',' + g + ',' + b + ')';
            }

            if (hexArrayLen === 6) {
                r = (getIndex(r1) * 16) + getIndex(r2);
                g = (getIndex(g1) * 16) + getIndex(g2);
                b = (getIndex(b1) * 16) + getIndex(b2);
                return 'rgb(' + r + ',' + g + ',' + b +  ')';
            }
        }
    }

    /**
     * Convert rgb to rgba
    */
    function rgbToRgba(val) {
        var rgba = val.replace(/rgb/i, 'rgba').replace(/\)/, ',1)');
        return rgba;
    }

    /**
     * loop through array
    */
    function forEach(elem, callBack) {
        var el = elem, len = el.length,
            i = 0;
        if (typeof el != 'number') {
            if (len === 1) {
                callBack.call(el[0], i, el[0]);
            } else {
                for ( ; i < len ; i++ ) {
                    callBack.call(el[i], i, el[i]);
                }
            }
        } else {
            for ( ; i < el ; i++ ) {
                callBack.call(null, i);
            }
        }
    }

    /**
     * loop through object
    */
    function forIn(obj, callBack) {
        for (var key in obj) {
            callBack.call(obj[key], key, obj[key]);
        }
    }

    /**
     * convert object key/value to css property if value not null
    */
    function toCss(key, value) {
        var p = '', css = '';
        if (value) {
            if (/[A-Z]+/g.test(key)) {
                p = key.replace(/[A-Z]/g, function (e) {
                      return '-' + e.toLowerCase();
                });
                css = p + ": " + value + ";";
            } else {
                css = key + ": " + value + ";";
            }
        }
        return css;
    }

    function getComputedStyle(elem, property) {
        return window.getComputedStyle(elem, null)[property]
    }

    /**
     * put hex value in object with it's key if value === string
     * set rgb/rgba to option.gridItems Object
    */
    forIn(options.gridItems, function(k, v) {
        var itemValue = options.gridItems;
        if (typeof v === 'string') {
            itemValue[k] = { hex: itemValue[k] };
            itemValue[k].rgb = hexToRGB( itemValue[k].hex );
            itemValue[k].rgba = rgbToRgba( itemValue[k].rgb );
        } else {
            itemValue[k].rgb = hexToRGB( itemValue[k].hex );
            itemValue[k].rgba = rgbToRgba( itemValue[k].rgb );
        }
    });
    console.log(options.gridItems)

    /**
     * booleans defaults values
    */
    var inlineStyle = isNotNull(options.inlineStyle) ? options.inlineStyle : true,
        /* @grid items */
        autoAppend = isNotNull(options.autoAppend) ? options.autoAppend : true,
        /* @navigation*/
        navState = isNotNull(options.navState) ? options.navState : true,
        autoBackgroundColor = isNotNull(options.autoBackgroundColor) ? options.autoBackgroundColor : true,
        stickyNavState = isNotNull(options.stickyNav) ? options.stickyNav : true,
        stickWithBottom = isNotNull(options.stickWithBottom) ? options.stickWithBottom : true,
        /*@overlay*/
        overlayState = isNotNull(options.overlayState) ? options.overlayState : true,
        overlayCenter = isNotNull(options.overlayCenter) ? options.overlayCenter : true,
        overlayMiddle = isNotNull(options.overlayMiddle) ? options.overlayMiddle : true,
        copiedMessageState = isNotNull(options.copiedMsgState) ? options.copiedMsgState : true,

        doc = document.body || document.documentElement,

        grid_container = selector.length ? selector[0].parentNode : selector,

        gridItemHTML = "

",
        gridItemList,
        gridItem,

        overlayHTML = "
copied!
",
        /**
         * style strings
        */
        activePattern = "hex",
        navStyle = '',
        navItemsStyle = '',
        navItemActiveStyle = '',
        stickyNavStyle = '',
        stickyNavHoverStyle = '',

        overlayStyle = '',
        colorValueBoxStyle = '',
        copiedMsgDuration,

        // copied message default style/copied animation
        navigation,
        navUL,
        navLI;

    /*
    ============================
        plugin Style
    ============================
    */

    /**
     * @nav outer
    */
    var nav = {
        outer_html: '

hex

rgb

rgba

',
        style: {
            width: defIfNull(options.navWidth, '100%'),
            height: defIfNull(options.navHeight, '50px'),
            transition: defIfNull(options.navTransition, 'all 0.4s ease'),
            paddingBottom: '40px'
        }
    };

    forIn(nav.style, function(key, value) {
        navStyle += toCss(key, value);
    });

    /**
     * @nav items default style
    */
    // get items height from nav
    // to make space at the bottom of nav;
    var navItemHeight = nav.style.height.replace(/\d+/g, (e)=> {
        return +e - 10;
    });

    var navItems = {
        style: {
            width: defIfNull(options.navItemsWidth, '170px'),
            lineHeight: defIfNull(options.navItemsHeight, navItemHeight),
            backgroundColor: "#ffffff",
            marginTop: '5px',
            transition: defIfNull(options.transition, 'all .4s ease-in-out'),
        },
        activeElement_style: {
            backgroundColor: defIfNull(options.navItemsActiveBackgroundColor, '#17c37b'),
            color: defIfNull(options.color, '#f1f1f1'),
            boxShadow: defIfNull(options.navItemsActiveBoxShadow, '0 0 0 #ffffff'),
            borderRadius: defIfNull(options.navItemsActiveBorderRadius, '0'),
        }
    };
    forIn(navItems.style, function(key, value) {
        navItemsStyle += toCss(key, value);
    });

    forIn(navItems.activeElement_style, function(key, value) {
        navItemActiveStyle += toCss(key, value);
    });

    /**
     * @stickynav style
    */
    var stickyNav = {
        style: {
            backgroundColor: defIfNull(options.stickyNavBackgroundColor, ''),
            boxShadow: defIfNull(options.stickyNavBoxShadow, ''),
            transition: defIfNull(options.stickyNavTransition, 'all 0.4s ease'),
        },
        hover_style: {
            backgroundColor: defIfNull(options.stickyNavHoverBackgroundColor, ''),
            boxShadow: defIfNull(options.stickyNavHoverBoxShadow, '')
        }
    };

    forIn(stickyNav.style, function(key, value) {
        stickyNavStyle += toCss(key, value);
    });

    forIn(stickyNav.hover_style, function (key, value) {
        stickyNavHoverStyle += toCss(key, value);
    });

    /**
     * @overlay style
    */
    var overlay = {
        backgroundColor: defIfNull(options.overlayBackgroundColor, 'rgba(0,0,0,.4)'),
        transition: defIfNull(options.overlayTransition, 'all 0.4s ease'),
    };

    forIn(overlay, function(key, value) {
        overlayStyle += toCss(key, value)
    })

    /**
     * @colorValueBox style
    */
    var colorValueBox = {
        width: '150px',
        height: '40px',
        backgroundColor: defIfNull(options.copiedMsgBackgroundColor, 'rgba(0,0,0,.7)'),
        color: defIfNull(options.copiedMsgColor, 'rgba(255,255,255,.8)'),
        boxShadow: defIfNull(options.copiedMsgBorder, '0 0 10px rgba(255,255,255,.8)'),
        transition: defIfNull(options.copiedMsgTransition, 'all 0.4s ease'),
    };

    colorValueBox.lineHeight = colorValueBox.height;

    forIn(colorValueBox, function (key, value) {
        colorValueBoxStyle += toCss(key, value);
    });

    if (copiedMessageState) {
        copiedMsgStyle = '.copy-state{opacity:0;z-index:-1;} span.copy-state.copied{z-index:1;opacity:1;}.copy-state.copied+span{index:-1;opacity:0;}';

        var colorValueBoxTransition = colorValueBox.transition.replace(/\s{1,}/, ' ');
        // get duration of colorValueBox transition to remove copied class on transition end
        copiedMsgDuration = +colorValueBoxTransition.split(' ')[1].replace(/s+/, '') * 2000;
    } else {
        copiedMsgStyle = '.copy-state{opacity:0;z-index:-1;}';
    }

    /* Grid items
     =========================================================
     * add color-holder class to selectors
     * equalize color holders with options.gridItems
        (append color holders if selectors length less than options.gridItems length)
     * clone color holders dimensions to colorsGrid-item
     * append color holder to colorsGrid-item
     ==========================================================
    */
    var colorHolderHTML = '

';

    /**
     * add color-holder class to selector if not container
    */
    if (selector.length) {
        // add color-holder class
        forEach(selector, function (i,e) {
            e.classList.add('color-holder');
        });
    } else {
        //append div with color-holder class to container
        selector.insertAdjacentHTML('beforeend', colorHolderHTML);
    }

    /**
     * append color-holder if selectors length less than options.gridItems length
    */
    var selectorsLength = doc.getElementsByClassName('color-holder').length,
        gridItemsObjectLength = Object.keys(options.gridItems||{}).length;

    if (selectorsLength < gridItemsObjectLength) {
        // get items number to append
        var reminingItems = gridItemsObjectLength - selectorsLength;
        // append remining items to container
        forEach(reminingItems, function () {
            grid_container.insertAdjacentHTML('beforeend', colorHolderHTML)
        });
    }

    /**
     * store color holder document list into array
     * get dimensions
    */
    var colorHolderList = [].slice.call(doc.getElementsByClassName('color-holder')),
        colorHolderWidth = colorHolderList[0].clientWidth + 'px',
        colorHolderHeight = colorHolderList[0].clientHeight + 'px',
        cHM = {
            top: getComputedStyle(colorHolderList[0], 'margin-top'),
            left: getComputedStyle(colorHolderList[0], 'margin-left'),
            bottom: getComputedStyle(colorHolderList[0], 'margin-bottom'),
            right: getComputedStyle(colorHolderList[0], 'margin-right')
        };

    /**
     * append colorHolder to colorsGrid-item
     * append overlay to colorsGrid-item
    */
    forEach(colorHolderList, function(i,e) {
        grid_container.insertAdjacentHTML('beforeend', gridItemHTML);
        gridItem = doc.getElementsByClassName('colorsGrid-item')[i];

        gridItem.setAttribute('data-hex-value', options.gridItems[i].hex);
        gridItem.setAttribute('data-rgb-value', options.gridItems[i].rgb);
        gridItem.setAttribute('data-rgba-value', options.gridItems[i].rgba);

        gridItem.style.width = colorHolderWidth;
        gridItem.style.height = colorHolderHeight;
        gridItem.style.margin = cHM.top +' '+ cHM.right +' '+ cHM.right +' '+ cHM.left;

        // make color dimensions relative to continer
        colorHolderList[i].style.width = '100%';
        colorHolderList[i].style.height = '100%';
        colorHolderList[i].style.margin = 'auto';
        colorHolderList[i].style.backgroundColor = options.gridItems[i].hex;

        gridItem.appendChild(colorHolderList[i]);
        gridItem.insertAdjacentHTML('beforeend', overlayHTML)
    });

    /**
     * colorsGrid-item
     * copy color when click on it
     * get overlay text value from it's active color pattern
    */
    gridItemList = [].slice.call(doc.getElementsByClassName('colorsGrid-item'));

    /* Navigation
     ======================================================================
     * prepend nav to gridItems container
     * make nav relative to container height when scrolling
        (stick it at the bottom of container if scrollTop > container.height)
     =======================================================================
    */
    if (navState) {
        console.log(navHeight)
        // prepend nav.outer_html to grid container
        grid_container.insertAdjacentHTML('afterbegin', nav.outer_html);

        navigation = doc.getElementsByClassName('color-navigation')[0],
        navUL = navigation.getElementsByTagName('ul')[0];
        navLI = navigation.getElementsByTagName('li');

        if (stickyNavState) {
            var navOffsetTop = navigation.offsetTop,
                navHeight = parseInt(nav.style.height.match(/\d+/)[0]) + 10,
                navSlideOutLength = (navHeight * -1) + 'px',
                containerHeight = grid_container.clientHeight - navHeight,
                scrollTop = window.screenTop;

            window.addEventListener('scroll', function() {
                scrollTop = window.pageYOffset;
                if (scrollTop >= navOffsetTop && scrollTop < containerHeight ) {
                    navUL.classList.add('sticky');
                    navUL.style.top = '';
                } else if (scrollTop >= containerHeight) {
                    navUL.style.top = navSlideOutLength;
                } else {
                    navUL.classList.remove('sticky');
                    navUL.style.top = '';
                }
            });
        }
    }

    /* Click event functions
    ===========================
    */

    /**
     * textarea to hold clicked item color value for coping
    */
    var textarea = document.createElement('textarea');
    textarea.setAttribute('id', 'copy-holder');
    textarea.style.position = 'fixed';
    textarea.style.left = '0px';
    textarea.style.top = '0px';
    textarea.style.zIndex = '0';
    textarea.tabIndex = '-1';
    doc.appendChild(textarea);
    var copyHolder = document.getElementById('copy-holder');

    /**
     * colorsGrid-item
    */
    forEach(gridItemList, function (i, gridItem) {
        var ths = this,
            copyState = ths.getElementsByClassName('copy-state')[0];
        // clone this active color pattern value to second span (vcolor value holder)
        ths.getElementsByTagName('span')[1].textContent = ths.getAttribute('data-'+ activePattern +'-value');

        ths.addEventListener('click', function() {
            copyHolder.value = ths.getAttribute('data-'+ activePattern +'-value');
            copyHolder.select();
            document.execCommand('copy');
            copyState.classList.add('copied');
            window.setTimeout(()=>{ copyState.classList.remove('copied') }, copiedMsgDuration);

        });
    });

    /**
     * navigation
    */
    forEach(navLI, function (i, li) {
        li.addEventListener('click', function () {
            forEach(navLI, function (i, li) {
                li.classList.remove('active-pattern');
            });
            activePattern = this.getAttribute('data-color-pattern');
            this.classList.add('active-pattern');
        });
    });
    console.log(colorValueBoxStyle)
    if (inlineStyle) {
        (function() {
            var defaultStyle = "";
            document.head.insertAdjacentHTML('beforeend', defaultStyle);
        }());
    }
};
<|endoftext|>


<commit_before>///////////////////////////////////////////////////////////
//
//   microbi.js
//
///////////////////////////////////////////////////////////
//
// Api server and http server for Node.js
//
//

var fs = require( 'fs' );
var http = require( 'http' )
var https = require( 'https' )
var path = require( 'path' )
var url = require( 'url' )
var mime = require( 'mimemap' ).map

// a couple of functions to route url paths
var router = require( './lib/router.js' )

// The api object
//
// This object is where the api routes are stored. It is only used when the
// Api server functionality is used. The router tries to match properties
// of this object to url paths. For example, the path:
//     stuff/items
// is mapped to a tree of properties in the api object as follows:
//     api.stuff.items.GET
//
// Note that the request method (GET in this case), is added as the last
// property name.
// If the api object if left empty, (no properties added) then the server
// will not match any api methods, and will work as a http static server only.
var api = null

// default Api content type is txt
// This is used to set the mime type for api requests.
// There is a function to set this.
var apiContentType = mime.txt

// Flag to enable or disable the static http server.
// This can be set to false to use microbi as an api server only.
// To use as an api server only, define an api from an external file,
// and use the provided method to set this flag to false.
var staticServer = true

/**
 * Request responder function. This function is called on each
 * request to the server
 *
 * This is the function that handles incoming requests to the server.
 * It does the next things:
 * - parses the url for incoming requests.
 * - verifies if there is an api request handler defined for that
 *   url. Executes it if there is one.
 * - If there is no api function, then it looks for a file that
 *   correspond to the requested url.
 * - If a file is found, set the corresponding content type and serves
 *   the file.
 */
var onRequest = function( request, response ) {

  // get request method (GET, POST, PUT, etc) and url
  var urlObj = url.parse( request.url, true )

  // collect request data into an object
  requestInfo = {
    method: request.method,    // "GET", "POST", etc
    pathname: urlObj.pathname, // request pathname (i.e: "/stuff/item")
    queryParameters: urlObj.query, // url params as key:values in an object
    body: '', // the request body, empty for now
  }

  if ( ! validatePath( requestInfo.pathname ) ) {
    respond404( response )
    return
  }

  // this section handles api request.
  // Only used if there is an api defined.
  if ( api ) {

    var apiOp = router.getOp( requestInfo, api )

    if ( apiOp && apiOp.stream ) {
      apiOp.fn( request, response )
    // If the "stream" property is not set, call the function with the
    // request url and the complete request message body as parameters.
    } else if ( apiOp ) {
      // collect the whole body before answering
      request.setEncoding( 'utf8' )
      request.on( 'data', function( data ) {
        requestInfo.body += data
      })
      // when the incoming message body is complete, call the defined
      // api method for this request
      request.on( 'end', function() {
        response.writeHead( 200, { 'Content-Type': apiContentType } );
        // call the api method, passing as parameter the url object,
        // and the incoming message body
        response.end( apiOp.fn( requestInfo ) )
      })

      return
    }
  }

  // If the static server has been disabled, don't look for files to
  // serve. just exit now with a 404 response.
  if ( ! staticServer ) {
    respond404( response )
    return
  }

  // If the responder function reaches to here, it means that there is no
  // api method to server. What is left is to check if there is a file
  // to serve at the given path. The static file server only allows for
  // GET request. If the request method is not GET, respond 405 and exit.
  if ( requestInfo.method != 'GET' ) {
    respond405( response )
    return
  }

  serveFile( requestInfo.pathname, request, response )
}

/**
 *
 */
var serveFile = function( pathname, request, response ) {
  // If the requested path ends in "/", add "index.html"
  if ( pathname[ pathname.length - 1 ] == '/' )
    pathname += 'index.html'

  var fileToServe = '.' + pathname

  // serve file or respond 404 if there is no file
  var readStream = fs.createReadStream( fileToServe )

  readStream.on( 'error', function() {
    respond404( response )
  })
  readStream.once( 'readable', function() {
    var ext = path.extname( fileToServe ).replace( '.', '' )
    response.writeHead( 200, {
      'Content-Type': mime[ext] }
    )
    // connect the file read stream to the response stream, to serve the file
    readStream.pipe( response )
    readStream.on( 'end', function() {
      response.end()
    })
  })
}

/**
 * Starts the server.
 *
 * Parameters:
 * Port and ip are taken from the first available of these:
 * - function parameters
 * - command line parameters
 * - defaults to 127.0.0.1:8080
 */
var server = function( port, ip ) {
  port = port || process.argv[ 2 ] || 8080
  ip = ip || process.argv[ 3 ] || '127.0.0.1'
  http.createServer( onRequest ).listen( port, ip );
  console.log( 'Server running at ip: ' + ip + ':' + port );
}

// export the server function, for use in external scripts
exports.server = server

// run server if not being required from external file.
// When the microbi file is being run directly with node:
//    node microbi.js
if ( ! module.parent ) server()

/**
 * Starts https server.
 *
 * Parameters:
 * Options is an object with key and certificate, as described
 * in node api docs for https.createServer method.
 * Port and ip are taken from the first available of these:
 * - function parameters
 * - command line parameters
 * - defaults to 127.0.0.1:8080
 */
var httpsServer = function( options, port, ip ) {
  port = port || process.argv[ 2 ] || 8080
  ip = ip || process.argv[ 3 ] || '127.0.0.1'
  https.createServer( options, onRequest ).listen( port, ip );
  console.log( 'Https server running at ip: ' + ip + ':' + port );
}

// export the server function, for use in external scripts
exports.httpsServer = httpsServer

// The paths requested to the server must match this regex.
// It will only allow letters, numbers underscore, minus
// sign and dots.
var VALID_PATH_REGEX = /^[\./_\-\d\w]*$/

// The path isn't allowed to contain ".." or "/." or "//"
var DISALLOWED_PATH_REGEX = /(\.\.)|(\/\.)|(\/\/)/

/**
 * Validate the path
 *
 * Returns true if the path is valid. False otherwise.
 */
var validatePath = function( path ) {
  if ( DISALLOWED_PATH_REGEX.test( path ) ) return false
  return VALID_PATH_REGEX.test( path )
}

/**
 * Emit a 404 response
 */
var respond404 = function( response ) {
  response.writeHead( 404 )
  response.end( '404 Not found.' )
}

/**
 * Emit a 405 response: method not allowed
 */
var respond405 = function( response ) {
  response.writeHead( 405 )
  response.end( '405 Method not allowed.' )
}

/**
 * Set the default content type for api requests
 * The content type for static file requests is determined from the file
 * termination. This is only for Api requests
 */
exports.setApiContentType = function( ext ) {
  apiContentType = mime[ext]
}

/**
 * Set the api object.
 * This is used to set api routes.
 */
exports.setApi = function( apiOb ) {
  api = apiOb
}

/**
 * Disable the static file server.
 * This allows to use microbi as an api server only.
 * This is for the cases when microbi is used as an api server
 * only, and the static server is not needed.
 */
exports.disableStaticServer = function() {
  staticServer = false
}
<commit_msg>Add path parameters to api methods<commit_after>///////////////////////////////////////////////////////////
//
//   microbi.js
//
///////////////////////////////////////////////////////////
//
// Api server and http server for Node.js
//
//

var fs = require( 'fs' );
var http = require( 'http' )
var https = require( 'https' )
var path = require( 'path' )
var url = require( 'url' )
var mime = require( 'mimemap' ).map

// a couple of functions to route url paths
var router = require( './lib/router.js' )

// The api object
//
// This object is where the api routes are stored. It is only used when the
// Api server functionality is used. The router tries to match properties
// of this object to url paths. For example, the path:
//     stuff/items
// is mapped to a tree of properties in the api object as follows:
//     api.stuff.items.GET
//
// Note that the request method (GET in this case), is added as the last
// property name.
// If the api object if left empty, (no properties added) then the server
// will not match any api methods, and will work as a http static server only.
var api = null

// default Api content type is txt
// This is used to set the mime type for api requests.
// There is a function to set this.
var apiContentType = mime.txt

// Flag to enable or disable the static http server.
// This can be set to false to use microbi as an api server only.
// To use as an api server only, define an api from an external file,
// and use the provided method to set this flag to false.
var staticServer = true

/**
 * Request responder function. This function is called on each
 * request to the server
 *
 * This is the function that handles incoming requests to the server.
 * It does the next things:
 * - parses the url for incoming requests.
 * - verifies if there is an api request handler defined for that
 *   url. Executes it if there is one.
 * - If there is no api function, then it looks for a file that
 *   correspond to the requested url.
 * - If a file is found, set the corresponding content type and serves
 *   the file.
 */
var onRequest = function( request, response ) {

  // get request method (GET, POST, PUT, etc) and url
  var urlObj = url.parse( request.url, true )

  // collect request data into an object
  requestInfo = {
    method: request.method,    // "GET", "POST", etc
    pathname: urlObj.pathname, // request pathname (i.e: "/stuff/item")
    queryParams: urlObj.query, // url params as key:values in an object
    body: '', // the request body, empty for now
    pathParams: null,
  }

  if ( ! validatePath( requestInfo.pathname ) ) {
    respond404( response )
    return
  }

  // this section handles api request.
  // Only used if there is an api defined.
  if ( api ) {

    var apiOp = router.getOp( requestInfo, api )

    if ( apiOp && apiOp.stream ) {
      apiOp.fn( request, response )
    // If the "stream" property is not set, call the function with the
    // request url and the complete request message body as parameters.
    } else if ( apiOp ) {
      // collect the whole body before answering
      request.setEncoding( 'utf8' )
      requestInfo.pathParams = apiOp.params
      request.on( 'data', function( data ) {
        requestInfo.body += data
      })
      // when the incoming message body is complete, call the defined
      // api method for this request
      request.on( 'end', function() {
        response.writeHead( 200, { 'Content-Type': apiContentType } );
        // call the api method, passing as parameter the url object,
        // and the incoming message body
        response.end( apiOp.fn( requestInfo ) )
      })

      return
    }
  }

  // If the static server has been disabled, don't look for files to
  // serve. just exit now with a 404 response.
  if ( ! staticServer ) {
    respond404( response )
    return
  }

  // If the responder function reaches to here, it means that there is no
  // api method to server. What is left is to check if there is a file
  // to serve at the given path. The static file server only allows for
  // GET request. If the request method is not GET, respond 405 and exit.
  if ( requestInfo.method != 'GET' ) {
    respond405( response )
    return
  }

  serveFile( requestInfo.pathname, request, response )
}

/**
 *
 */
var serveFile = function( pathname, request, response ) {
  // If the requested path ends in "/", add "index.html"
  if ( pathname[ pathname.length - 1 ] == '/' )
    pathname += 'index.html'

  var fileToServe = '.' + pathname

  // serve file or respond 404 if there is no file
  var readStream = fs.createReadStream( fileToServe )

  readStream.on( 'error', function() {
    respond404( response )
  })
  readStream.once( 'readable', function() {
    var ext = path.extname( fileToServe ).replace( '.', '' )
    response.writeHead( 200, {
      'Content-Type': mime[ext] }
    )
    // connect the file read stream to the response stream, to serve the file
    readStream.pipe( response )
    readStream.on( 'end', function() {
      response.end()
    })
  })
}

/**
 * Starts the server.
 *
 * Parameters:
 * Port and ip are taken from the first available of these:
 * - function parameters
 * - command line parameters
 * - defaults to 127.0.0.1:8080
 */
var server = function( port, ip ) {
  port = port || process.argv[ 2 ] || 8080
  ip = ip || process.argv[ 3 ] || '127.0.0.1'
  http.createServer( onRequest ).listen( port, ip );
  console.log( 'Server running at ip: ' + ip + ':' + port );
}

// export the server function, for use in external scripts
exports.server = server

// run server if not being required from external file.
// When the microbi file is being run directly with node:
//    node microbi.js
if ( ! module.parent ) server()

/**
 * Starts https server.
 *
 * Parameters:
 * Options is an object with key and certificate, as described
 * in node api docs for https.createServer method.
 * Port and ip are taken from the first available of these:
 * - function parameters
 * - command line parameters
 * - defaults to 127.0.0.1:8080
 */
var httpsServer = function( options, port, ip ) {
  port = port || process.argv[ 2 ] || 8080
  ip = ip || process.argv[ 3 ] || '127.0.0.1'
  https.createServer( options, onRequest ).listen( port, ip );
  console.log( 'Https server running at ip: ' + ip + ':' + port );
}

// export the server function, for use in external scripts
exports.httpsServer = httpsServer

// The paths requested to the server must match this regex.
// It will only allow letters, numbers underscore, minus
// sign and dots.
var VALID_PATH_REGEX = /^[\./_\-\d\w]*$/

// The path isn't allowed to contain ".." or "/." or "//"
var DISALLOWED_PATH_REGEX = /(\.\.)|(\/\.)|(\/\/)/

/**
 * Validate the path
 *
 * Returns true if the path is valid. False otherwise.
 */
var validatePath = function( path ) {
  if ( DISALLOWED_PATH_REGEX.test( path ) ) return false
  return VALID_PATH_REGEX.test( path )
}

/**
 * Emit a 404 response
 */
var respond404 = function( response ) {
  response.writeHead( 404 )
  response.end( '404 Not found.' )
}

/**
 * Emit a 405 response: method not allowed
 */
var respond405 = function( response ) {
  response.writeHead( 405 )
  response.end( '405 Method not allowed.' )
}

/**
 * Set the default content type for api requests
 * The content type for static file requests is determined from the file
 * termination. This is only for Api requests
 */
exports.setApiContentType = function( ext ) {
  apiContentType = mime[ext]
}

/**
 * Set the api object.
 * This is used to set api routes.
 */
exports.setApi = function( apiOb ) {
  api = apiOb
}

/**
 * Disable the static file server.
 * This allows to use microbi as an api server only.
 * This is for the cases when microbi is used as an api server
 * only, and the static server is not needed.
 */
exports.disableStaticServer = function() {
  staticServer = false
}
<|endoftext|>


<commit_before>module.exports = Mixcoin

var _ = require('lodash')
var canonicalize = require('canonical-json')

var EventEmitter = require('events').EventEmitter

var bitcore = require('bitcore')
var networks = bitcore.networks
var WalletKey = bitcore.WalletKey
var coinUtil = bitcore.util
var Peer = bitcore.Peer
var PeerManager = bitcore.PeerManager

/**
* An implementation of the Mixcoin accountable mixing service protocol
* @param {string|Buffer} opts
*/
function Mixcoin (opts) {
  var self = this

  if (!(self instanceof Mixcoin)) return new Mixcoin(opts)
  EventEmitter.call(self)

  if (!opts.privateKey) return new Error('you must supply a private key')

  self.ready = false
  self.listening = false
  self._binding = false
  self._destroyed = false

  self.rpcIp = opts.rpcIp
  self.rpcPort = opts.rpcPort

  // generate public key
  var keyOptions = {
    network: networks.testnet
  }

  self.mixKey = new WalletKey(keyOptions)
  self.mixKey.fromObj({
    priv: opts.privateKey
  })

  self.escrowKeyOptions = {
    network: networks.testnet
  }

  /**
  * Table of outstanding chunks
  * @type {Object} addr:string -> chunk object
  */
  self.chunkTable = {}

  /**
  * List of outstanding escrow addresses
  * @type {WalletKey|Buffer}
  */
  self.escrowAddresses = []

  self.peerManager = new PeerManager({network: networks.testnet})

  self.peerManager.on('connection', function(conn) {
    conn.on('block', self._handleBlock.bind(self))
  })

  self.peerManager.start()
}

Mixcoin.prototype.handleChunkRequest = function(chunkJson, cb) {
  var self = this
  var err = self._validateChunkRequest(chunkJson)
  if (err) return cb(err)

  // generate a fresh escrow keypair
  var escrowKey = self._generateEscrowKey()
  var escrowAddress = bitcore.buffertools.toHex(escrowKey.privKey.public)

  chunkJson.escrow = escrowAddress

  // serialize chunk json in canonical form, hash it
  var serializedChunk = JSON.stringify(canonicalize(chunkJson))
  var chunkHash = coinUtil.sha256(serializedChunk)

  var sigBuf = self.mixKey.privKey.signSync(chunkHash)
  var derSig = self._toHex(sigBuf)

  debugger
  chunkJson.warrant = derSig

  // store escrow address and the chunk
  self.escrowAddresses.push(escrowKey)
  self._registerNewChunk(chunkJson)

  cb(null, chunkJson)
}

Mixcoin.prototype._toHex = function (buf) {
  return bitcore.buffertools.toHex(buf)
}

Mixcoin.prototype._validateChunkRequest = function(chunkJson) {
  var self = this
  // TODO implement this
  return null
}

Mixcoin.prototype._registerNewChunk = function (chunkJson) {
  var self = this
  var chunk = _.clone(chunkJson)

  // escrow = escrow public key for this chunk
  self.chunkTable[chunk.escrow] = chunk
}

Mixcoin.prototype._generateEscrowKey = function () {
  var self = this
  var escrowKey = new WalletKey(self.escrowKeyOptions)
  escrowKey.generate()
  return escrowKey
}

Mixcoin.prototype._handleBlock = function (info) {
  var block = info.message
  var txs = block.tx

  for (var tx in txs) {
    var txsIn = tx.in
    var txsOut = tx.out

    // check if there's a transaction sending correct value to
    // one of the escrow addresses
    // TODO assume all chunk values are the same?
  }
}
<commit_msg>wip modularize chunk tables<commit_after>module.exports = Mixcoin

var _ = require('lodash')
var canonicalize = require('canonical-json')

var EventEmitter = require('events').EventEmitter

var bitcore = require('bitcore')
var networks = bitcore.networks
var WalletKey = bitcore.WalletKey
var coinUtil = bitcore.util
var Peer = bitcore.Peer
var PeerManager = bitcore.PeerManager

/**
* An implementation of the Mixcoin accountable mixing service protocol
* @param {string|Buffer} opts
*/
function Mixcoin (opts) {
  var self = this

  if (!(self instanceof Mixcoin)) return new Mixcoin(opts)
  EventEmitter.call(self)

  if (!opts.privateKey) return new Error('you must supply a private key')

  self.ready = false
  self.listening = false
  self._binding = false
  self._destroyed = false

  self.rpcIp = opts.rpcIp
  self.rpcPort = opts.rpcPort

  // generate public key
  var keyOptions = {
    network: networks.testnet
  }

  self.mixKey = new WalletKey(keyOptions)
  self.mixKey.fromObj({
    priv: opts.privateKey
  })

  self.escrowKeyOptions = {
    network: networks.testnet
  }

  /**
  * Table of outstanding chunks
  * @type {Object} addr:string -> chunk object
  */
  self.chunksPendingArrival = {}

  /**
  * List of outstanding escrow addresses
  * @type {WalletKey|Buffer}
  */
  self.escrowAddresses = []

  self.peerManager = new PeerManager({network: networks.testnet})

  self.peerManager.on('connection', function(conn) {
    conn.on('block', self._handleBlock.bind(self))
  })

  self.peerManager.start()
}

Mixcoin.prototype.handleChunkRequest = function(chunkJson, cb) {
  var self = this
  var err = self._validateChunkRequest(chunkJson)
  if (err) return cb(err)

  // generate a fresh escrow keypair
  var escrowKey = self._generateEscrowKey()
  var escrowAddress = bitcore.buffertools.toHex(escrowKey.privKey.public)

  chunkJson.escrow = escrowAddress

  // serialize chunk json in canonical form, hash it
  var serializedChunk = JSON.stringify(canonicalize(chunkJson))
  var chunkHash = coinUtil.sha256(serializedChunk)

  var sigBuf = self.mixKey.privKey.signSync(chunkHash)
  var derSig = self._toHex(sigBuf)

  debugger
  chunkJson.warrant = derSig

  // store escrow address and the chunk
  self.escrowAddresses.push(escrowKey)
  self._registerNewChunk(chunkJson)

  cb(null, chunkJson)
}

Mixcoin.prototype._toHex = function (buf) {
  return bitcore.buffertools.toHex(buf)
}

Mixcoin.prototype._validateChunkRequest = function(chunkJson) {
  var self = this
  // TODO implement this
  return null
}

Mixcoin.prototype._registerNewChunk = function (chunkJson) {
  var self = this
  var chunk = _.clone(chunkJson)

  // escrow = escrow public key for this chunk
  self.chunksPendingArrival[chunk.escrow] = chunk
}

Mixcoin.prototype._generateEscrowKey = function () {
  var self = this
  var escrowKey = new WalletKey(self.escrowKeyOptions)
  escrowKey.generate()
  return escrowKey
}

Mixcoin.prototype._handleBlock = function (info) {
  var block = info.message
  var txs = block.tx

  for (var tx in txs) {
    var txsIn = tx.in
    var txsOut = tx.out

    // check if there's a transaction sending correct value to
    // one of the escrow addresses
    // TODO assume all chunk values are the same?
  }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

'use strict';
var _ = require('underscore');
var $ = require('jquery');
var EventEmitter = require('events');
var options = require('./options');

function makeFontSizeDropdown(elem, obj, buttonDropdown) {
    var onWheelEvent = function (e) {
        e.preventDefault();
        var selectedId = elem.find('.active').index();
        if (e.originalEvent.deltaY >= 0 && selectedId < elem.children().length - 1) {
            selectedId++;
        } else if (e.originalEvent.deltaY < 0 && selectedId > 0) {
            selectedId--;
        }
        elem.children().eq(selectedId).trigger('click');
    };

    var onClickEvent = function () {
        // Toggle off the selection of the others
        $(this)
            .addClass('active')
            .siblings().removeClass('active');
        // Send the data
        obj.scale = $(this).data('value');
        obj.apply();
        obj.emit('change');
    };

    for (var i = 8; i <= 30; i++) {
        var item = $('<button></button>');

        item.attr('data-value', i)
            .addClass('dropdown-item btn btn-sm btn-light')
            .text(i)
            .appendTo(elem)
            .click(onClickEvent);

        if (obj.scale === i) {
            item.addClass('active');
        }
    }

    if (buttonDropdown) {
        buttonDropdown.on('wheel', onWheelEvent);
    }
}

function convertOldScale(oldScale) {
    // New low + ((new max - new low) * (oldScale - old low) / (old max - old low))
    return Math.floor(8 + (22 * (oldScale - 0.3) / 2.7));
}

function FontScale(domRoot, state, fontSelectorOrEditor) {
    EventEmitter.call(this);
    this.domRoot = domRoot;
    // Old scale went from 0.3 to 3. New one uses 8 up to 30, so we can convert the old ones to the new format
    this.scale = state.fontScale || options.defaultFontScale;
    if (this.scale < 8) {
        this.scale = convertOldScale(this.scale);
    }
    this.setTarget(fontSelectorOrEditor);
    this.apply();
    makeFontSizeDropdown(this.domRoot.find('.font-size-list'), this, this.domRoot.find('.fs-button'));
}

_.extend(FontScale.prototype, EventEmitter.prototype);

FontScale.prototype.apply = function () {
    if (this.isFontOfStr) {
        this.domRoot.find(this.fontSelectorOrEditor).css('font-size', this.scale + 'pt');
    } else {
        this.fontSelectorOrEditor.updateOptions({
            fontSize: this.scale,
        });
    }
};

FontScale.prototype.addState = function (state) {
    state.fontScale = this.scale;
};

FontScale.prototype.setScale = function (scale) {
    this.scale = scale;
    this.apply();
};

FontScale.prototype.setTarget = function (target) {
    this.fontSelectorOrEditor = target;
    this.isFontOfStr = typeof (this.fontSelectorOrEditor) === 'string';
};

module.exports = FontScale;
<commit_msg>Change new font units to pixels (#2521)<commit_after>// Copyright (c) 2016, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

'use strict';
var _ = require('underscore');
var $ = require('jquery');
var EventEmitter = require('events');
var options = require('./options');

function makeFontSizeDropdown(elem, obj, buttonDropdown) {
    var onWheelEvent = function (e) {
        e.preventDefault();
        var selectedId = elem.find('.active').index();
        if (e.originalEvent.deltaY >= 0 && selectedId < elem.children().length - 1) {
            selectedId++;
        } else if (e.originalEvent.deltaY < 0 && selectedId > 0) {
            selectedId--;
        }
        elem.children().eq(selectedId).trigger('click');
    };

    var onClickEvent = function () {
        // Toggle off the selection of the others
        $(this)
            .addClass('active')
            .siblings().removeClass('active');
        // Send the data
        obj.scale = $(this).data('value');
        obj.apply();
        obj.emit('change');
    };

    for (var i = 8; i <= 30; i++) {
        var item = $('<button></button>');

        item.attr('data-value', i)
            .addClass('dropdown-item btn btn-sm btn-light')
            .text(i)
            .appendTo(elem)
            .click(onClickEvent);

        if (obj.scale === i) {
            item.addClass('active');
        }
    }

    if (buttonDropdown) {
        buttonDropdown.on('wheel', onWheelEvent);
    }
}

function convertOldScale(oldScale) {
    // New low + ((new max - new low) * (oldScale - old low) / (old max - old low))
    return Math.floor(8 + (22 * (oldScale - 0.3) / 2.7));
}

function FontScale(domRoot, state, fontSelectorOrEditor) {
    EventEmitter.call(this);
    this.domRoot = domRoot;
    // Old scale went from 0.3 to 3. New one uses 8 up to 30, so we can convert the old ones to the new format
    this.scale = state.fontScale || options.defaultFontScale;
    // The check seems pointless, but it ensures a false in case it's undefined
    this.usePxUnits = state.fontUsePx === true;
    if (this.scale < 8) {
        this.scale = convertOldScale(this.scale);
    }
    this.setTarget(fontSelectorOrEditor);
    this.apply();
    makeFontSizeDropdown(this.domRoot.find('.font-size-list'), this, this.domRoot.find('.fs-button'));
}

_.extend(FontScale.prototype, EventEmitter.prototype);

FontScale.prototype.apply = function () {
    if (this.isFontOfStr) {
        this.domRoot.find(this.fontSelectorOrEditor).css('font-size', this.scale + (this.usePxUnits ? 'px' : 'pt'));
    } else {
        this.fontSelectorOrEditor.updateOptions({
            fontSize: this.scale,
        });
    }
};

FontScale.prototype.addState = function (state) {
    state.fontScale = this.scale;
    state.fontUsePx = true;
};

FontScale.prototype.setScale = function (scale) {
    this.scale = scale;
    this.apply();
};

FontScale.prototype.setTarget = function (target) {
    this.fontSelectorOrEditor = target;
    this.isFontOfStr = typeof (this.fontSelectorOrEditor) === 'string';
};

module.exports = FontScale;
<|endoftext|>