<commit_before>/**
 * @file fractals.js - a library of Javascript functions for graphing fractals
 * @author Alya C. B.
 * @version 1.0
 */

/**
 * {Complex} is the complex number type, represented by a JS object for the form {"imaginary": <value>; "real": <value>}
 *  both values are of the type {Number}
 * {Color} is the {String} hexadecimal representation of a color, of the form #RRGGBB
 *  where RR, GG, and BB are two-digit hexadecimal numbers from 0 to 255 (00 to FF)
 */

/**
 * graphs the Mandelbrot Set fractal on the given canvas
 *  using the number of iterations specified,
 *  and within the range of values specified
 * @requires - positive {Integer} values, rxmin < rxmax, and rymin < rymax
 * @param {DOMElement} canvas - canvas element on which to graph fractal
 * @param {Integer} max_iter - maximum number of iterations used for approximate the limit at every point
 * @param {Integer} rxmin - scaled minimum ordinate
 * @param {Integer} rxmax - scaled maximum ordinate
 * @param {Integer} rymin - scaled minimum abcissa
 * @param {Integer} rymax - scaled maximum abcissa
 * @param {DOMElement} informer - element with a value field used for updating status of graph (for 'loading' prompt)
 * @param {Boolean} smooth - enables or disables smooth coloring
 * @return {Integer} - returns int id of timer used to pace fractal loading
 */
function graph(canvas, max_iter, rxmin, rxmax, rymin, rymax, informer, smooth){
	var context = canvas.getContext("2d");

	// Mandelbrot radius
	var threshold = 4;

	var xdiff = canvas.width;
	var ydiff = canvas.height;
	var rangex = rxmax - rxmin;
	var rangey = rymax - rymin;

	// constant values
	var xf = rangex/xdiff;
	var yf = rangey/ydiff;
	var xd = Math.round(xdiff/40); // width of rectangle subdivision
	var yd = Math.round(ydiff/20); // height of rectangle subdivision
	var xc = rxmax;
	var yc = rymax;
	var ptw = 1; // point-square width
	var l2 = Math.log(2);

	// set up color palette
	var colors = palette(max_iter);

	// initial variable values
	var x = xcurr = 0;
	var y = ycurr = 0;

	// Draws the next subdivision of the canvas
	//  every millisecond, until entire canvas is drawn
	//  or, until graph is called again.
	var timer = setInterval(function() {
		// if exceeds bounds of canvas, stop graphing
		if(xcurr>=xdiff && ycurr>=ydiff){
			informer.value = "hide";
			return;
		}

		// if x-coordinate is too large, start a new row
		if(xcurr>=xdiff){
			xcurr = 0;
			ycurr += yd;
		}

		// clear box, then draw over it
		context.clearRect(xcurr, ycurr, xd, yd);

		// loop over all coordinates inside subdivision of canvas
		for(x = xcurr; x<xcurr+xd; x+=ptw){
			for(y = ycurr; y<ycurr+yd; y+=ptw){
				// convert from canvas coordinates to fractal coordinates
				var xac = xf*x-xc;
				var yac = yf*y-yc;

				var c = {"real": xac, "imaginary": yac};
				var count = 0; // iteration count
				var z = {"imaginary": 0, "real": 0};
				var m = 0; // magnitude of z
				// check if P(z) at c diverges to infinity, or is in Mandelbrot set
				while(m < threshold && count < max_iter){
					z = eval(z, c);
					count++;
					m = magnitude2(z);
				}

				// color point based on the value of count when the prior loop exits
				var color = colors[count];
				if((count < max_iter - 1) && smooth){ // smooth coloring
					var log_zn = Math.log(m)/2;
    					var nu = Math.log(log_zn/l2)/l2;
					var it = count + 1 - nu;
					color = colors[Math.floor(it)];
					var color2 = colors[Math.floor(it)+1];
					color = linear_interpolate(color, color2, it%1);
				}

				context.fillStyle = color;
				context.fillRect(x, y, ptw, ptw);
			}
		}

		xcurr += xd;

	}, 1);

	return timer;
}

/**
 * Evaluates the complex equation P(z) = z^2 + c (Mandelbrot Set)
 *
 * @param {Complex} variable - the value of the complex variable z
 * @param {Complex} constant - the value of the complex constant c (representing the coordinates of the point being iterated over)
 * @return {Complex} - the evaluated complex number P(z)
 */
function eval(variable, constant){
	// var z = {"imaginary": Math.abs(variable["imaginary"]), "real": Math.abs(variable["real"])}; // burning ship fractal
	var mult = multiply(variable, variable);
	return add(constant, mult);
}

// Coloring Methods

/**
 * Linear interpolates an intermediate color between the given ones, using given slope
 * @param {Color} color1 - first color
 * @param {Color} color2 - second color
 * @param {Number} slope - value of slope for linterpolation
 * @return {Color} - value of intermediate color
 */
function linear_interpolate(color1, color2, slope){
	var r1 = parseInt(color1.substring(1,3), 16);
	var r2 = parseInt(color2.substring(1,3), 16);
	var r = Math.floor(Math.abs(r1 + slope*(r2 - r1))).toString(16);
	r = (r.length == 2) ? r : "0" + r;

	var g1 = parseInt(color1.substring(3,5), 16);
	var g2 = parseInt(color2.substring(3,5), 16);
	var g = Math.floor(Math.abs(g1 + slope*(g2 - g1))).toString(16);
	g = (g.length == 2) ? g : "0" + g;

	var b1 = parseInt(color1.substring(5,7), 16);
	var b2 = parseInt(color2.substring(5,7), 16);
	var b = Math.floor(Math.abs(b1 + slope*(b2 - b1))).toString(16);
	b = (b.length == 2) ? b : "0" + b;

	var c = "#" + r + g + b;
	return c;
}

/**
 * Generates a smooth palette of colors based on the iteration-count, last color is always black
 * @requires - max_iter > 0
 * @param {Integer} max_iter - maximum number of iterations to be evaluated
 * @return {Color[]} - returns an array of {Color} of size max_iter + 1
 */
function palette(max_iter){
	var end_color = '#773399';
	var mid_color = '#CCAA66';
        var colors = new Array(max_iter+1);

	var factor = Math.PI/(2*Math.max(1, max_iter-1));
        for(var i=0; i<max_iter; i++){
		colors[i] = linear_interpolate(end_color, mid_color, Math.sin(i*factor));
        }
        colors[max_iter] = "#000000";

	return colors;
}

// Complex number operands

/**
 * Adds two complex numbers
 * @param {Complex} complex1
 * @param {Complex} complex2
 * @return {Complex} - complex sum of complex1 and complex2
 */
function add(complex1, complex2){
	return {"imaginary": complex1["imaginary"] + complex2["imaginary"],
		"real": complex1["real"] + complex2["real"]};
}

/**
 * Multiplies two complex numbers
 * @param {Complex} complex1
 * @param {Complex} complex2
 * @return {Complex} - complex product of complex1 and complex2
 */
function multiply(complex1, complex2){
	return {"imaginary": complex1["real"]*complex2["imaginary"] + complex1["imaginary"]*complex2["real"],
		"real": complex1["real"]*complex2["real"] - complex1["imaginary"]*complex2["imaginary"]};
}

/**
 * Finds square of magnitude of a complex number
 * @param {Complex} complex
 * @return {Complex} - magnitude of complex squared
 */
function magnitude2(complex){
	return complex["imaginary"]*complex["imaginary"] + complex["real"]*complex["real"];
}

<commit_msg>Slight iteration coloring improvement<commit_after>/**
 * @file fractals.js - a library of Javascript functions for graphing fractals
 * @author Alya C. B.
 * @version 1.0
 */

/**
 * {Complex} is the complex number type, represented by a JS object for the form {"imaginary": <value>; "real": <value>}
 *  both values are of the type {Number}
 * {Color} is the {String} hexadecimal representation of a color, of the form #RRGGBB
 *  where RR, GG, and BB are two-digit hexadecimal numbers from 0 to 255 (00 to FF)
 */

/**
 * graphs the Mandelbrot Set fractal on the given canvas
 *  using the number of iterations specified,
 *  and within the range of values specified
 * @requires - positive {Integer} values, rxmin < rxmax, and rymin < rymax
 * @param {DOMElement} canvas - canvas element on which to graph fractal
 * @param {Integer} max_iter - maximum number of iterations used for approximate the limit at every point
 * @param {Integer} rxmin - scaled minimum ordinate
 * @param {Integer} rxmax - scaled maximum ordinate
 * @param {Integer} rymin - scaled minimum abcissa
 * @param {Integer} rymax - scaled maximum abcissa
 * @param {DOMElement} informer - element with a value field used for updating status of graph (for 'loading' prompt)
 * @param {Boolean} smooth - enables or disables smooth coloring
 * @return {Integer} - returns int id of timer used to pace fractal loading
 */
function graph(canvas, max_iter, rxmin, rxmax, rymin, rymax, informer, smooth){
	var context = canvas.getContext("2d");

	// Mandelbrot radius
	var threshold = 4;

	var xdiff = canvas.width;
	var ydiff = canvas.height;
	var rangex = rxmax - rxmin;
	var rangey = rymax - rymin;

	// constant values
	var xf = rangex/xdiff;
	var yf = rangey/ydiff;
	var xd = Math.round(xdiff/40); // width of rectangle subdivision
	var yd = Math.round(ydiff/20); // height of rectangle subdivision
	var xc = rxmax;
	var yc = rymax;
	var ptw = 1; // point-square width
	var l2 = Math.log(2);

	// set up color palette
	var colors = palette(max_iter);

	// initial variable values
	var x = xcurr = 0;
	var y = ycurr = 0;

	// Draws the next subdivision of the canvas
	//  every millisecond, until entire canvas is drawn
	//  or, until graph is called again.
	var timer = setInterval(function() {
		// if exceeds bounds of canvas, stop graphing
		if(xcurr>=xdiff && ycurr>=ydiff){
			informer.value = "hide";
			return;
		}

		// if x-coordinate is too large, start a new row
		if(xcurr>=xdiff){
			xcurr = 0;
			ycurr += yd;
		}

		// clear box, then draw over it
		context.clearRect(xcurr, ycurr, xd, yd);

		// loop over all coordinates inside subdivision of canvas
		for(x = xcurr; x<xcurr+xd; x+=ptw){
			for(y = ycurr; y<ycurr+yd; y+=ptw){
				// convert from canvas coordinates to fractal coordinates
				var xac = xf*x-xc;
				var yac = yf*y-yc;

				var c = {"real": xac, "imaginary": yac};
				var count = 0; // iteration count
				var z = {"imaginary": 0, "real": 0};
				var m = 0; // magnitude of z
				// check if P(z) at c diverges to infinity, or is in Mandelbrot set
				while(m < threshold && count < max_iter){
					z = eval(z, c);
					count++;
					m = magnitude2(z);
				}

				// color point based on the value of count when the prior loop exits
				var color = colors[count];
				if((count < max_iter) && smooth){ // smooth coloring
					var log_zn = Math.log(m)/2;
    					var nu = Math.log(log_zn/l2)/l2;
					var it = count + 1 - nu;
					color = colors[Math.floor(it)];
					var color2 = colors[Math.floor(it)+1];
					color = linear_interpolate(color, color2, it%1);
				}

				context.fillStyle = color;
				context.fillRect(x, y, ptw, ptw);
			}
		}

		xcurr += xd;

	}, 1);

	return timer;
}

/**
 * Evaluates the complex equation P(z) = z^2 + c (Mandelbrot Set)
 *
 * @param {Complex} variable - the value of the complex variable z
 * @param {Complex} constant - the value of the complex constant c (representing the coordinates of the point being iterated over)
 * @return {Complex} - the evaluated complex number P(z)
 */
function eval(variable, constant){
	// var z = {"imaginary": Math.abs(variable["imaginary"]), "real": Math.abs(variable["real"])}; // burning ship fractal
	var mult = multiply(variable, variable);
	return add(constant, mult);
}

// Coloring Methods

/**
 * Linear interpolates an intermediate color between the given ones, using given slope
 * @param {Color} color1 - first color
 * @param {Color} color2 - second color
 * @param {Number} slope - value of slope for linterpolation
 * @return {Color} - value of intermediate color
 */
function linear_interpolate(color1, color2, slope){
	var r1 = parseInt(color1.substring(1,3), 16);
	var r2 = parseInt(color2.substring(1,3), 16);
	var r = Math.floor(Math.abs(r1 + slope*(r2 - r1))).toString(16);
	r = (r.length == 2) ? r : "0" + r;

	var g1 = parseInt(color1.substring(3,5), 16);
	var g2 = parseInt(color2.substring(3,5), 16);
	var g = Math.floor(Math.abs(g1 + slope*(g2 - g1))).toString(16);
	g = (g.length == 2) ? g : "0" + g;

	var b1 = parseInt(color1.substring(5,7), 16);
	var b2 = parseInt(color2.substring(5,7), 16);
	var b = Math.floor(Math.abs(b1 + slope*(b2 - b1))).toString(16);
	b = (b.length == 2) ? b : "0" + b;

	var c = "#" + r + g + b;
	return c;
}

/**
 * Generates a smooth palette of colors based on the iteration-count, last color is always black
 * @requires - max_iter > 0
 * @param {Integer} max_iter - maximum number of iterations to be evaluated
 * @return {Color[]} - returns an array of {Color} of size max_iter + 1
 */
function palette(max_iter){
	var end_color = '#773399';
	var mid_color = '#CCAA66';
        var colors = new Array(max_iter+1);

	var factor = Math.PI/(2*Math.max(1, max_iter-1));
        for(var i=0; i<max_iter; i++){
		colors[i] = linear_interpolate(end_color, mid_color, Math.sin(i*factor));
        }
        colors[max_iter] = "#000000";

	return colors;
}

// Complex number operands

/**
 * Adds two complex numbers
 * @param {Complex} complex1
 * @param {Complex} complex2
 * @return {Complex} - complex sum of complex1 and complex2
 */
function add(complex1, complex2){
	return {"imaginary": complex1["imaginary"] + complex2["imaginary"],
		"real": complex1["real"] + complex2["real"]};
}

/**
 * Multiplies two complex numbers
 * @param {Complex} complex1
 * @param {Complex} complex2
 * @return {Complex} - complex product of complex1 and complex2
 */
function multiply(complex1, complex2){
	return {"imaginary": complex1["real"]*complex2["imaginary"] + complex1["imaginary"]*complex2["real"],
		"real": complex1["real"]*complex2["real"] - complex1["imaginary"]*complex2["imaginary"]};
}

/**
 * Finds square of magnitude of a complex number
 * @param {Complex} complex
 * @return {Complex} - magnitude of complex squared
 */
function magnitude2(complex){
	return complex["imaginary"]*complex["imaginary"] + complex["real"]*complex["real"];
}

<|endoftext|>


<commit_before>/* Copyright 2013 Florent Galland & Luc Verdier

This file is part of glark.io.

glark.io is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option) any later version.

glark.io is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with glark.io.  If not, see <http://www.gnu.org/licenses/>. */
'use strict';

/* Services */

angular.module('glark.services', ['glark.filters'])
    .factory('ace', function () {
        return ace.edit('editor');
    })

    .factory('EditSession', function () {
        return ace.require("ace/edit_session").EditSession;
    })

    .factory('editor', function (EditSession, ace) {
        ace.setTheme("ace/theme/twilight");

        return {
            focus: function () {
                setTimeout(function () {
                    ace.focus();
                }, 0);
            },

            setSession: function (session) {
                session.setFoldStyle('markbegin');

                ace.setSession(session);
                ace.setReadOnly(false);
            },

            clearSession: function () {
                ace.setSession(new EditSession(''));
                ace.setReadOnly(true);
            },

            goToLine: function (lineNumber) {
                ace.gotoLine(lineNumber);
            },

            find: function (value) {
                return ace.find(value);
            },

            findNext: function () {
                return ace.findNext();
            },

            findPrevious: function () {
                return ace.findPrevious();
            }
        };
    })

    /* Helper providing services to manage the layout . */
    .factory('layout', function () {
        var layout = {};

        var components = {};
        var styles = {};
        var resizes = {};

        /* Register a new component. */
        layout.registerComponent = function (name, selector, style, resizeWidth, resizeHeight) {
            var $component = angular.element(selector);
            $component.css(style);

            components[name] = $component;
            styles[name] = style;

            resizes[name] = {};
            if(resizeWidth !== undefined) {
                resizes[name].width = resizeWidth;
            }
            else {
                resizes[name].width = function () {};
            }

            if(resizeHeight !== undefined) {
                resizes[name].height = resizeHeight;
            }
            else {
                resizes[name].height = function () {};
            }
        };

        /* Reset the layout. */
        layout.resetLayout = function () {
            angular.forEach(components, function ($component, name) {
                $component.css(styles[name]);
            });
        };

        /* @param name is the component name. */
        layout.getWidth = function (name) {
            return components[name].width();
        };

        /* @param name is the component name.
         * @param width is in pixel. */
        layout.setWidth = function (name, width) {
            resizes[name].width(width);
        };

        /* Register default components. */
        layout.registerComponent('editor', '#editor', {
            top: '35px',
            bottom: '0',
            left: '150px',
            right: '0'
        });

        layout.registerComponent('top-bar', '#editor-top-bar', {
            top: '0',
            height: '35px',
            left: '150px',
            right: '0'
        });

        layout.registerComponent('left-bar', '#editor-left-bar', {
            top: '0',
            bottom: '0',
            left: '0',
            width: '150px'
        }, function (width) {
            if(width<50) return;
            components['editor'].css('left', width + 'px');
            components['top-bar'].css('left', width + 'px');
            components['left-bar'].css('width', width + 'px');
        });

        /* Reset the layout at the first access. */
        layout.resetLayout();

        return layout;
    })

    /* Helper providing services to handle the html5 filesystem api. */
    .factory('filesystem', function ($q, $rootScope) {
        return {
            getFileContent: function (fileEntry) {
                var defered = $q.defer();
                var reader = new FileReader();
                reader.onload = function (event) {
                    defered.resolve(event.target.result);
                    $rootScope.$digest();
                };
                reader.readAsText(fileEntry);
                return defered.promise;
            }
        };
    })

    /* Create a glark.services.File object from a html5 File or Blob object. */
    .factory('File', function (filesystem, EditSession, basenameFilter) {

        return function (fileEntry) {
            var file = this;

            file.fileEntry = fileEntry;

            file.name = fileEntry.name;
            file.basename = '/';
            if (typeof fileEntry.fullPath !== 'undefined') {
                file.basename = basenameFilter(fileEntry.fullPath);
            }

            file.session = new EditSession('');

            // TODO: Should be changed.
            file.session.setMode("ace/mode/javascript");

            if (typeof fileEntry.file === 'function') {
                fileEntry.file(function (blob) {
                    var promise = filesystem.getFileContent(blob);
                    promise.then(function (content) {
                        file.session.setValue(content, -1);
                    });
                });
            } else {
                var promise = filesystem.getFileContent(fileEntry);
                promise.then(function (content) {
                    file.session.setValue(content, -1);
                });
            }
        };
    })

    /* Main model of the glark.io application. Contains among other all the
     * data describing the files of the workspace, the open ones and the active
     * one. */
    .factory('workspace', function (editor) {
        var workspace = {};

        var activeFile = null;

        /* Files of the workspace. A collection of glark.services.File object. */
        workspace.files = [];

        /* A file tree representation of the files collection. */
        workspace.tree = {directory: '/', containedDirectories: {}, containedFiles: {}};

        /* @param file is a glark.services.File object. */
        workspace.addFile = function (file) {
            if (this.files.indexOf(file) == -1) {
                this.files.push(file);
                this.updateTree();
            }
        };

        /* @param file is a glark.services.File object. */
        workspace.removeFile = function (file) {
            var idx = this.files.indexOf(file);
            if (idx != -1) {
                this.files.splice(idx, 1);
                file.session.setValue('', -1);
                this.updateTree();
                return true;
            }
            return false;
        };

        /* @return the number of files. */
        workspace.getFileCount = function () {
            return this.files.length;
        };

        /* @return the active glark.services.File file. */
        workspace.getActiveFile = function () {
            return activeFile;
        };

        /* @param file is a glark.services.File object. */
        workspace.setActiveFile = function (file) {
            this.addFile(file);
            activeFile = file;
            editor.setSession(file.session);
        };

        /* @param file is a glark.services.File object. */
        workspace.isActiveFile = function (file) {
            return file == activeFile;
        };

        /* Update the file tree description when the files collection changes. */
        workspace.updateTree = function () {
            var buildDirectoryEntry = function (splitBasename) {
                var dirEntry = {directory: splitBasename[0], containedDirectories: {}, containedFiles: {}};
                for (var dirIdx = 1; dirIdx < splitBasename.length; ++dirIdx) {
                    var dirName = splitBasename[dirIdx];
                    if (!(dirName in dirEntry.containedDirectories)) {
                        dirEntry.containedDirectories[dirName] = buildDirectoryEntry(splitBasename.slice(1));
                    }
                }

                return dirEntry;
            };

            for (var fileIdx = 0; fileIdx < workspace.files.length; ++fileIdx) {
                /* Split and remove empty elements. */
                var splitBasename = [];
                var split = this.files[fileIdx].basename.split('/');
                for (var i = 0; i < split.length; ++i) {
                    if (split[i] !== '') {
                        splitBasename.push(split[i]);
                    }
                }

                if (splitBasename.length > 0) {
                    var parentEntry = this.tree;
                    for (var dirIdx = 0; dirIdx < splitBasename.length; ++dirIdx) {
                        if (splitBasename[dirIdx] in parentEntry.containedDirectories) {
                            parentEntry = parentEntry.containedDirectories[splitBasename[dirIdx]];
                        } else {
                            var dirEntry = buildDirectoryEntry(splitBasename.slice(dirIdx));
                            parentEntry.containedDirectories[splitBasename[dirIdx]] = dirEntry;
                        }
                    }
                }
            }
        };

        return workspace;
    });
<commit_msg>Implement workspace.tree<commit_after>/* Copyright 2013 Florent Galland & Luc Verdier

This file is part of glark.io.

glark.io is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
at your option) any later version.

glark.io is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with glark.io.  If not, see <http://www.gnu.org/licenses/>. */
'use strict';

/* Services */

angular.module('glark.services', ['glark.filters'])
    .factory('ace', function () {
        return ace.edit('editor');
    })

    .factory('EditSession', function () {
        return ace.require("ace/edit_session").EditSession;
    })

    .factory('editor', function (EditSession, ace) {
        ace.setTheme("ace/theme/twilight");

        return {
            focus: function () {
                setTimeout(function () {
                    ace.focus();
                }, 0);
            },

            setSession: function (session) {
                session.setFoldStyle('markbegin');

                ace.setSession(session);
                ace.setReadOnly(false);
            },

            clearSession: function () {
                ace.setSession(new EditSession(''));
                ace.setReadOnly(true);
            },

            goToLine: function (lineNumber) {
                ace.gotoLine(lineNumber);
            },

            find: function (value) {
                return ace.find(value);
            },

            findNext: function () {
                return ace.findNext();
            },

            findPrevious: function () {
                return ace.findPrevious();
            }
        };
    })

    /* Helper providing services to manage the layout . */
    .factory('layout', function () {
        var layout = {};

        var components = {};
        var styles = {};
        var resizes = {};

        /* Register a new component. */
        layout.registerComponent = function (name, selector, style, resizeWidth, resizeHeight) {
            var $component = angular.element(selector);
            $component.css(style);

            components[name] = $component;
            styles[name] = style;

            resizes[name] = {};
            if(resizeWidth !== undefined) {
                resizes[name].width = resizeWidth;
            }
            else {
                resizes[name].width = function () {};
            }

            if(resizeHeight !== undefined) {
                resizes[name].height = resizeHeight;
            }
            else {
                resizes[name].height = function () {};
            }
        };

        /* Reset the layout. */
        layout.resetLayout = function () {
            angular.forEach(components, function ($component, name) {
                $component.css(styles[name]);
            });
        };

        /* @param name is the component name. */
        layout.getWidth = function (name) {
            return components[name].width();
        };

        /* @param name is the component name.
         * @param width is in pixel. */
        layout.setWidth = function (name, width) {
            resizes[name].width(width);
        };

        /* Register default components. */
        layout.registerComponent('editor', '#editor', {
            top: '35px',
            bottom: '0',
            left: '150px',
            right: '0'
        });

        layout.registerComponent('top-bar', '#editor-top-bar', {
            top: '0',
            height: '35px',
            left: '150px',
            right: '0'
        });

        layout.registerComponent('left-bar', '#editor-left-bar', {
            top: '0',
            bottom: '0',
            left: '0',
            width: '150px'
        }, function (width) {
            if(width<50) return;
            components['editor'].css('left', width + 'px');
            components['top-bar'].css('left', width + 'px');
            components['left-bar'].css('width', width + 'px');
        });

        /* Reset the layout at the first access. */
        layout.resetLayout();

        return layout;
    })

    /* Helper providing services to handle the html5 filesystem api. */
    .factory('filesystem', function ($q, $rootScope) {
        return {
            getFileContent: function (fileEntry) {
                var defered = $q.defer();
                var reader = new FileReader();
                reader.onload = function (event) {
                    defered.resolve(event.target.result);
                    $rootScope.$digest();
                };
                reader.readAsText(fileEntry);
                return defered.promise;
            }
        };
    })

    /* Create a glark.services.File object from a html5 File or Blob object. */
    .factory('File', function (filesystem, EditSession, basenameFilter) {

        return function (fileEntry) {
            var file = this;

            file.fileEntry = fileEntry;

            file.name = fileEntry.name;
            file.basename = '/';
            if (typeof fileEntry.fullPath !== 'undefined') {
                file.basename = basenameFilter(fileEntry.fullPath);
            }

            file.session = new EditSession('');

            // TODO: Should be changed.
            file.session.setMode("ace/mode/javascript");

            if (typeof fileEntry.file === 'function') {
                fileEntry.file(function (blob) {
                    var promise = filesystem.getFileContent(blob);
                    promise.then(function (content) {
                        file.session.setValue(content, -1);
                    });
                });
            } else {
                var promise = filesystem.getFileContent(fileEntry);
                promise.then(function (content) {
                    file.session.setValue(content, -1);
                });
            }
        };
    })

    /* Main model of the glark.io application. Contains among other all the
     * data describing the files of the workspace, the open ones and the active
     * one. */
    .factory('workspace', function (editor) {
        var workspace = {};

        var activeFile = null;

        /* Files of the workspace. A collection of glark.services.File object. */
        workspace.files = [];

        /* A file tree representation of the files collection. */
        workspace.tree = {directory: '/', containedDirectories: {}, containedFiles: {}};

        /* @param file is a glark.services.File object. */
        workspace.addFile = function (file) {
            if (this.files.indexOf(file) == -1) {
                this.files.push(file);
                this.updateTree();
            }
        };

        /* @param file is a glark.services.File object. */
        workspace.removeFile = function (file) {
            var idx = this.files.indexOf(file);
            if (idx != -1) {
                this.files.splice(idx, 1);
                file.session.setValue('', -1);
                this.updateTree();
                return true;
            }
            return false;
        };

        /* @return the number of files. */
        workspace.getFileCount = function () {
            return this.files.length;
        };

        /* @return the active glark.services.File file. */
        workspace.getActiveFile = function () {
            return activeFile;
        };

        /* @param file is a glark.services.File object. */
        workspace.setActiveFile = function (file) {
            this.addFile(file);
            activeFile = file;
            editor.setSession(file.session);
        };

        /* @param file is a glark.services.File object. */
        workspace.isActiveFile = function (file) {
            return file == activeFile;
        };

        /* Update the file tree description when the files collection changes. */
        workspace.updateTree = function () {
            var buildDirectoryEntry = function (splitBasename) {
                var dirEntry = {directory: splitBasename[0], containedDirectories: {}, containedFiles: {}};
                for (var dirIdx = 1; dirIdx < splitBasename.length; ++dirIdx) {
                    var dirName = splitBasename[dirIdx];
                    if (!(dirName in dirEntry.containedDirectories)) {
                        dirEntry.containedDirectories[dirName] = buildDirectoryEntry(splitBasename.slice(1));
                    }
                }

                return dirEntry;
            };

            for (var fileIdx = 0; fileIdx < workspace.files.length; ++fileIdx) {
                /* Split and remove empty elements. */
                var splitBasename = [];
                var split = this.files[fileIdx].basename.split('/');
                for (var i = 0; i < split.length; ++i) {
                    if (split[i] !== '') {
                        splitBasename.push(split[i]);
                    }
                }

                var parentEntry = this.tree;
                if (splitBasename.length > 0) {
                    for (var dirIdx = 0; dirIdx < splitBasename.length; ++dirIdx) {
                        if (splitBasename[dirIdx] in parentEntry.containedDirectories) {
                            parentEntry = parentEntry.containedDirectories[splitBasename[dirIdx]];
                        } else {
                            var dirEntry = buildDirectoryEntry(splitBasename.slice(dirIdx));
                            parentEntry.containedDirectories[splitBasename[dirIdx]] = dirEntry;
                            parentEntry = dirEntry;
                        }
                    }
                }

                /* Now add the file if necessary. */
                if (!(this.files[fileIdx] in parentEntry.containedFiles)) {
                    parentEntry.containedFiles[this.files[fileIdx].name] = this.files[fileIdx];
                }
            }
        };

        return workspace;
    });
<|endoftext|>


<commit_before>'use strict';
var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert');

function getDictSize(dict){
	var dictSize = 0;
	var rl = readline.createInterface({
		input: fs.createReadStream(dict),
		output: process.stdout,
		terminal: false
	});
	rl.on('line',function(line){
		dictSize += 1;
	});
	return dictSize;
}

function getCurrCode(mongoURI){
	return new Promise(function (resolve, rejedct){
		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("GET CURRCODE: OPENING", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.find().toArray(function(err, res) {
					if(err){
						console.log("GET CURRCODE: READING", err);
					} else{
						console.log(res[0]);
						resolve(res[0].codeCounter);
						//
					}
					})
				}
			});
	});
}

function makeNewCode(currCode, dictSize){

}

/*needs the mongoURI and the local dictionary JSON file path
chose to take in mongoURI rather than get from process because this is module*/
module.exports.genCode = function(dict, mongoURI){
	var dictSize = getDictSize(dict);
	var currCode = getCurrCode(mongoURI);
	console.log("get Promise: " + currCode);
	var newCode = makeNewCode(currCode, dictSize);
	return currCode;
}
<commit_msg>reject typo<commit_after>'use strict';
var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert');

function getDictSize(dict){
	var dictSize = 0;
	var rl = readline.createInterface({
		input: fs.createReadStream(dict),
		output: process.stdout,
		terminal: false
	});
	rl.on('line',function(line){
		dictSize += 1;
	});
	return dictSize;
}

function getCurrCode(mongoURI){
	return new Promise(function (resolve, reject){
		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("GET CURRCODE: OPENING", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.find().toArray(function(err, res) {
					if(err){
						console.log("GET CURRCODE: READING", err);
					} else{
						console.log(res[0]);
						resolve(res[0].codeCounter);
						//
					}
					})
				}
			});
	});
}

function makeNewCode(currCode, dictSize){

}

/*needs the mongoURI and the local dictionary JSON file path
chose to take in mongoURI rather than get from process because this is module*/
module.exports.genCode = function(dict, mongoURI){
	var dictSize = getDictSize(dict);
	var currCode = getCurrCode(mongoURI);
	console.log("get Promise: " + currCode);
	var newCode = makeNewCode(currCode, dictSize);
	return currCode;
}
<|endoftext|>


<commit_before>var gamestate = {};
gamestate.planetData = [];
gamestate.planetData[0] = {name:"Earth"  ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[1] = {name:"Mars"   ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[2] = {name:"Jupiter",technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[3] = {name:"Saturn" ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[4] = {name:"Uranus" ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[5] = {name:"Neptune",technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.currentPlanet = 0;
gamestate.costCache = [];

function onStart(){
  //random gen gamestate.planetData
  //gen player stats
  onArrive();
}
function onLoadGame(){
  //Load gamestate.planetData
  //load player stats
  onArrive();
}
function onArrive(){
  timeStep();
  gamestate.costCache = generateCosts(gamestate.currentPlanet);
  loadPlanetInterface(gamestate.currentPlanet);
}

function onLeave(){
  clearPlanetInterface();
  hideInfo();
  updateInventory();
}

function generateWorlds(){

}

function timeStep(){

}

function generateCosts(planet){
  var out = {};
  for(var item in itemConfig){
    out[item] = itemConfig[item](gamestate.planetData[planet]); //returns [supply, demand]
    out[item] = out[item][1]/out[item][0];
  }
  return out;
}

var itemConfig =
{
  computers:function(traits){return [traits.production*traits.technology,traits.population/traits.minerals]},
  fuel:function(traits){return [traits.production*traits.fuel,traits.population/traits.technology]}
};

gamestate.playerData = {reputation:[0,0,0,0,0,0]};
gamestate.playerData.inventory = {"credits":1000,"fuel":1000};
gamestate.playerData.ship = {engine:{},cargobay:{}};

function jump(to){
  var d = getDistance(to,gamestate.currentPlanet);
  var cost = ((d/30) | 0);
  if(gamestate.playerData.inventory.fuel - cost >= 0){
    gamestate.playerData.inventory.fuel -= cost;
    fly(to,gamestate.currentPlanet);
    gamestate.currentPlanet = to;
    onLeave();
  }else{
    showNotification('You do not have enough fuel to complete the journey');
  }
}

function transaction(itemName,itemQuantity) {
  // item quantity + inventory quantity >= 0 && player credits + (itemQuantity * item cost on planet) >= 0
  if (itemQuantity + gamestate.playerData.inventory[itemName] >= 0 && gamestate.playerData.inventory.credits + (itemQuantity * gamestate.costCache[itemName]) >= 0 ) {
    gamestate.playerData.inventory[itemName] += itemQuantity;
    gamestate.playerData.inventory.credits += (itemQuantity * gamestate.costCache[itemName]);
    updateInventory();
  } else{
    showNotification("You cannot afford this transaction");
  };
}<commit_msg>added saving and loading<commit_after>var gamestate = {};
gamestate.planetData = [];
gamestate.planetData[0] = {name:"Earth"  ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[1] = {name:"Mars"   ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[2] = {name:"Jupiter",technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[3] = {name:"Saturn" ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[4] = {name:"Uranus" ,technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.planetData[5] = {name:"Neptune",technology:0,minerals:0,fuel:0,food:0,antimatter:0,production:0,population:0,society:0};
gamestate.currentPlanet = 0;
gamestate.costCache = [];

function onStart(){

  onArrive();
}

function newGame(){
  //random gen gamestate.planetData
  //gen player state
  onStart();
}

function loadGame(name){
  gamestate = JSON.parse(localStorage[name]);
  onStart();
}

function saveGame(name){
  localStorage.setItem(name,JSON.stringify(gamestate));
}

function onArrive(){
  timeStep();
  gamestate.costCache = generateCosts(gamestate.currentPlanet);
  loadPlanetInterface(gamestate.currentPlanet);
}

function onLeave(){
  clearPlanetInterface();
  hideInfo();
  updateInventory();
}

function generateWorlds(){

}

function timeStep(){

}

function generateCosts(planet){
  var out = {};
  for(var item in itemConfig){
    out[item] = itemConfig[item](gamestate.planetData[planet]); //returns [supply, demand]
    out[item] = out[item][1]/out[item][0];
  }
  return out;
}

var itemConfig =
{
  computers:function(traits){return [traits.production*traits.technology,traits.population/traits.minerals]},
  fuel:function(traits){return [traits.production*traits.fuel,traits.population/traits.technology]}
};

gamestate.playerData = {reputation:[0,0,0,0,0,0]};
gamestate.playerData.inventory = {"credits":1000,"fuel":1000};
gamestate.playerData.ship = {engine:{},cargobay:{}};

function jump(to){
  var d = getDistance(to,gamestate.currentPlanet);
  var cost = ((d/30) | 0);
  if(gamestate.playerData.inventory.fuel - cost >= 0){
    gamestate.playerData.inventory.fuel -= cost;
    fly(to,gamestate.currentPlanet);
    gamestate.currentPlanet = to;
    onLeave();
  }else{
    showNotification('You do not have enough fuel to complete the journey');
  }
}

function transaction(itemName,itemQuantity) {
  // item quantity + inventory quantity >= 0 && player credits + (itemQuantity * item cost on planet) >= 0
  if (itemQuantity + gamestate.playerData.inventory[itemName] >= 0 && gamestate.playerData.inventory.credits + (itemQuantity * gamestate.costCache[itemName]) >= 0 ) {
    gamestate.playerData.inventory[itemName] += itemQuantity;
    gamestate.playerData.inventory.credits += (itemQuantity * gamestate.costCache[itemName]);
    updateInventory();
  } else{
    showNotification("You cannot afford this transaction");
  };
}<|endoftext|>


<commit_before>var url = Npm.require('url');

// By default, we use the permessage-deflate extension with default
// configuration. If $SERVER_WEBSOCKET_COMPRESSION is set, then it must be valid
// JSON. If it represents a falsey value, then we do not use permessage-deflate
// at all; otherwise, the JSON value is used as an argument to deflate's
// configure method; see
// https://github.com/faye/permessage-deflate-node/blob/master/README.md
//
// (We do this in an _.once instead of at startup, because we don't want to
// crash the tool during isopacket load if your JSON doesn't parse. This is only
// a problem because the tool has to load the DDP server code just in order to
// be a DDP client; see https://github.com/meteor/meteor/issues/3452 .)
var websocketExtensions = _.once(function () {
  var extensions = [];

  var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION
        ? JSON.parse(process.env.SERVER_WEBSOCKET_COMPRESSION) : {};
  if (websocketCompressionConfig) {
    extensions.push(Npm.require('permessage-deflate').configure(
      websocketCompressionConfig
    ));
  }

  return extensions;
});

var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX ||  "";

StreamServer = function () {
  var self = this;
  self.registration_callbacks = [];
  self.open_sockets = [];

  // Because we are installing directly onto WebApp.httpServer instead of using
  // WebApp.app, we have to process the path prefix ourselves.
  self.prefix = pathPrefix + '/sockjs';
  RoutePolicy.declare(self.prefix + '/', 'network');

  // set up sockjs
  var sockjs = Npm.require('sockjs');
  var serverOptions = {
    prefix: self.prefix,
    log: function() {},
    // this is the default, but we code it explicitly because we depend
    // on it in stream_client:HEARTBEAT_TIMEOUT
    heartbeat_delay: 45000,
    // The default disconnect_delay is 5 seconds, but if the server ends up CPU
    // bound for that much time, SockJS might not notice that the user has
    // reconnected because the timer (of disconnect_delay ms) can fire before
    // SockJS processes the new connection. Eventually we'll fix this by not
    // combining CPU-heavy processing with SockJS termination (eg a proxy which
    // converts to Unix sockets) but for now, raise the delay.
    disconnect_delay: 60 * 1000,
    // Set the USE_JSESSIONID environment variable to enable setting the
    // JSESSIONID cookie. This is useful for setting up proxies with
    // session affinity.
    jsessionid: !!process.env.USE_JSESSIONID
  };

  // If you know your server environment (eg, proxies) will prevent websockets
  // from ever working, set $DISABLE_WEBSOCKETS and SockJS clients (ie,
  // browsers) will not waste time attempting to use them.
  // (Your server will still have a /websocket endpoint.)
  if (process.env.DISABLE_WEBSOCKETS) {
    serverOptions.websocket = false;
  } else {
    serverOptions.faye_server_options = {
      extensions: websocketExtensions()
    };
  }

  self.server = sockjs.createServer(serverOptions);

  // Install the sockjs handlers, but we want to keep around our own particular
  // request handler that adjusts idle timeouts while we have an outstanding
  // request.  This compensates for the fact that sockjs removes all listeners
  // for "request" to add its own.
  WebApp.httpServer.removeListener(
    'request', WebApp._timeoutAdjustmentRequestCallback);
  self.server.installHandlers(WebApp.httpServer);
  WebApp.httpServer.addListener(
    'request', WebApp._timeoutAdjustmentRequestCallback);

  // Support the /websocket endpoint
  self._redirectWebsocketEndpoint();

  self.server.on('connection', function (socket) {
    // We want to make sure that if a client connects to us and does the initial
    // Websocket handshake but never gets to the DDP handshake, that we
    // eventually kill the socket.  Once the DDP handshake happens, DDP
    // heartbeating will work. And before the Websocket handshake, the timeouts
    // we set at the server level in webapp_server.js will work. But
    // faye-websocket calls setTimeout(0) on any socket it takes over, so there
    // is an "in between" state where this doesn't happen.  We work around this
    // by explicitly setting the socket timeout to a relatively large time here,
    // and setting it back to zero when we set up the heartbeat in
    // livedata_server.js.
    socket.setWebsocketTimeout = function (timeout) {
      if ((socket.protocol === 'websocket' ||
           socket.protocol === 'websocket-raw')
          && socket._session.recv) {
        socket._session.recv.connection.setTimeout(timeout);
      }
    };
    socket.setWebsocketTimeout(45 * 1000);

    socket.send = function (data) {
      socket.write(data);
    };
    socket.on('close', function () {
      self.open_sockets = _.without(self.open_sockets, socket);
    });
    self.open_sockets.push(socket);

    // XXX COMPAT WITH 0.6.6. Send the old style welcome message, which
    // will force old clients to reload. Remove this once we're not
    // concerned about people upgrading from a pre-0.7.0 release. Also,
    // remove the clause in the client that ignores the welcome message
    // (livedata_connection.js)
    socket.send(JSON.stringify({server_id: "0"}));

    // call all our callbacks when we get a new socket. they will do the
    // work of setting up handlers and such for specific messages.
    _.each(self.registration_callbacks, function (callback) {
      callback(socket);
    });
  });

};

_.extend(StreamServer.prototype, {
  // call my callback when a new socket connects.
  // also call it for all current connections.
  register: function (callback) {
    var self = this;
    self.registration_callbacks.push(callback);
    _.each(self.all_sockets(), function (socket) {
      callback(socket);
    });
  },

  // get a list of all sockets
  all_sockets: function () {
    var self = this;
    return _.values(self.open_sockets);
  },

  // Redirect /websocket to /sockjs/websocket in order to not expose
  // sockjs to clients that want to use raw websockets
  _redirectWebsocketEndpoint: function() {
    var self = this;
    // Unfortunately we can't use a connect middleware here since
    // sockjs installs itself prior to all existing listeners
    // (meaning prior to any connect middlewares) so we need to take
    // an approach similar to overshadowListeners in
    // https://github.com/sockjs/sockjs-node/blob/cf820c55af6a9953e16558555a31decea554f70e/src/utils.coffee
    _.each(['request', 'upgrade'], function(event) {
      var httpServer = WebApp.httpServer;
      var oldHttpServerListeners = httpServer.listeners(event).slice(0);
      httpServer.removeAllListeners(event);

      // request and upgrade have different arguments passed but
      // we only care about the first one which is always request
      var newListener = function(request /*, moreArguments */) {
        // Store arguments for use within the closure below
        var args = arguments;

        // Rewrite /websocket and /websocket/ urls to /sockjs/websocket while
        // preserving query string.
        var parsedUrl = url.parse(request.url);
        if (parsedUrl.pathname === pathPrefix + '/websocket' ||
            parsedUrl.pathname === pathPrefix + '/websocket/') {
          parsedUrl.pathname = self.prefix + '/websocket';
          request.url = url.format(parsedUrl);
        }
        _.each(oldHttpServerListeners, function(oldListener) {
          oldListener.apply(httpServer, args);
        });
      };
      httpServer.addListener(event, newListener);
    });
  }
});
<commit_msg>Add workaround for suspected sockjs bug<commit_after>var url = Npm.require('url');

// By default, we use the permessage-deflate extension with default
// configuration. If $SERVER_WEBSOCKET_COMPRESSION is set, then it must be valid
// JSON. If it represents a falsey value, then we do not use permessage-deflate
// at all; otherwise, the JSON value is used as an argument to deflate's
// configure method; see
// https://github.com/faye/permessage-deflate-node/blob/master/README.md
//
// (We do this in an _.once instead of at startup, because we don't want to
// crash the tool during isopacket load if your JSON doesn't parse. This is only
// a problem because the tool has to load the DDP server code just in order to
// be a DDP client; see https://github.com/meteor/meteor/issues/3452 .)
var websocketExtensions = _.once(function () {
  var extensions = [];

  var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION
        ? JSON.parse(process.env.SERVER_WEBSOCKET_COMPRESSION) : {};
  if (websocketCompressionConfig) {
    extensions.push(Npm.require('permessage-deflate').configure(
      websocketCompressionConfig
    ));
  }

  return extensions;
});

var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX ||  "";

StreamServer = function () {
  var self = this;
  self.registration_callbacks = [];
  self.open_sockets = [];

  // Because we are installing directly onto WebApp.httpServer instead of using
  // WebApp.app, we have to process the path prefix ourselves.
  self.prefix = pathPrefix + '/sockjs';
  RoutePolicy.declare(self.prefix + '/', 'network');

  // set up sockjs
  var sockjs = Npm.require('sockjs');
  var serverOptions = {
    prefix: self.prefix,
    log: function() {},
    // this is the default, but we code it explicitly because we depend
    // on it in stream_client:HEARTBEAT_TIMEOUT
    heartbeat_delay: 45000,
    // The default disconnect_delay is 5 seconds, but if the server ends up CPU
    // bound for that much time, SockJS might not notice that the user has
    // reconnected because the timer (of disconnect_delay ms) can fire before
    // SockJS processes the new connection. Eventually we'll fix this by not
    // combining CPU-heavy processing with SockJS termination (eg a proxy which
    // converts to Unix sockets) but for now, raise the delay.
    disconnect_delay: 60 * 1000,
    // Set the USE_JSESSIONID environment variable to enable setting the
    // JSESSIONID cookie. This is useful for setting up proxies with
    // session affinity.
    jsessionid: !!process.env.USE_JSESSIONID
  };

  // If you know your server environment (eg, proxies) will prevent websockets
  // from ever working, set $DISABLE_WEBSOCKETS and SockJS clients (ie,
  // browsers) will not waste time attempting to use them.
  // (Your server will still have a /websocket endpoint.)
  if (process.env.DISABLE_WEBSOCKETS) {
    serverOptions.websocket = false;
  } else {
    serverOptions.faye_server_options = {
      extensions: websocketExtensions()
    };
  }

  self.server = sockjs.createServer(serverOptions);

  // Install the sockjs handlers, but we want to keep around our own particular
  // request handler that adjusts idle timeouts while we have an outstanding
  // request.  This compensates for the fact that sockjs removes all listeners
  // for "request" to add its own.
  WebApp.httpServer.removeListener(
    'request', WebApp._timeoutAdjustmentRequestCallback);
  self.server.installHandlers(WebApp.httpServer);
  WebApp.httpServer.addListener(
    'request', WebApp._timeoutAdjustmentRequestCallback);

  // Support the /websocket endpoint
  self._redirectWebsocketEndpoint();

  self.server.on('connection', function (socket) {
    // Work around a bug in sockjs, where something (not sure exactly what,
    // possibly closing the connection unexpectedly during handshake) causes
    // this to get called with socket equal to null. In this case, just return;
    // we don't have a socket to work with, even to close it.
    if (!socket) {
      return;
    }

    // We want to make sure that if a client connects to us and does the initial
    // Websocket handshake but never gets to the DDP handshake, that we
    // eventually kill the socket.  Once the DDP handshake happens, DDP
    // heartbeating will work. And before the Websocket handshake, the timeouts
    // we set at the server level in webapp_server.js will work. But
    // faye-websocket calls setTimeout(0) on any socket it takes over, so there
    // is an "in between" state where this doesn't happen.  We work around this
    // by explicitly setting the socket timeout to a relatively large time here,
    // and setting it back to zero when we set up the heartbeat in
    // livedata_server.js.
    socket.setWebsocketTimeout = function (timeout) {
      if ((socket.protocol === 'websocket' ||
           socket.protocol === 'websocket-raw')
          && socket._session.recv) {
        socket._session.recv.connection.setTimeout(timeout);
      }
    };
    socket.setWebsocketTimeout(45 * 1000);

    socket.send = function (data) {
      socket.write(data);
    };
    socket.on('close', function () {
      self.open_sockets = _.without(self.open_sockets, socket);
    });
    self.open_sockets.push(socket);

    // XXX COMPAT WITH 0.6.6. Send the old style welcome message, which
    // will force old clients to reload. Remove this once we're not
    // concerned about people upgrading from a pre-0.7.0 release. Also,
    // remove the clause in the client that ignores the welcome message
    // (livedata_connection.js)
    socket.send(JSON.stringify({server_id: "0"}));

    // call all our callbacks when we get a new socket. they will do the
    // work of setting up handlers and such for specific messages.
    _.each(self.registration_callbacks, function (callback) {
      callback(socket);
    });
  });

};

_.extend(StreamServer.prototype, {
  // call my callback when a new socket connects.
  // also call it for all current connections.
  register: function (callback) {
    var self = this;
    self.registration_callbacks.push(callback);
    _.each(self.all_sockets(), function (socket) {
      callback(socket);
    });
  },

  // get a list of all sockets
  all_sockets: function () {
    var self = this;
    return _.values(self.open_sockets);
  },

  // Redirect /websocket to /sockjs/websocket in order to not expose
  // sockjs to clients that want to use raw websockets
  _redirectWebsocketEndpoint: function() {
    var self = this;
    // Unfortunately we can't use a connect middleware here since
    // sockjs installs itself prior to all existing listeners
    // (meaning prior to any connect middlewares) so we need to take
    // an approach similar to overshadowListeners in
    // https://github.com/sockjs/sockjs-node/blob/cf820c55af6a9953e16558555a31decea554f70e/src/utils.coffee
    _.each(['request', 'upgrade'], function(event) {
      var httpServer = WebApp.httpServer;
      var oldHttpServerListeners = httpServer.listeners(event).slice(0);
      httpServer.removeAllListeners(event);

      // request and upgrade have different arguments passed but
      // we only care about the first one which is always request
      var newListener = function(request /*, moreArguments */) {
        // Store arguments for use within the closure below
        var args = arguments;

        // Rewrite /websocket and /websocket/ urls to /sockjs/websocket while
        // preserving query string.
        var parsedUrl = url.parse(request.url);
        if (parsedUrl.pathname === pathPrefix + '/websocket' ||
            parsedUrl.pathname === pathPrefix + '/websocket/') {
          parsedUrl.pathname = self.prefix + '/websocket';
          request.url = url.format(parsedUrl);
        }
        _.each(oldHttpServerListeners, function(oldListener) {
          oldListener.apply(httpServer, args);
        });
      };
      httpServer.addListener(event, newListener);
    });
  }
});
<|endoftext|>


<commit_before>const electron = require('electron')

const Client = require('@bugsnag/core/client')
const Event = require('@bugsnag/core/event')
const Breadcrumb = require('@bugsnag/core/breadcrumb')
const Session = require('@bugsnag/core/session')

const makeDelivery = require('@bugsnag/delivery-electron')
const { FileStore } = require('@bugsnag/electron-filestore')
const { schema } = require('../config/main')

Event.__type = 'electronnodejs'

// noop native client for now
const NativeClient = {
  setApp () {},
  setDevice () {}
}

module.exports = (opts) => {
  const filestore = new FileStore(
    opts.apiKey,
    electron.app.getPath('userCache'),
    electron.app.getPath('crashDumps')
  )

  // main internal plugins go here
  const internalPlugins = [
    // THIS PLUGIN MUST BE FIRST!
    require('@bugsnag/plugin-internal-callback-marker').FirstPlugin,
    require('@bugsnag/plugin-electron-client-state-manager'),
    require('@bugsnag/plugin-electron-ipc'),
    require('@bugsnag/plugin-node-uncaught-exception'),
    require('@bugsnag/plugin-node-unhandled-rejection'),
    require('@bugsnag/plugin-electron-app')(NativeClient, process, electron.app, electron.BrowserWindow),
    require('@bugsnag/plugin-electron-app-breadcrumbs')(electron.app, electron.BrowserWindow),
    require('@bugsnag/plugin-electron-device')(electron.app, electron.screen, process, filestore, NativeClient, electron.powerMonitor),
    require('@bugsnag/plugin-electron-session')(electron.app, electron.BrowserWindow, filestore),
    require('@bugsnag/plugin-console-breadcrumbs'),
    require('@bugsnag/plugin-strip-project-root'),
    require('@bugsnag/plugin-electron-process-info')(),
    require('@bugsnag/plugin-electron-preload-error')(electron.app),
    require('@bugsnag/plugin-electron-net-breadcrumbs')(electron.net),
    // THIS PLUGIN MUST BE LAST!
    require('@bugsnag/plugin-internal-callback-marker').LastPlugin
  ]

  const bugsnag = new Client(opts, schema, internalPlugins, require('../id'))

  bugsnag._setDelivery(makeDelivery(filestore, electron.net, electron.app))

  bugsnag._logger.debug('Loaded! In main process.')

  // Normalise the project root upfront so renderers have a fully resolved path
  // The renderers can't do this themselves as they cannot access the 'path' module
  if (bugsnag._config.projectRoot) {
    const normalizePath = require('@bugsnag/core/lib/path-normalizer')
    bugsnag._config.projectRoot = normalizePath(bugsnag._config.projectRoot)
  }

  return bugsnag
}

module.exports.Client = Client
module.exports.Event = Event
module.exports.Session = Session
module.exports.Breadcrumb = Breadcrumb
<commit_msg>Normalise the project root earlier<commit_after>const electron = require('electron')

const Client = require('@bugsnag/core/client')
const Event = require('@bugsnag/core/event')
const Breadcrumb = require('@bugsnag/core/breadcrumb')
const Session = require('@bugsnag/core/session')

const makeDelivery = require('@bugsnag/delivery-electron')
const { FileStore } = require('@bugsnag/electron-filestore')
const { schema } = require('../config/main')

Event.__type = 'electronnodejs'

// noop native client for now
const NativeClient = {
  setApp () {},
  setDevice () {}
}

module.exports = (opts) => {
  const filestore = new FileStore(
    opts.apiKey,
    electron.app.getPath('userCache'),
    electron.app.getPath('crashDumps')
  )

  // Normalise the project root upfront so renderers have a fully resolved path
  // The renderers can't do this themselves as they cannot access the 'path' module
  if (opts.projectRoot) {
    const normalizePath = require('@bugsnag/core/lib/path-normalizer')
    opts.projectRoot = normalizePath(opts.projectRoot)
  }

  // main internal plugins go here
  const internalPlugins = [
    // THIS PLUGIN MUST BE FIRST!
    require('@bugsnag/plugin-internal-callback-marker').FirstPlugin,
    require('@bugsnag/plugin-electron-client-state-manager'),
    require('@bugsnag/plugin-electron-ipc'),
    require('@bugsnag/plugin-node-uncaught-exception'),
    require('@bugsnag/plugin-node-unhandled-rejection'),
    require('@bugsnag/plugin-electron-app')(NativeClient, process, electron.app, electron.BrowserWindow),
    require('@bugsnag/plugin-electron-app-breadcrumbs')(electron.app, electron.BrowserWindow),
    require('@bugsnag/plugin-electron-device')(electron.app, electron.screen, process, filestore, NativeClient, electron.powerMonitor),
    require('@bugsnag/plugin-electron-session')(electron.app, electron.BrowserWindow, filestore),
    require('@bugsnag/plugin-console-breadcrumbs'),
    require('@bugsnag/plugin-strip-project-root'),
    require('@bugsnag/plugin-electron-process-info')(),
    require('@bugsnag/plugin-electron-preload-error')(electron.app),
    require('@bugsnag/plugin-electron-net-breadcrumbs')(electron.net),
    // THIS PLUGIN MUST BE LAST!
    require('@bugsnag/plugin-internal-callback-marker').LastPlugin
  ]

  const bugsnag = new Client(opts, schema, internalPlugins, require('../id'))

  bugsnag._setDelivery(makeDelivery(filestore, electron.net, electron.app))

  bugsnag._logger.debug('Loaded! In main process.')

  return bugsnag
}

module.exports.Client = Client
module.exports.Event = Event
module.exports.Session = Session
module.exports.Breadcrumb = Breadcrumb
<|endoftext|>


<commit_before>import Emit from '@genxtract/extract/src/Emit.js';
import Extraction from '@genxtract/extract/src/Extraction.js';
import GedcomX from './GedcomX.js';

let extraction;
let emit;
let promise;

describe('GedcomX', () => {
  beforeEach(() => {
    extraction = new Extraction('test');
    emit = new Emit(extraction);
    const combinator = new GedcomX();
    promise = combinator.start();
    extraction.start();
  });

  it('basic person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234'});

    extraction.end();
  });

  it('primary person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          principle: true,
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234', primary: true});

    extraction.end();
  });

  it('gender', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          gender: {
            type: 'http://gedcomx.org/Male',
          },
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Gender({person: '1234', gender: 'Male'});

    extraction.end();
  });

  it('name', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Given', value: 'John'},
              ],
              fullText: 'John',
            }],
          }],
        },
        {
          id: '5678',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Given', value: 'John C'},
                {type: 'http://gedcomx.org/Surname', value: 'Smith'},
              ],
              fullText: 'John C Smith',
            }],
          }],
        },
        {
          id: '910',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Prefix', value: 'Sir'},
                {type: 'http://gedcomx.org/Given', value: 'Richard Charles Nicholas'},
                {type: 'http://gedcomx.org/Surname', value: 'Branson'},
                {type: 'http://gedcomx.org/Suffix', value: 'III'},
              ],
              fullText: 'Sir Richard Charles Nicholas Branson III',
            }],
          }],
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Name({person: '1234', name: 'John'});
    emit.Name({person: '1234', name: 'John'}); // making sure we ignore duplicate names
    emit.Name({person: '5678', name: 'John C Smith'});
    emit.Name({person: '5678', given: 'John C', surname: 'Smith'});
    emit.Name({person: '910', prefix: 'Sir', given: 'Richard Charles Nicholas', surname: 'Branson', suffix: 'III'});

    extraction.end();
  });

  it('basic events/facts', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [
            {type: 'http://gedcomx.org/Baptism', place: {original: 'Somewhere'}},
            {type: 'http://gedcomx.org/Death', date: {original: 'Sometime'}},
            {type: 'http://gedcomx.org/Citizenship', value: 'Somewhere'},
          ],
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Baptism({person: '1234', place: 'Somewhere'});
    emit.Death({person: '1234', date: 'Sometime'});
    emit.Citizenship({person: '1234', value: 'Somewhere'});

    extraction.end();
  });

  it('parent events', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {
            id: '12',
            facts: [
              {type: 'http://gedcomx.org/Birth', place: {original: 'Somewhere'}},
            ],
          },
          {id: '34'},
          {id: '56'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#34'},
            person2: {resource: '#12'},
          },
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#56'},
            person2: {resource: '#12'},
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Birth({person: '12', place: 'Somewhere', parents: ['34', '56']});

    extraction.end();
  });

  it('spouse events', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {id: '12'},
          {id: '34'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/Couple',
            person1: {resource: '#12'},
            person2: {resource: '#34'},
            facts: [
              {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}},
            ],
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Marriage({spouses: ['12', '34'], place: 'Somewhere'});

    extraction.end();
  });

  it('citation', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        description: '#1',
        persons: [
          {
            id: '12',
            sources: [{description: '#1'}],
          },
          {id: '34', sources: [{description: '#1'}]},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/Couple',
            person1: {resource: '#12'},
            person2: {resource: '#34'},
            facts: [
              {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}},
            ],
            sources: [{description: '#1'}],
          },
        ],
        agents: [{
          id: 'agent',
          names: [{lang: 'en', value: 'Example'}],
          homepage: {resource: 'http://example.com'},
        }],
        sourceDescriptions: [{
          id: '1',
          citations: [{
            value: 'The Title, Example (http://example.com/foo : accessed Fri May 12 2017)',
          }],
          about: 'http://example.com/foo',
          titles: [{value: 'The Title'}],
          repository: {resource: '#agent'},
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Marriage({spouses: ['12', '34'], place: 'Somewhere'});
    emit.Citation({
      title: 'The Title',
      url: 'http://example.com/foo',
      accessed: 1494603896789,
      repository_name: 'Example',
      repository_website: 'example.com',
      repository_url: 'http://example.com',
    });

    extraction.end();
  });

  it('should dedupe birth facts', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {
            id: '12',
            facts: [
              {
                type: 'http://gedcomx.org/Birth',
                date: {original: 'Sometime'},
                place: {original: 'Somewhere'},
              },
            ],
          },
          {id: '34'},
          {id: '56'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#34'},
            person2: {resource: '#12'},
          },
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#56'},
            person2: {resource: '#12'},
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Birth({person: '12', place: 'Somewhere', parents: []});
    emit.Birth({person: '12', parents: ['34', '56']});
    emit.Birth({person: '12', date: 'Sometime', parents: []});

    extraction.end();
  });

  it('alternate id', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          gender: {
            type: 'http://gedcomx.org/Female',
          },
        }, {
          id: 'xyz',
          gender: {
            type: 'http://gedcomx.org/Female',
          },
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234'});
    emit.AlternateId({person: '1234', id: '5678'});
    emit.Gender({person: '5678', gender: 'Female'});

    emit.Person({id: 'abc'});
    emit.AlternateId({person: 'abc', id: 'xyz', preferred: true});
    emit.Gender({person: 'xyz', gender: 'Female'});

    extraction.end();
  });

  it('external id');

  it('don\'t create relationships array unless a rel is added', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [{
            type: 'http://gedcomx.org/Birth',
            date: {
              original: 'Sometime',
            },
          }],
        }],
      });
      done();
    })
    .catch(done);

    emit.Birth({person: '1234', date: 'Sometime'});
    emit.Marriage({spouses: ['1234'], date: 'Sometime'});
    extraction.end();
  });

  it.only('marriage event with unknown spouse is added to the person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [
            {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}, date: {original: 'Sometime'}},
          ],
        }],
      });
      done();
    })
    .catch((error) => done(error));
    emit.Marriage({spouses: ['1234'], place: 'Somewhere', date: 'Sometime'});
    extraction.end();
  });
});

function extractionErrorListener(done) {
  window.addEventListener('genxtract', (e) => {
    if(e.detail.type === 'ERROR') {
      done(new Error(e.detail.data));
    };
  });
}
<commit_msg>Remove test exclusion<commit_after>import Emit from '@genxtract/extract/src/Emit.js';
import Extraction from '@genxtract/extract/src/Extraction.js';
import GedcomX from './GedcomX.js';

let extraction;
let emit;
let promise;

describe('GedcomX', () => {
  beforeEach(() => {
    extraction = new Extraction('test');
    emit = new Emit(extraction);
    const combinator = new GedcomX();
    promise = combinator.start();
    extraction.start();
  });

  it('basic person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234'});

    extraction.end();
  });

  it('primary person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          principle: true,
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234', primary: true});

    extraction.end();
  });

  it('gender', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          gender: {
            type: 'http://gedcomx.org/Male',
          },
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Gender({person: '1234', gender: 'Male'});

    extraction.end();
  });

  it('name', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Given', value: 'John'},
              ],
              fullText: 'John',
            }],
          }],
        },
        {
          id: '5678',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Given', value: 'John C'},
                {type: 'http://gedcomx.org/Surname', value: 'Smith'},
              ],
              fullText: 'John C Smith',
            }],
          }],
        },
        {
          id: '910',
          names: [{
            nameForms: [{
              parts: [
                {type: 'http://gedcomx.org/Prefix', value: 'Sir'},
                {type: 'http://gedcomx.org/Given', value: 'Richard Charles Nicholas'},
                {type: 'http://gedcomx.org/Surname', value: 'Branson'},
                {type: 'http://gedcomx.org/Suffix', value: 'III'},
              ],
              fullText: 'Sir Richard Charles Nicholas Branson III',
            }],
          }],
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Name({person: '1234', name: 'John'});
    emit.Name({person: '1234', name: 'John'}); // making sure we ignore duplicate names
    emit.Name({person: '5678', name: 'John C Smith'});
    emit.Name({person: '5678', given: 'John C', surname: 'Smith'});
    emit.Name({person: '910', prefix: 'Sir', given: 'Richard Charles Nicholas', surname: 'Branson', suffix: 'III'});

    extraction.end();
  });

  it('basic events/facts', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [
            {type: 'http://gedcomx.org/Baptism', place: {original: 'Somewhere'}},
            {type: 'http://gedcomx.org/Death', date: {original: 'Sometime'}},
            {type: 'http://gedcomx.org/Citizenship', value: 'Somewhere'},
          ],
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Baptism({person: '1234', place: 'Somewhere'});
    emit.Death({person: '1234', date: 'Sometime'});
    emit.Citizenship({person: '1234', value: 'Somewhere'});

    extraction.end();
  });

  it('parent events', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {
            id: '12',
            facts: [
              {type: 'http://gedcomx.org/Birth', place: {original: 'Somewhere'}},
            ],
          },
          {id: '34'},
          {id: '56'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#34'},
            person2: {resource: '#12'},
          },
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#56'},
            person2: {resource: '#12'},
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Birth({person: '12', place: 'Somewhere', parents: ['34', '56']});

    extraction.end();
  });

  it('spouse events', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {id: '12'},
          {id: '34'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/Couple',
            person1: {resource: '#12'},
            person2: {resource: '#34'},
            facts: [
              {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}},
            ],
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Marriage({spouses: ['12', '34'], place: 'Somewhere'});

    extraction.end();
  });

  it('citation', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        description: '#1',
        persons: [
          {
            id: '12',
            sources: [{description: '#1'}],
          },
          {id: '34', sources: [{description: '#1'}]},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/Couple',
            person1: {resource: '#12'},
            person2: {resource: '#34'},
            facts: [
              {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}},
            ],
            sources: [{description: '#1'}],
          },
        ],
        agents: [{
          id: 'agent',
          names: [{lang: 'en', value: 'Example'}],
          homepage: {resource: 'http://example.com'},
        }],
        sourceDescriptions: [{
          id: '1',
          citations: [{
            value: 'The Title, Example (http://example.com/foo : accessed Fri May 12 2017)',
          }],
          about: 'http://example.com/foo',
          titles: [{value: 'The Title'}],
          repository: {resource: '#agent'},
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Marriage({spouses: ['12', '34'], place: 'Somewhere'});
    emit.Citation({
      title: 'The Title',
      url: 'http://example.com/foo',
      accessed: 1494603896789,
      repository_name: 'Example',
      repository_website: 'example.com',
      repository_url: 'http://example.com',
    });

    extraction.end();
  });

  it('should dedupe birth facts', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [
          {
            id: '12',
            facts: [
              {
                type: 'http://gedcomx.org/Birth',
                date: {original: 'Sometime'},
                place: {original: 'Somewhere'},
              },
            ],
          },
          {id: '34'},
          {id: '56'},
        ],
        relationships: [
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#34'},
            person2: {resource: '#12'},
          },
          {
            type: 'http://gedcomx.org/ParentChild',
            person1: {resource: '#56'},
            person2: {resource: '#12'},
          },
        ],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Birth({person: '12', place: 'Somewhere', parents: []});
    emit.Birth({person: '12', parents: ['34', '56']});
    emit.Birth({person: '12', date: 'Sometime', parents: []});

    extraction.end();
  });

  it('alternate id', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          gender: {
            type: 'http://gedcomx.org/Female',
          },
        }, {
          id: 'xyz',
          gender: {
            type: 'http://gedcomx.org/Female',
          },
        }],
      });
      done();
    })
    .catch((error) => done(error));

    emit.Person({id: '1234'});
    emit.AlternateId({person: '1234', id: '5678'});
    emit.Gender({person: '5678', gender: 'Female'});

    emit.Person({id: 'abc'});
    emit.AlternateId({person: 'abc', id: 'xyz', preferred: true});
    emit.Gender({person: 'xyz', gender: 'Female'});

    extraction.end();
  });

  it('external id');

  it('don\'t create relationships array unless a rel is added', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [{
            type: 'http://gedcomx.org/Birth',
            date: {
              original: 'Sometime',
            },
          }],
        }],
      });
      done();
    })
    .catch(done);

    emit.Birth({person: '1234', date: 'Sometime'});
    emit.Marriage({spouses: ['1234'], date: 'Sometime'});
    extraction.end();
  });

  it('marriage event with unknown spouse is added to the person', (done) => {
    extractionErrorListener(done);
    promise.then((data) => {
      expect(data).to.deep.equal({
        persons: [{
          id: '1234',
          facts: [
            {type: 'http://gedcomx.org/Marriage', place: {original: 'Somewhere'}, date: {original: 'Sometime'}},
          ],
        }],
      });
      done();
    })
    .catch((error) => done(error));
    emit.Marriage({spouses: ['1234'], place: 'Somewhere', date: 'Sometime'});
    extraction.end();
  });
});

function extractionErrorListener(done) {
  window.addEventListener('genxtract', (e) => {
    if(e.detail.type === 'ERROR') {
      done(new Error(e.detail.data));
    };
  });
}
<|endoftext|>


<commit_before>import Icon from '@conveyal/woonerf/components/icon'
import React, {Component, PropTypes} from 'react'
import { Button, ButtonGroup, DropdownButton, Dropdown, MenuItem, Tooltip, OverlayTrigger } from 'react-bootstrap'
import { browserHistory } from 'react-router'

import CreateSnapshotModal from './CreateSnapshotModal'
import SelectFileModal from '../../common/components/SelectFileModal.js'
import { GTFS_ICONS } from '../util/ui'

const PANEL_WIDTH = 400

export default class FeedInfoPanel extends Component {
  static propTypes = {
    feedSource: PropTypes.object,
    project: PropTypes.object,
    feedInfo: PropTypes.object,
    createSnapshot: PropTypes.func,
    setActiveEntity: PropTypes.func
  }

  state = {
    right: 5
  }

  _onAddSelect = key => {
    this.props.setActiveEntity(this.props.feedSource.id, key, {id: 'new'})
  }

  _onDropdownArrowClick = () => this.props.getSnapshots(this.props.feedSource)

  _onNavigate = key => {
    switch (key) {
      case '1':
        return browserHistory.push(`/project/${this.props.project.id}`)
      case '2':
        return browserHistory.push(`/feed/${this.props.feedSource.id}`)
    }
  }

  _onOkClicked = (name, comment) => {
    this.props.createSnapshot(this.props.feedSource, name, comment)
  }

  _onSelectSnapshot = key => {
    const snapshot = this.props.feedSource.editorSnapshots.find(s => s.id === key)
    this.props.showConfirmModal({
      title: `Restore ${key}?`,
      body: `Are you sure you want to restore this snapshot?`,
      onConfirm: () => {
        this.props.restoreSnapshot(this.props.feedSource, snapshot)
      }
    })
  }

  _onToggleHide = () => {
    const toolbarVisible = this.state.right > 0
    if (toolbarVisible) {
      this.setState({right: 30 - PANEL_WIDTH})
    } else {
      this.setState({right: 5})
    }
  }

  _openSnapshotModal = () => this.refs.snapshotModal.open()

  showUploadFileModal = () => {
    this.refs.selectFileModal.open({
      title: 'Upload route shapefile',
      body: 'Select a zipped shapefile to display on map:',
      onConfirm: (files) => {
        const nameArray = files[0].name.split('.')
        if (files[0].type !== 'application/zip' || nameArray[nameArray.length - 1] !== 'zip') {
          return false
        } else {
          this.props.displayRoutesShapefile(this.props.feedSource, files[0])
          return true
        }
      },
      errorMessage: 'Uploaded file must be a valid zip file (.zip).'
    })
  }

  render () {
    const { feedSource, feedInfo } = this.props
    if (!feedInfo) return null
    const panelStyle = {
      position: 'absolute',
      right: this.state.right,
      bottom: 20,
      zIndex: 500,
      borderRadius: '5px',
      width: `${PANEL_WIDTH}px`
    }
    if (!feedInfo || !feedSource) {
      return null
    }
    const toolbarVisible = this.state.right > 0
    const feedName = feedSource && feedSource.name && feedSource.name.length > 10
      ? feedSource.name.substr(0, 10) + '...'
      : feedSource && feedSource.name
      ? feedSource.name
      : 'Unnamed'

    return (

        <SelectFileModal ref='selectFileModal' />

          <CreateSnapshotModal
            ref='snapshotModal'
            onOkClicked={this._onOkClicked} />
          <ButtonGroup>
            {/* Hide toolbar toggle */}
            <OverlayTrigger placement='top' overlay={<Tooltip id='hide-tooltip'>{toolbarVisible ? 'Hide toolbar' : 'Show toolbar'}</Tooltip>}>
              <Button
                onClick={this._onToggleHide}
              >
                <Icon type={toolbarVisible ? 'caret-right' : 'caret-left'} />
              </Button>
            </OverlayTrigger>
            {/* Navigation dropdown */}
            <DropdownButton
              dropup
              title={<span title={`Editing ${feedSource && feedSource.name}`}>Editing {feedName}}
              id='navigation-dropdown'
              onSelect={this._onNavigate}>
              <MenuItem eventKey='1'><Icon type='reply' /> Back to project</MenuItem>
              <MenuItem eventKey='2'><Icon type='reply' /> Back to feed source</MenuItem>
            </DropdownButton>
            <Button
              onClick={this.showUploadFileModal}>
              <Icon type='upload' />
            </Button>
            {/* Add entity dropdown */}
            <DropdownButton
              pullRight dropup
              title={<Icon type='plus' />}
              id='add-entity-dropdown'
              onSelect={this._onAddSelect}>
              {GTFS_ICONS.map(c => {
                if (!c.addable) return null
                const name = c.id === 'scheduleexception' ? 'schedule exception' : c.id
                return (
                  <MenuItem key={c.id} eventKey={c.id}><Icon type={c.icon} /> Add {name}</MenuItem>
                )
              })}
            </DropdownButton>
            {/* Snapshot dropdown */}
            {
              <Dropdown
                dropup
                pullRight
                id='snapshot-dropdown'
                onSelect={this._onSelectSnapshot}>
                <OverlayTrigger placement='top' overlay={<Tooltip id='snapshot-tooltip'>Take snapshot</Tooltip>}>
                  <Button
                    bsStyle='primary'
                    onClick={this._openSnapshotModal}>
                    <Icon type='camera' />
                  </Button>
                </OverlayTrigger>
                <Dropdown.Toggle
                  bsStyle='primary'
                  onClick={this._onDropdownArrowClick} />
                <Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
                  {feedSource && feedSource.editorSnapshots
                    ? feedSource.editorSnapshots.map(snapshot => {
                      return (
                        <MenuItem
                          key={snapshot.id}
                          eventKey={snapshot.id}>
                          <Icon type='reply' /> Revert to {snapshot.name}
                        </MenuItem>
                      )
                    })
                    : <MenuItem disabled eventKey={null}>No snapshots</MenuItem>
                  }
                </Dropdown.Menu>
              </Dropdown>
            }
          </ButtonGroup>

    )
  }
}
<commit_msg>style(FeedInfoPanel): move props to single line<commit_after>import Icon from '@conveyal/woonerf/components/icon'
import React, {Component, PropTypes} from 'react'
import { Button, ButtonGroup, DropdownButton, Dropdown, MenuItem, Tooltip, OverlayTrigger } from 'react-bootstrap'
import { browserHistory } from 'react-router'

import CreateSnapshotModal from './CreateSnapshotModal'
import SelectFileModal from '../../common/components/SelectFileModal.js'
import { GTFS_ICONS } from '../util/ui'

const PANEL_WIDTH = 400

export default class FeedInfoPanel extends Component {
  static propTypes = {
    feedSource: PropTypes.object,
    project: PropTypes.object,
    feedInfo: PropTypes.object,
    createSnapshot: PropTypes.func,
    setActiveEntity: PropTypes.func
  }

  state = {
    right: 5
  }

  _onAddSelect = key => {
    this.props.setActiveEntity(this.props.feedSource.id, key, {id: 'new'})
  }

  _onDropdownArrowClick = () => this.props.getSnapshots(this.props.feedSource)

  _onNavigate = key => {
    switch (key) {
      case '1':
        return browserHistory.push(`/project/${this.props.project.id}`)
      case '2':
        return browserHistory.push(`/feed/${this.props.feedSource.id}`)
    }
  }

  _onOkClicked = (name, comment) => {
    this.props.createSnapshot(this.props.feedSource, name, comment)
  }

  _onSelectSnapshot = key => {
    const snapshot = this.props.feedSource.editorSnapshots.find(s => s.id === key)
    this.props.showConfirmModal({
      title: `Restore ${key}?`,
      body: `Are you sure you want to restore this snapshot?`,
      onConfirm: () => {
        this.props.restoreSnapshot(this.props.feedSource, snapshot)
      }
    })
  }

  _onToggleHide = () => {
    const toolbarVisible = this.state.right > 0
    if (toolbarVisible) {
      this.setState({right: 30 - PANEL_WIDTH})
    } else {
      this.setState({right: 5})
    }
  }

  _openSnapshotModal = () => this.refs.snapshotModal.open()

  showUploadFileModal = () => {
    this.refs.selectFileModal.open({
      title: 'Upload route shapefile',
      body: 'Select a zipped shapefile to display on map:',
      onConfirm: (files) => {
        const nameArray = files[0].name.split('.')
        if (files[0].type !== 'application/zip' || nameArray[nameArray.length - 1] !== 'zip') {
          return false
        } else {
          this.props.displayRoutesShapefile(this.props.feedSource, files[0])
          return true
        }
      },
      errorMessage: 'Uploaded file must be a valid zip file (.zip).'
    })
  }

  render () {
    const { feedSource, feedInfo } = this.props
    if (!feedInfo) return null
    const panelStyle = {
      position: 'absolute',
      right: this.state.right,
      bottom: 20,
      zIndex: 500,
      borderRadius: '5px',
      width: `${PANEL_WIDTH}px`
    }
    if (!feedInfo || !feedSource) {
      return null
    }
    const toolbarVisible = this.state.right > 0
    const feedName = feedSource && feedSource.name && feedSource.name.length > 10
      ? feedSource.name.substr(0, 10) + '...'
      : feedSource && feedSource.name
      ? feedSource.name
      : 'Unnamed'

    return (

        <SelectFileModal ref='selectFileModal' />

          <CreateSnapshotModal
            ref='snapshotModal'
            onOkClicked={this._onOkClicked} />
          <ButtonGroup>
            {/* Hide toolbar toggle */}
            <OverlayTrigger placement='top' overlay={<Tooltip id='hide-tooltip'>{toolbarVisible ? 'Hide toolbar' : 'Show toolbar'}</Tooltip>}>
              <Button onClick={this._onToggleHide}>
                <Icon type={toolbarVisible ? 'caret-right' : 'caret-left'} />
              </Button>
            </OverlayTrigger>
            {/* Navigation dropdown */}
            <DropdownButton
              dropup
              title={<span title={`Editing ${feedSource && feedSource.name}`}>Editing {feedName}}
              id='navigation-dropdown'
              onSelect={this._onNavigate}>
              <MenuItem eventKey='1'><Icon type='reply' /> Back to project</MenuItem>
              <MenuItem eventKey='2'><Icon type='reply' /> Back to feed source</MenuItem>
            </DropdownButton>
            <Button
              onClick={this.showUploadFileModal}>
              <Icon type='upload' />
            </Button>
            {/* Add entity dropdown */}
            <DropdownButton
              pullRight dropup
              title={<Icon type='plus' />}
              id='add-entity-dropdown'
              onSelect={this._onAddSelect}>
              {GTFS_ICONS.map(c => {
                if (!c.addable) return null
                const name = c.id === 'scheduleexception' ? 'schedule exception' : c.id
                return (
                  <MenuItem key={c.id} eventKey={c.id}><Icon type={c.icon} /> Add {name}</MenuItem>
                )
              })}
            </DropdownButton>
            {/* Snapshot dropdown */}
            {
              <Dropdown
                dropup
                pullRight
                id='snapshot-dropdown'
                onSelect={this._onSelectSnapshot}>
                <OverlayTrigger placement='top' overlay={<Tooltip id='snapshot-tooltip'>Take snapshot</Tooltip>}>
                  <Button
                    bsStyle='primary'
                    onClick={this._openSnapshotModal}>
                    <Icon type='camera' />
                  </Button>
                </OverlayTrigger>
                <Dropdown.Toggle
                  bsStyle='primary'
                  onClick={this._onDropdownArrowClick} />
                <Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
                  {feedSource && feedSource.editorSnapshots
                    ? feedSource.editorSnapshots.map(snapshot => {
                      return (
                        <MenuItem
                          key={snapshot.id}
                          eventKey={snapshot.id}>
                          <Icon type='reply' /> Revert to {snapshot.name}
                        </MenuItem>
                      )
                    })
                    : <MenuItem disabled eventKey={null}>No snapshots</MenuItem>
                  }
                </Dropdown.Menu>
              </Dropdown>
            }
          </ButtonGroup>

    )
  }
}
<|endoftext|>