<commit_before>function Scene() {
    if (typeof Float64Array !== "undefined") {
        glMatrixArrayType = Float64Array;
    }

    /** Demo loading functions **/

    /**
     * Load the specified demo at the given step. (The step indexes into
     * the demo's steps array.)
     */
    this.load = function(demo, step) {
        this.carray = [];
        this.curStep = step;
        this.demo = demo;

        // If the demo specifies global options, set 'em
        if (typeof demo.steps[step].origin === "object") {
            this.origin = demo.steps[step].origin;
        }
        if (typeof demo.steps[step].timeScale === "number") {
            this.timeScale = demo.steps[step].timeScale;
        }
        if (typeof demo.steps[step].zoom === "number") {
            this.zoom = demo.steps[step].zoom;
        }

        // Clone defaults into curOptions, rather than getting a reference to it
        this.curOptions = jQuery.extend({}, this.defaults);

        if (typeof demo.steps[step].options === "object") {
            $.extend(this.curOptions, demo.steps[step].options);
        }

        // Update c with the demo's chosen value
        c = (this.curOptions.c) ? this.curOptions.c : 1;

        drawLightCone(this);

        this.boost = {"left": boostFrom3Vel(-0.005, 0, 0, this.zoom),
                      "right": boostFrom3Vel(0.005, 0, 0, this.zoom),
                      "up": boostFrom3Vel(0, 0.005, 0, this.zoom),
                      "down": boostFrom3Vel(0, -0.005, 0, this.zoom)};

        demo.steps[step].objects.forEach(this.createObject, this);

        $('#caption').html(demo.steps[step].caption);

        // If the demo specifies an object whose frame is preferred, shift to that frame.
        if (typeof demo.steps[step].frame === "number") {
            this.shiftToFrameOfObject(this.carray[demo.steps[step].frame]);
        }
        this.frameStartTime = new Date().getTime();
    };

    /**
     * Called by scene.load() to create each individual object in a scene.
     * Hence obj is an object from the demo system specifying options,
     * a label, coordinates, and momentum.
     */
    this.createObject = function (obj) {
        if (typeof obj.options === "undefined") {
            obj.options = {};
        }
        if (typeof obj.label === "undefined") {
            obj.label = "";
        }

        // Copy object-specific options in on top of the global defaults
        obj.options = $.extend({}, this.curOptions, obj.options);

        // Upgrade 2D to 3D
        if (obj.x.length == 2) {
            obj.x[2] = 0;
        }
        if (obj.p && obj.p.length == 2) {
            obj.p[2] = 0;
        }
        var thingy;
        if (obj.shape) {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.p[0], obj.p[1], obj.p[2], 0]), obj.label, obj.options, obj.shape, this.timeStep);
        } else if (obj.v) {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.v[0], obj.v[1], obj.v[2], 0]), obj.label, obj.options);
        } else {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.p[0], obj.p[1], obj.p[2], 0]), obj.label, obj.options);
        }
        this.carray.push(thingy);
    };

    /** Scene drawing functions **/

    /**
     * Draw the scene onto the canvas. Uses requestAnimFrame to schedule the
     * next frame.
     */
    this.draw = function() {
        this.oldFrameStartTime = this.frameStartTime;
        this.frameStartTime = new Date().getTime();
        this.timeStep = (this.frameStartTime - this.oldFrameStartTime) * this.timeScale;
        this.clear();

        this.h.drawImage(this.lightConeCanvas, 0, 0);
        this.carray.forEach(function(obj) {
            obj.update(this.timeStep);
            obj.draw(this);
            obj.drawXT(this);
        }, this);
        this.drawCrosshairs();
        this.drawInfo();
        this.t = this.t + (this.timeStep * c);

        if (leftDown === true)     this.changeArrayFrame(nullQuat4, this.boost.left,  this.carray);
        if (upDown === true)       this.changeArrayFrame(nullQuat4, this.boost.up,    this.carray);
        if (downDown === true)     this.changeArrayFrame(nullQuat4, this.boost.down,  this.carray);
        if (rightDown === true)    this.changeArrayFrame(nullQuat4, this.boost.right, this.carray);
        if (rotLeftDown === true)  this.changeArrayFrame(nullQuat4, rotRight,   this.carray);
        if (rotRightDown === true) this.changeArrayFrame(nullQuat4, rotLeft,    this.carray);
        if (rotUpDown === true)    this.changeArrayFrame(nullQuat4, rotUp,      this.carray);
        if (rotDownDown === true)  this.changeArrayFrame(nullQuat4, rotDown,    this.carray);

        if (this.drawing) {
            requestAnimFrame(drawScene);
        }
        this.lastFrameEndTime = this.frameEndTime;
        this.frameEndTime = new Date().getTime();
    };

    this.drawInfo = function() {
        scene.g.fillStyle = "rgba(100,100,100,0.3)";
        scene.g.beginPath();
        scene.g.moveTo(10,10);
        scene.g.lineTo(150,10);
        scene.g.lineTo(150,100);
        scene.g.lineTo(10,100);
        scene.g.closePath();
        scene.g.fill();
        scene.g.fillStyle = "rgba(150,0,150,1)";
        scene.g.fillText("Game Time: " + Math.round(this.t/c) / 1000, 30, 30);
        scene.g.fillText("Real Time: " + Math.round((this.frameStartTime - this.initialTime)/c) / 1000, 30, 50);

        if (window.console && window.console.firebug) {
            scene.g.fillText("Fps: " + Math.round((1000 / (-this.lastFrameEndTime + this.frameEndTime))), 30, 70);
            scene.g.fillText("c: " + c, 30, 90);
        }
    };

    this.drawCrosshairs = function () {
        this.g.strokeStyle = "#fff";
        this.g.beginPath();
        this.g.moveTo(this.origin[0] - 10, this.origin[1]);
        this.g.lineTo(this.origin[0] + 10, this.origin[1]);
        this.g.stroke();

        this.g.beginPath();
        this.g.moveTo(this.origin[0], this.origin[1] - 10);
        this.g.lineTo(this.origin[0], this.origin[1] + 10);
        this.g.stroke();
    };

    this.clear = function() {
        this.g.clearRect(0, 0, this.width, this.height);
        this.h.clearRect(0, 0, this.mWidth, this.mHeight);
        this.TDC.clearRect(0, 0, this.tWidth, this.tHeight);
    };

    /** Animation and step control functions **/

    this.startAnimation = function() {
        // Frame timing is used to maintain constant speed. Reset.
        this.frameEndTime = new Date().getTime();
        this.initialTime = new Date().getTime();
        this.t = 0;

        // If not currently animating (we're paused), draw the first frame.
        if (!this.drawing) {
            this.draw();
        }
    };

    this.nextStep = function() {
        if (this.curStep + 1 < this.demo.steps.length) {
            this.curStep += 1;
            this.replay();
        }
    };

    this.prevStep = function() {
        if (this.curStep > 0) {
            this.curStep -= 1;
            this.replay();
        }
    };

    /**
     * Reload the current demo from scratch and restart the animation.
     */
    this.replay = function() {
        this.load(this.demo, this.curStep);
        this.startAnimation();
    };

    this.pause = function() {
        if (this.timeScale === 0) {
            this.timeScale = this.prevTimeScale;
            this.frameStartTime = new Date().getTime();
            this.draw();
        } else {
            this.prevTimeScale = this.timeScale;
            this.timeScale = 0;
        }
    };

    /** Object utilities **/

    /**
     * Find the closest object to the given (x,y), within a distance maxDist
     * in screen pixels (i.e. (x,y) is a screen location, not a scaled scene
     * coordinate)
     */
    this.findClosestObject = function(x, y, maxDist) {
        var minDist = this.width;
        var minElement = -1;

        for (var i = 0; i < this.carray.length; i++) {
            var dist = getDistance([x,y], [this.carray[i].XView[0] / this.zoom + this.origin[0],
                                           -this.carray[i].XView[1] / this.zoom + this.origin[1]]);
            if (dist < minDist) {
                minDist = dist;
                minElement = i;
            }
        }

        if (minDist < maxDist) {
            return this.carray[minElement];
        }
        return false;
    };

    // Take a given inertialObject and switch to its reference frame
    this.shiftToFrameOfObject = function(obj) {
        this.changeArrayFrame(quat4.create(obj.X0), cBoostMat(obj.V, c));
    };

    /**
     * Switch every object in the scene to a new reference frame given by
     * the provided translation and boost.
     */
    this.changeArrayFrame = function(translation, boost) {
        this.carray.forEach(function(obj) {
            obj.changeFrame(translation, boost);
        });
    };

    this.initialTime = new Date().getTime();
    this.g = $('#canvas')[0].getContext("2d");
    this.h = $('#minkowski')[0].getContext("2d");
    this.TDC = $('#3DCanvas')[0].getContext("2d");
    this.width = $("#canvas").width();
    this.height = $("#canvas").height();
    this.mWidth = $("#minkowski").width();
    this.mHeight = $("#minkowski").height();
    this.tWidth = $("#3DCanvas").width();
    this.tHeight = $("#3DCanvas").height();

    this.lightConeCanvas = document.createElement('canvas');
    this.lightConeCanvas.width =  this.mWidth;
    this.lightConeCanvas.height =  this.mHeight;
    this.lCCtx = this.lightConeCanvas.getContext('2d');

    this.camBack = 0;
    this.hwidth = this.width / 2;
    this.hheight = this.height / 2;
    this.origin = [this.hwidth, this.hheight, this.hheight];
    this.carray = [];
    this.zoom = 0.25;
    this.timeStep = 5;
    this.timeScale = 0.02;
    this.t = 0;

    this.defaults = {"showDoppler": true,
                     "showVisualPos": true,
                     "showFramePos": false,
                     "showVelocity": true,
                     "showTime": false,
                     "showGamma": true,
                     "show3D": false,
                     "c": 3};

    this.options = {"alwaysDoppler": false,
                    "neverDoppler": false,
                    "alwaysShowFramePos": false,
                    "neverShowFramePos": false,
                    "showTime": false};

    this.drawing = true;
}

/**
 * Helper function to draw the scene. Necessary because of the setInterval()
 * this problem.
 */
function drawScene(event) {
    scene.draw();
}

function drawLightCone(scene){
    var size = Math.max(scene.mHeight - scene.origin[2], scene.origin[2]);
    scene.lCCtx.fillStyle = "#300";
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(0,0);
    scene.lCCtx.lineTo(0, scene.mHeight);
    scene.lCCtx.lineTo(-size * c + scene.origin[0], size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo(scene.mWidth, 0);
    scene.lCCtx.lineTo(scene.mWidth, scene.mHeight);
    scene.lCCtx.lineTo( size * c + scene.origin[0], size + scene.origin[2]);
    scene.lCCtx.lineTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.closePath();
    scene.lCCtx.fill();
    scene.lCCtx.fillStyle = "#003";
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo(-size * c + scene.origin[0],  size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0],  size + scene.origin[2]);
    scene.lCCtx.moveTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.closePath();
    scene.lCCtx.fill();
    scene.lCCtx.strokeStyle = "#FFF";
    scene.lCCtx.lineWidth = 3;
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(0, scene.origin[2]);
    scene.lCCtx.lineTo(scene.mWidth, scene.origin[2]);
    scene.lCCtx.moveTo(scene.origin[0], 0);
    scene.lCCtx.lineTo(scene.origin[0], scene.mHeight);
    scene.lCCtx.stroke();
    scene.lCCtx.lineWidth = 1;
    scene.lCCtx.fillStyle = "#fff";
    scene.lCCtx.fillText("t(s)", 5 + scene.origin[0], 10);
    scene.lCCtx.fillText("x(m)", scene.width - 30, scene.origin[2] - 10);
}
<commit_msg>Don't start a new draw timer when returning from pause. We're already drawing<commit_after>function Scene() {
    if (typeof Float64Array !== "undefined") {
        glMatrixArrayType = Float64Array;
    }

    /** Demo loading functions **/

    /**
     * Load the specified demo at the given step. (The step indexes into
     * the demo's steps array.)
     */
    this.load = function(demo, step) {
        this.carray = [];
        this.curStep = step;
        this.demo = demo;

        // If the demo specifies global options, set 'em
        if (typeof demo.steps[step].origin === "object") {
            this.origin = demo.steps[step].origin;
        }
        if (typeof demo.steps[step].timeScale === "number") {
            this.timeScale = demo.steps[step].timeScale;
        }
        if (typeof demo.steps[step].zoom === "number") {
            this.zoom = demo.steps[step].zoom;
        }

        // Clone defaults into curOptions, rather than getting a reference to it
        this.curOptions = jQuery.extend({}, this.defaults);

        if (typeof demo.steps[step].options === "object") {
            $.extend(this.curOptions, demo.steps[step].options);
        }

        // Update c with the demo's chosen value
        c = (this.curOptions.c) ? this.curOptions.c : 1;

        drawLightCone(this);

        this.boost = {"left": boostFrom3Vel(-0.005, 0, 0, this.zoom),
                      "right": boostFrom3Vel(0.005, 0, 0, this.zoom),
                      "up": boostFrom3Vel(0, 0.005, 0, this.zoom),
                      "down": boostFrom3Vel(0, -0.005, 0, this.zoom)};

        demo.steps[step].objects.forEach(this.createObject, this);

        $('#caption').html(demo.steps[step].caption);

        // If the demo specifies an object whose frame is preferred, shift to that frame.
        if (typeof demo.steps[step].frame === "number") {
            this.shiftToFrameOfObject(this.carray[demo.steps[step].frame]);
        }
        this.frameStartTime = new Date().getTime();
    };

    /**
     * Called by scene.load() to create each individual object in a scene.
     * Hence obj is an object from the demo system specifying options,
     * a label, coordinates, and momentum.
     */
    this.createObject = function (obj) {
        if (typeof obj.options === "undefined") {
            obj.options = {};
        }
        if (typeof obj.label === "undefined") {
            obj.label = "";
        }

        // Copy object-specific options in on top of the global defaults
        obj.options = $.extend({}, this.curOptions, obj.options);

        // Upgrade 2D to 3D
        if (obj.x.length == 2) {
            obj.x[2] = 0;
        }
        if (obj.p && obj.p.length == 2) {
            obj.p[2] = 0;
        }
        var thingy;
        if (obj.shape) {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.p[0], obj.p[1], obj.p[2], 0]), obj.label, obj.options, obj.shape, this.timeStep);
        } else if (obj.v) {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.v[0], obj.v[1], obj.v[2], 0]), obj.label, obj.options);
        } else {
            thingy = new obj.object(quat4.create([obj.x[0], obj.x[1], obj.x[2], 0]),
                                    quat4.create([obj.p[0], obj.p[1], obj.p[2], 0]), obj.label, obj.options);
        }
        this.carray.push(thingy);
    };

    /** Scene drawing functions **/

    /**
     * Draw the scene onto the canvas. Uses requestAnimFrame to schedule the
     * next frame.
     */
    this.draw = function() {
        this.oldFrameStartTime = this.frameStartTime;
        this.frameStartTime = new Date().getTime();
        this.timeStep = (this.frameStartTime - this.oldFrameStartTime) * this.timeScale;
        this.clear();

        this.h.drawImage(this.lightConeCanvas, 0, 0);
        this.carray.forEach(function(obj) {
            obj.update(this.timeStep);
            obj.draw(this);
            obj.drawXT(this);
        }, this);
        this.drawCrosshairs();
        this.drawInfo();
        this.t = this.t + (this.timeStep * c);

        if (leftDown === true)     this.changeArrayFrame(nullQuat4, this.boost.left,  this.carray);
        if (upDown === true)       this.changeArrayFrame(nullQuat4, this.boost.up,    this.carray);
        if (downDown === true)     this.changeArrayFrame(nullQuat4, this.boost.down,  this.carray);
        if (rightDown === true)    this.changeArrayFrame(nullQuat4, this.boost.right, this.carray);
        if (rotLeftDown === true)  this.changeArrayFrame(nullQuat4, rotRight,   this.carray);
        if (rotRightDown === true) this.changeArrayFrame(nullQuat4, rotLeft,    this.carray);
        if (rotUpDown === true)    this.changeArrayFrame(nullQuat4, rotUp,      this.carray);
        if (rotDownDown === true)  this.changeArrayFrame(nullQuat4, rotDown,    this.carray);

        if (this.drawing) {
            requestAnimFrame(drawScene);
        }
        this.lastFrameEndTime = this.frameEndTime;
        this.frameEndTime = new Date().getTime();
    };

    this.drawInfo = function() {
        scene.g.fillStyle = "rgba(100,100,100,0.3)";
        scene.g.beginPath();
        scene.g.moveTo(10,10);
        scene.g.lineTo(150,10);
        scene.g.lineTo(150,100);
        scene.g.lineTo(10,100);
        scene.g.closePath();
        scene.g.fill();
        scene.g.fillStyle = "rgba(150,0,150,1)";
        scene.g.fillText("Game Time: " + Math.round(this.t/c) / 1000, 30, 30);
        scene.g.fillText("Real Time: " + Math.round((this.frameStartTime - this.initialTime)/c) / 1000, 30, 50);

        if (window.console && window.console.firebug) {
            scene.g.fillText("Fps: " + Math.round((1000 / (-this.lastFrameEndTime + this.frameEndTime))), 30, 70);
            scene.g.fillText("c: " + c, 30, 90);
        }
    };

    this.drawCrosshairs = function () {
        this.g.strokeStyle = "#fff";
        this.g.beginPath();
        this.g.moveTo(this.origin[0] - 10, this.origin[1]);
        this.g.lineTo(this.origin[0] + 10, this.origin[1]);
        this.g.stroke();

        this.g.beginPath();
        this.g.moveTo(this.origin[0], this.origin[1] - 10);
        this.g.lineTo(this.origin[0], this.origin[1] + 10);
        this.g.stroke();
    };

    this.clear = function() {
        this.g.clearRect(0, 0, this.width, this.height);
        this.h.clearRect(0, 0, this.mWidth, this.mHeight);
        this.TDC.clearRect(0, 0, this.tWidth, this.tHeight);
    };

    /** Animation and step control functions **/

    this.startAnimation = function() {
        // Frame timing is used to maintain constant speed. Reset.
        this.frameEndTime = new Date().getTime();
        this.initialTime = new Date().getTime();
        this.t = 0;

        // If not currently animating, draw the first frame.
        if (!this.drawing) {
            this.draw();
        }
    };

    this.nextStep = function() {
        if (this.curStep + 1 < this.demo.steps.length) {
            this.curStep += 1;
            this.replay();
        }
    };

    this.prevStep = function() {
        if (this.curStep > 0) {
            this.curStep -= 1;
            this.replay();
        }
    };

    /**
     * Reload the current demo from scratch and restart the animation.
     */
    this.replay = function() {
        this.load(this.demo, this.curStep);
        this.startAnimation();
    };

    this.pause = function() {
        if (this.timeScale === 0) {
            this.timeScale = this.prevTimeScale;
            this.frameStartTime = new Date().getTime();
        } else {
            this.prevTimeScale = this.timeScale;
            this.timeScale = 0;
        }
    };

    /** Object utilities **/

    /**
     * Find the closest object to the given (x,y), within a distance maxDist
     * in screen pixels (i.e. (x,y) is a screen location, not a scaled scene
     * coordinate)
     */
    this.findClosestObject = function(x, y, maxDist) {
        var minDist = this.width;
        var minElement = -1;

        for (var i = 0; i < this.carray.length; i++) {
            var dist = getDistance([x,y], [this.carray[i].XView[0] / this.zoom + this.origin[0],
                                           -this.carray[i].XView[1] / this.zoom + this.origin[1]]);
            if (dist < minDist) {
                minDist = dist;
                minElement = i;
            }
        }

        if (minDist < maxDist) {
            return this.carray[minElement];
        }
        return false;
    };

    // Take a given inertialObject and switch to its reference frame
    this.shiftToFrameOfObject = function(obj) {
        this.changeArrayFrame(quat4.create(obj.X0), cBoostMat(obj.V, c));
    };

    /**
     * Switch every object in the scene to a new reference frame given by
     * the provided translation and boost.
     */
    this.changeArrayFrame = function(translation, boost) {
        this.carray.forEach(function(obj) {
            obj.changeFrame(translation, boost);
        });
    };

    this.initialTime = new Date().getTime();
    this.g = $('#canvas')[0].getContext("2d");
    this.h = $('#minkowski')[0].getContext("2d");
    this.TDC = $('#3DCanvas')[0].getContext("2d");
    this.width = $("#canvas").width();
    this.height = $("#canvas").height();
    this.mWidth = $("#minkowski").width();
    this.mHeight = $("#minkowski").height();
    this.tWidth = $("#3DCanvas").width();
    this.tHeight = $("#3DCanvas").height();

    this.lightConeCanvas = document.createElement('canvas');
    this.lightConeCanvas.width =  this.mWidth;
    this.lightConeCanvas.height =  this.mHeight;
    this.lCCtx = this.lightConeCanvas.getContext('2d');

    this.camBack = 0;
    this.hwidth = this.width / 2;
    this.hheight = this.height / 2;
    this.origin = [this.hwidth, this.hheight, this.hheight];
    this.carray = [];
    this.zoom = 0.25;
    this.timeStep = 5;
    this.timeScale = 0.02;
    this.t = 0;

    this.defaults = {"showDoppler": true,
                     "showVisualPos": true,
                     "showFramePos": false,
                     "showVelocity": true,
                     "showTime": false,
                     "showGamma": true,
                     "show3D": false,
                     "c": 3};

    this.options = {"alwaysDoppler": false,
                    "neverDoppler": false,
                    "alwaysShowFramePos": false,
                    "neverShowFramePos": false,
                    "showTime": false};

    this.drawing = true;
}

/**
 * Helper function to draw the scene. Necessary because of the setInterval()
 * this problem.
 */
function drawScene(event) {
    scene.draw();
}

function drawLightCone(scene){
    var size = Math.max(scene.mHeight - scene.origin[2], scene.origin[2]);
    scene.lCCtx.fillStyle = "#300";
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(0,0);
    scene.lCCtx.lineTo(0, scene.mHeight);
    scene.lCCtx.lineTo(-size * c + scene.origin[0], size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo(scene.mWidth, 0);
    scene.lCCtx.lineTo(scene.mWidth, scene.mHeight);
    scene.lCCtx.lineTo( size * c + scene.origin[0], size + scene.origin[2]);
    scene.lCCtx.lineTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.closePath();
    scene.lCCtx.fill();
    scene.lCCtx.fillStyle = "#003";
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.lineTo(-size * c + scene.origin[0],  size + scene.origin[2]);
    scene.lCCtx.lineTo( size * c + scene.origin[0],  size + scene.origin[2]);
    scene.lCCtx.moveTo(-size * c + scene.origin[0], -size + scene.origin[2]);
    scene.lCCtx.closePath();
    scene.lCCtx.fill();
    scene.lCCtx.strokeStyle = "#FFF";
    scene.lCCtx.lineWidth = 3;
    scene.lCCtx.beginPath();
    scene.lCCtx.moveTo(0, scene.origin[2]);
    scene.lCCtx.lineTo(scene.mWidth, scene.origin[2]);
    scene.lCCtx.moveTo(scene.origin[0], 0);
    scene.lCCtx.lineTo(scene.origin[0], scene.mHeight);
    scene.lCCtx.stroke();
    scene.lCCtx.lineWidth = 1;
    scene.lCCtx.fillStyle = "#fff";
    scene.lCCtx.fillText("t(s)", 5 + scene.origin[0], 10);
    scene.lCCtx.fillText("x(m)", scene.width - 30, scene.origin[2] - 10);
}
<|endoftext|>


<commit_before>const dagreD3 = require('dagre-d3');

module.exports.change_node = (g, node_id, color) => {
    g.node(node_id).style = "fill : " + color;
    return g;
}

module.exports.change_edge = (g, sourceId, targetId, color) => {
    console.log(g.edge(sourceId, targetId));
    g.edge(sourceId, targetId).style = "stroke : " + color + ";" + " stroke-width:2px;";
    return g;
}

module.exports.change_neighbours = (g, node_id, color) => {

    let inEdges = g.inEdges(node_id);
    let outEdges = g.outEdges(node_id);

    let neighbours = new Array();

    for(let i=0; i<inEdges.length; i++)
        neighbours.push(inEdges[i].v);

    for(let i=0; i<outEdges.length; i++)
        neighbours.push(outEdges[i].w);

    console.log(neighbours);

    for(let i=0; i<neighbours.length; i++)
        g.node(neighbours[i]).style = "fill : " + color;

    return g;
}

module.exports.change_two_nodes_of_edge = (g, sourceId, targetId, color) => {
    g.node(sourceId).style = "fill : " + color;
    g.node(targetId).style = "fill : " + color;
    return g;
}<commit_msg>edit change color when mouse hover on nodes<commit_after>const dagreD3 = require('dagre-d3');

module.exports.change_node_color_onHover = (svg) => {

    svg.selectAll("g.node")
        .on("mouseover", handleMouseOver)
        .on("mouseout", handleMouseOut);

    // Create Event Handlers for mouse
    function handleMouseOver(d, i) {  // Add interactivity

        // Use D3 to select element, change color and size
        d3.select(this).attr({
            fill: "yellow",
            stroke : "yellow"
        });
    }

    function handleMouseOut(d, i) {
        // Use D3 to select element, change color back to normal
        d3.select(this).attr({
            fill: "black",
            stroke : null

        });

    }

}<|endoftext|>


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

var chalk = require('chalk');
var program = require('commander');
var licenses = require('osi-licenses');
var licenseFiles = require('osi-licenses-full');
var githubUsername = require('github-username');
var paramCase = require('param-case');
var titleCase = require('title-case');
var camelCase = require('camel-case');
var sentenceCase = require('sentence-case');
var fs = require('fs');
var join = require('path').join;
var format = require('util').format;
var Handlebars = require('handlebars');
var rimraf = require('rimraf');
var somebody = require('somebody');
var pkg = require('../package.json');

// Set the basic author information.
var author = somebody.stringify({
  name: require('git-user-name'),
  email: require('git-user-email')
});

// Program commands.
program
  .version(pkg.version)
  .description(pkg.description)
  .option('-a, --author [value]', 'set the author [' + author + ']', author)
  .option('-l, --license [value]', 'set the module license [MIT]', 'MIT')
  .parse(process.argv);

// Exit when no arguments.
if (!program.args.length) {
  program.help();
  process.exit();
}

var license = program.license;

// Add whitespace below command line.
log();

// Exit on unknown license.
if (!licenses.hasOwnProperty(license)) {
  log('Unknown license (%s), use one of:', license);
  log();
  log(Object.keys(licenses).join(', '));
  log();

  process.exit();
}

// Parse a passed in author.
var info = somebody.parse(process.author || author);

if (!info.name || !info.email) {
  log(
    'Unable to get author name and email, run with %s option',
    chalk.bold('--author')
  );
  log();

  process.exit();
}

var name = info.name;
var email = info.email;
var url = info.url;

var moduleName = paramCase(program.args[0]);
var moduleTitle = titleCase(moduleName);
var moduleVariable = camelCase(moduleName);
var moduleSentence = sentenceCase(moduleName);

var licenseName = licenses[license];

log('Using name: %s', chalk.bold(name));
log('Using email: %s', chalk.bold(email));
log('Using license: %s', chalk.bold(licenseName));
log('Using module name: %s', chalk.bold(moduleName));
log();

log(chalk.dim('Searching for GitHub username...'));
log();

// Search GitHub for your username.
githubUsername(email, function (err, username) {
  if (err) {
    log(chalk.red('Unable to find GitHub username for %s'), email);
    log();

    return;
  }

  log('Using username: %s', chalk.bold(username));
  log();

  return createModule(join(process.cwd(), moduleName), {
    moduleName: moduleName,
    moduleTitle: moduleTitle,
    moduleVariable: moduleVariable,
    name: name,
    email: email,
    author: author,
    url: url,
    description: moduleSentence,
    username: username,
    license: license,
    licenseName: licenseName
  });
});

/**
 * Create a module folder.
 *
 * @param {String} destDir
 * @param {Object} opts
 */
function createModule (destDir, opts) {
  var srcDir = join(__dirname, '../src');

  log(chalk.dim('Creating module...'));
  log();

  /**
   * Write a file to the filesystem.
   *
   * @param {String} destFile
   * @param {String} contens
   */
  function writeFile (basename, contents) {
    var filename = join(destDir, basename);

    log('Writing "%s" to filesystem...', filename);
    fs.writeFileSync(filename, contents);
  }

  /**
   * Create a file in the destination directory.
   *
   * @param {String} filename
   */
  function generateFile (filename) {
    var contents = fs.readFileSync(join(srcDir, filename), 'utf8');
    var template = Handlebars.compile(contents);

    writeFile(filename, template(opts));
  }

  try {
    // Create the destination directory.
    fs.mkdirSync(destDir);
  } catch (e) {
    log(chalk.red('Directory "%s" already exists'), destDir);
    log();

    return;
  }

  try {
    // Copy files into destination directory.
    fs.readdirSync(srcDir).forEach(generateFile);

    // Write dynamic files manually.
    writeFile(opts.moduleName + '.js', '');
    writeFile('LICENSE', licenseFiles[license]);
    log();
  } catch (e) {
    // Remove everything if an error occurs.
    rimraf.sync(destDir);

    // Rethrow errors.
    log(chalk.red(e.message));
    log();

    return;
  }
}

/**
 * Log strings to the terminal.
 */
function log () {
  console.log(format.apply(null, arguments));
}
<commit_msg>Add module sentence option<commit_after>#!/usr/bin/env node

var chalk = require('chalk');
var program = require('commander');
var licenses = require('osi-licenses');
var licenseFiles = require('osi-licenses-full');
var githubUsername = require('github-username');
var paramCase = require('param-case');
var titleCase = require('title-case');
var camelCase = require('camel-case');
var sentenceCase = require('sentence-case');
var fs = require('fs');
var join = require('path').join;
var format = require('util').format;
var Handlebars = require('handlebars');
var rimraf = require('rimraf');
var somebody = require('somebody');
var pkg = require('../package.json');

// Set the basic author information.
var author = somebody.stringify({
  name: require('git-user-name'),
  email: require('git-user-email')
});

// Program commands.
program
  .version(pkg.version)
  .description(pkg.description)
  .option('-a, --author [value]', 'set the author [' + author + ']', author)
  .option('-l, --license [value]', 'set the module license [MIT]', 'MIT')
  .parse(process.argv);

// Exit when no arguments.
if (!program.args.length) {
  program.help();
  process.exit();
}

var license = program.license;

// Add whitespace below command line.
log();

// Exit on unknown license.
if (!licenses.hasOwnProperty(license)) {
  log('Unknown license (%s), use one of:', license);
  log();
  log(Object.keys(licenses).join(', '));
  log();

  process.exit();
}

// Parse a passed in author.
var info = somebody.parse(process.author || author);

if (!info.name || !info.email) {
  log(
    'Unable to get author name and email, run with %s option',
    chalk.bold('--author')
  );
  log();

  process.exit();
}

var name = info.name;
var email = info.email;
var url = info.url;

var moduleName = paramCase(program.args[0]);
var moduleTitle = titleCase(moduleName);
var moduleVariable = camelCase(moduleName);
var moduleSentence = sentenceCase(moduleName);

var licenseName = licenses[license];

log('Using name: %s', chalk.bold(name));
log('Using email: %s', chalk.bold(email));
log('Using license: %s', chalk.bold(licenseName));
log('Using module name: %s', chalk.bold(moduleName));
log();

log(chalk.dim('Searching for GitHub username...'));
log();

// Search GitHub for your username.
githubUsername(email, function (err, username) {
  if (err) {
    log(chalk.red('Unable to find GitHub username for %s'), email);
    log();

    return;
  }

  log('Using username: %s', chalk.bold(username));
  log();

  return createModule(join(process.cwd(), moduleName), {
    moduleName: moduleName,
    moduleTitle: moduleTitle,
    moduleVariable: moduleVariable,
    moduleSentence: moduleSentence,
    name: name,
    email: email,
    author: author,
    url: url,
    description: moduleSentence,
    username: username,
    license: license,
    licenseName: licenseName
  });
});

/**
 * Create a module folder.
 *
 * @param {String} destDir
 * @param {Object} opts
 */
function createModule (destDir, opts) {
  var srcDir = join(__dirname, '../src');

  log(chalk.dim('Creating module...'));
  log();

  /**
   * Write a file to the filesystem.
   *
   * @param {String} destFile
   * @param {String} contens
   */
  function writeFile (basename, contents) {
    var filename = join(destDir, basename);

    log('Writing "%s" to filesystem...', filename);
    fs.writeFileSync(filename, contents);
  }

  /**
   * Create a file in the destination directory.
   *
   * @param {String} filename
   */
  function generateFile (filename) {
    var contents = fs.readFileSync(join(srcDir, filename), 'utf8');
    var template = Handlebars.compile(contents);

    writeFile(filename, template(opts));
  }

  try {
    // Create the destination directory.
    fs.mkdirSync(destDir);
  } catch (e) {
    log(chalk.red('Directory "%s" already exists'), destDir);
    log();

    return;
  }

  try {
    // Copy files into destination directory.
    fs.readdirSync(srcDir).forEach(generateFile);

    // Write dynamic files manually.
    writeFile(opts.moduleName + '.js', '');
    writeFile('LICENSE', licenseFiles[license]);
    log();
  } catch (e) {
    // Remove everything if an error occurs.
    rimraf.sync(destDir);

    // Rethrow errors.
    log(chalk.red(e.message));
    log();

    return;
  }
}

/**
 * Log strings to the terminal.
 */
function log () {
  console.log(format.apply(null, arguments));
}
<|endoftext|>


<commit_before>'use strict';

var util = require('../util');
var Store = require('../store');
var hat = require('hat');

module.exports = {

  initialize(map, options) {
    this._map = map;
    this._currentFeature = false;
    this._container = map.getContainer();
    this._editStore = new Store([]);
    mapboxgl.util.setOptions(this, options);
    this._drawStore = this.options.geoJSON;
    this._onKeyUp = this._onKeyUp.bind(this);
    this.enable();
  },

  enable() {
    var map = this._map;
    if (map) {
      util.DOM.disableSelection();
      this._container.addEventListener('keyup', this._onKeyUp);
      this._container.classList.add('mapboxgl-draw-activated');
      map.fire('draw.start', { featureType: this.type });
      this.drawStart();
    }
  },

  disable() {
    util.DOM.enableSelection();
    this._container.removeEventListener('keyup', this._onKeyUp);
    this._container.classList.remove('mapboxgl-draw-activated');
    this._map.fire('draw.stop', { featureType: this.type });
    this.drawStop(this._map);
  },

  featureComplete() {
    this._currentFeature = false;
  },

  editCreate(type, coords) {
    var id = hat();
    // Keeps track of temporary linestring guideline that's
    // drawn after the second point is selected.
    if (type === 'LineString') this._editLineGuide = id;
    this._editStore.set(type, id, coords);
    this._map.fire('edit.feature.update', {geojson: this._editStore.getAll()});
  },

  editUnsetGuide() {
    if (this._editLineGuide) this._editStore.unset('LineString', this._editLineGuide);
    this._editLineGuide = false;
  },

  editDestroy() {
    this._editStore.clear();
    this._map.fire('edit.feature.update', {geojson: this._editStore.getAll()});
  },

  drawCreate(type, coords) {
    if (!this._currentFeature) this._currentFeature = hat();
    var feature = this._drawStore.set(type, this._currentFeature, coords);

    this._map.fire('draw.feature.update', {
      geojson: this._drawStore.getAll()
    });

    this._map.fire('draw.created', {
      featureType: this.type,
      feature: feature
    });
  },

  _translate(id, init, pos) {
    if (!this.initialCoords) {
      var f = this._drawStore.getById(id);
      this.initialCoords = f.geometry.coordinates;
      this.type = f.geometry.type;
    }
    var dx = pos.x - init.x;
    var dy = pos.y - init.y;
    var coords;

    if (this.type === 'Polygon')
      coords = this.initialCoords[0].map(p => this._translatePoint(p, dx, dy));
    else if (this.type === 'MultiLineString')
      coords = this.initialCoords.map(line => line.map(p => this._translatePoint(p, dx, dy)));
    else
      coords = this.initialCoords.map(p => this._translatePoint(p, dx, dy));

    var feature = {
      type: 'Feature',
      properties: {
        _drawid: id
      },
      geometry: {
        type: this.type,
        coordinates: this.type === 'Polygon' ? [coords] : coords
      }
    };

    this._drawStore.update(feature);

    console.log(this._drawStore.getAll());
    this._map.fire('edit.feature.update', {
      geojson: this._drawStore.getAll()
    });
  },

  _translatePoint(point, dx, dy) {
    var c = this._map.project([ point[1], point[0] ]);
    c = this._map.unproject([c.x + dx, c.y + dy]);
    return [c.lng, c.lat];
  },

  _onKeyUp(e) {
    switch (e.keyCode) {
      case 27: // ESC
      this.disable();
      break;
    }
  }
};
<commit_msg>remove handlers.js<commit_after><|endoftext|>


<commit_before>import { ok, equal, throws, doesNotThrow } from 'assert';
import { innerHTML, html, use } from 'diffhtml';
import process from 'diffhtml-shared-internals/lib/process';
import PropTypes from 'prop-types';
import Component from '../lib/component';

describe('React Like Component', function() {
  beforeEach(() => {
    process.env.NODE_ENV = 'development';
  });

  after(() => {
    Component.unsubscribeMiddleware();
  });

  it('can make a component', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

        `;
      }
    }

    const domNode = document.createElement('div');
    innerHTML(domNode, html`<${CustomComponent} />`);

    equal(domNode.outerHTML, '

Hello world

');
  });

  it('cannot return multiple elements yet', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

Test

        `;
      }
    }

    const domNode = document.createElement('div');

    throws(() => innerHTML(domNode, html`<${CustomComponent} />`));
  });

  it.skip('can return multiple elements', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

Test

        `;
      }
    }

    const domNode = document.createElement('div');
    innerHTML(domNode, html`<${CustomComponent} />`);

    equal(domNode.outerHTML, '

Hello world

test

');
  });

  describe('Lifecycle', () => {
    it('can map to shouldComponentUpdate', () => {
      let wasCalled = false;

      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }

        shouldComponentUpdate() {
          wasCalled = true;
          return false;
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.setState({ message: 'something' });
      equal(domNode.innerHTML, 'default');
      ok(wasCalled);
    });

    it('can map to componentWillReceiveProps', () => {
      let wasCalled = false;

      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }

        componentWillReceiveProps() {
          wasCalled = true;
          return false;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} someProp="true" />`);
      innerHTML(domNode, html`<${CustomComponent} someProp="false" />`);

      ok(wasCalled);
    });

    it.skip('can map to componentDidMount', () => {});
    it.skip('can map to componentDidUpdate', () => {});
    it.skip('can map to componentWillUnmount', () => {});
    it.skip('can map to componentDidUnmount', () => {});
  });

  describe('Props', () => {
    it('can set simple string', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      const vTree = html`<${CustomComponent} test="true" />`;

      equal(vTree.attributes.test, 'true');
    });

    it('can set complex object', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      const ref = {};
      const vTree = html`<${CustomComponent} test=${ref} />`;

      equal(vTree.attributes.test, ref);
    });

    it('can warn if missing proptypes in development', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      CustomComponent.propTypes = {
        customProperty: PropTypes.string.isRequired,
      };

      const domNode = document.createElement('div');
      const oldConsoleError = console.error;

      let logCalled = false;
      console.error = () => logCalled = true;

      innerHTML(domNode, html`<${CustomComponent} />`);
      console.error = oldConsoleError;
      ok(logCalled);
    });

    it('cannot throw if missing proptypes in production', () => {
      process.env.NODE_ENV = 'production';

      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      CustomComponent.propTypes = {
        customProperty: PropTypes.string.isRequired,
      };

      const domNode = document.createElement('div');

      doesNotThrow(() => innerHTML(domNode, html`<${CustomComponent} />`));
    });

  });

  describe('Refs', () => {
    it('can invoke a ref attribute on a Component', () => {
      let refNode = null;

      class CustomComponent extends Component {
        render() {
          return html`<div ref=${node => (refNode = node)}/>`;
        }
      }

      const domNode = document.createElement('div');

      innerHTML(domNode, html`<${CustomComponent}
        ref=${node => (refNode = node)}
      />`);

      ok(refNode);
    });

    it('can invoke a ref attribute on a DOM Node', () => {
      let refNode = null;

      class CustomComponent extends Component {
        render() {
          return html`

 (refNode = node)} />

`;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      ok(refNode);
      equal(domNode.nodeName, 'DIV');
    });
  });

  describe('State', () => {
    it('will always be an object', () => {
      let state = null;

      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }

        constructor() {
          super();
          state = this.state;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      equal(typeof state, 'object');
    });

    it('can set state in constructor', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor() {
          super();
          this.state.message = 'default'
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      equal(domNode.innerHTML, 'default');
    });

    it('can call setState to re-render the component', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.setState({ message: 'something' });
      equal(domNode.innerHTML, 'something');
    });

  });

  describe('forceUpdate', () => {
    it('can set state manually and call forceUpdate', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.state.message = 'something';
      ref.forceUpdate();
      equal(domNode.innerHTML, 'something');
    });
  });

  describe('Context', () => {
    it.skip('can inherit context from a parent component', () => {
      class ChildComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      class ParentComponent extends Component {
        render() {
          return html`<${ChildComponent} />`;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${ParentComponent} />`);
    });
  });
});
<commit_msg>Fix broken reference to process<commit_after>import { ok, equal, throws, doesNotThrow } from 'assert';
import { innerHTML, html, use } from 'diffhtml';
import { process } from 'diffhtml-shared-internals';
import PropTypes from 'prop-types';
import Component from '../lib/component';

describe('React Like Component', function() {
  beforeEach(() => {
    process.env.NODE_ENV = 'development';
  });

  after(() => {
    Component.unsubscribeMiddleware();
  });

  it('can make a component', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

        `;
      }
    }

    const domNode = document.createElement('div');
    innerHTML(domNode, html`<${CustomComponent} />`);

    equal(domNode.outerHTML, '

Hello world

');
  });

  it('cannot return multiple elements yet', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

Test

        `;
      }
    }

    const domNode = document.createElement('div');

    throws(() => innerHTML(domNode, html`<${CustomComponent} />`));
  });

  it.skip('can return multiple elements', () => {
    class CustomComponent extends Component {
      render() {
        return html`

Hello world

Test

        `;
      }
    }

    const domNode = document.createElement('div');
    innerHTML(domNode, html`<${CustomComponent} />`);

    equal(domNode.outerHTML, '

Hello world

test

');
  });

  describe('Lifecycle', () => {
    it('can map to shouldComponentUpdate', () => {
      let wasCalled = false;

      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }

        shouldComponentUpdate() {
          wasCalled = true;
          return false;
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.setState({ message: 'something' });
      equal(domNode.innerHTML, 'default');
      ok(wasCalled);
    });

    it('can map to componentWillReceiveProps', () => {
      let wasCalled = false;

      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }

        componentWillReceiveProps() {
          wasCalled = true;
          return false;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} someProp="true" />`);
      innerHTML(domNode, html`<${CustomComponent} someProp="false" />`);

      ok(wasCalled);
    });

    it.skip('can map to componentDidMount', () => {});
    it.skip('can map to componentDidUpdate', () => {});
    it.skip('can map to componentWillUnmount', () => {});
    it.skip('can map to componentDidUnmount', () => {});
  });

  describe('Props', () => {
    it('can set simple string', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      const vTree = html`<${CustomComponent} test="true" />`;

      equal(vTree.attributes.test, 'true');
    });

    it('can set complex object', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      const ref = {};
      const vTree = html`<${CustomComponent} test=${ref} />`;

      equal(vTree.attributes.test, ref);
    });

    it('can warn if missing proptypes in development', () => {
      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      CustomComponent.propTypes = {
        customProperty: PropTypes.string.isRequired,
      };

      const domNode = document.createElement('div');
      const oldConsoleError = console.error;

      let logCalled = false;
      console.error = () => logCalled = true;

      innerHTML(domNode, html`<${CustomComponent} />`);
      console.error = oldConsoleError;
      ok(logCalled);
    });

    it('cannot throw if missing proptypes in production', () => {
      process.env.NODE_ENV = 'production';

      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }
      }

      CustomComponent.propTypes = {
        customProperty: PropTypes.string.isRequired,
      };

      const domNode = document.createElement('div');

      doesNotThrow(() => innerHTML(domNode, html`<${CustomComponent} />`));
    });

  });

  describe('Refs', () => {
    it('can invoke a ref attribute on a Component', () => {
      let refNode = null;

      class CustomComponent extends Component {
        render() {
          return html`<div ref=${node => (refNode = node)}/>`;
        }
      }

      const domNode = document.createElement('div');

      innerHTML(domNode, html`<${CustomComponent}
        ref=${node => (refNode = node)}
      />`);

      ok(refNode);
    });

    it('can invoke a ref attribute on a DOM Node', () => {
      let refNode = null;

      class CustomComponent extends Component {
        render() {
          return html`

 (refNode = node)} />

`;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      ok(refNode);
      equal(domNode.nodeName, 'DIV');
    });
  });

  describe('State', () => {
    it('will always be an object', () => {
      let state = null;

      class CustomComponent extends Component {
        render() {
          return html`<div />`;
        }

        constructor() {
          super();
          state = this.state;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      equal(typeof state, 'object');
    });

    it('can set state in constructor', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor() {
          super();
          this.state.message = 'default'
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} />`);
      equal(domNode.innerHTML, 'default');
    });

    it('can call setState to re-render the component', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.setState({ message: 'something' });
      equal(domNode.innerHTML, 'something');
    });

  });

  describe('forceUpdate', () => {
    it('can set state manually and call forceUpdate', () => {
      class CustomComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      let ref = null;
      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${CustomComponent} ref=${node => (ref = node)} />`);

      equal(domNode.innerHTML, 'default');
      ref.state.message = 'something';
      ref.forceUpdate();
      equal(domNode.innerHTML, 'something');
    });
  });

  describe('Context', () => {
    it.skip('can inherit context from a parent component', () => {
      class ChildComponent extends Component {
        render() {
          const { message } = this.state;
          return html`${message}`;
        }

        constructor(props) {
          super(props);
          this.state.message = 'default'
        }
      }

      class ParentComponent extends Component {
        render() {
          return html`<${ChildComponent} />`;
        }
      }

      const domNode = document.createElement('div');
      innerHTML(domNode, html`<${ParentComponent} />`);
    });
  });
});
<|endoftext|>


<commit_before>// libraries
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import { ResponsiveContainer, ComposedChart, Area, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ReferenceLine } from 'recharts'
import moment from 'moment'

// utils

// components
import BasicTooltip from './basic-tooltip'

// logic

export const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']

function sharedStart (array) {
  let A = array.concat().sort()
  let a1 = A[0]
  let a2 = A[A.length - 1]
  let L = a1.length
  let i = 0
  while (i < L && a1.charAt(i) === a2.charAt(i)) { i++ }
  return a1.substring(0, i)
}

const alphabeticalFacetSorter = (a, b) => a.name.localeCompare(b.name)

function getGraphData (graph, controls) {
  const { results, keys } = graph
  const { percentages, compareWith } = controls

  const graphData = results.map(oldRow => {
    const time = oldRow.time
    const row = Object.assign({}, oldRow, { time: moment(time).valueOf() })

    if (percentages) {
      let total = 0
      for (const key of keys) {
        total += parseFloat(row[key] || 0)
      }
      for (const key of keys) {
        row[key + '__%'] = total !== 0 ? (parseFloat(row[key]) / total * 100) : 0
      }

      if (compareWith) {
        let compareWithTotal = 0
        for (const key of keys) {
          compareWithTotal += parseFloat(row['compareWith::' + key] || 0)
        }
        for (const key of keys) {
          row['compareWith::' + key + '__%'] = compareWithTotal !== 0 ? (parseFloat(row['compareWith::' + key]) / compareWithTotal * 100) : 0
        }
      }
    }
    return row
  })

  return graphData
}

export class Graph extends Component {
  static propTypes = {
    graph: PropTypes.object,

    controls: PropTypes.shape({
      type: PropTypes.oneOf(['area', 'bar', 'line']).isRequired,
      sort: PropTypes.oneOf(['123', 'abc']).isRequired,
      cumulative: PropTypes.bool,
      percentages: PropTypes.bool,
      labels: PropTypes.bool,
      compareWith: PropTypes.number
    }).isRequired
  }

  static defaultProps = {
    tooltip: BasicTooltip
  }

  constructor (props) {
    super(props)

    this.state = {
      visibility: {},
      graphData: getGraphData(props.graph, props.controls)
    }
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.graph !== nextProps.graph || this.props.controls !== nextProps.controls) {
      this.setState({
        graphData: getGraphData(nextProps.graph, nextProps.controls)
      })
    }
  }

  handleClick = (o) => {
    const { dataKey } = o
    const { visibility } = this.state

    const key = dataKey.replace(/__hidden$/, '')

    this.setState({
      visibility: { ...visibility, [key]: typeof visibility[key] === 'undefined' ? false : !visibility[key] }
    })
  }

  timeFormatter (time) {
    return moment(time).format('MMM DD')
  }

  yearFormatter (time) {
    return moment(time).format('YYYY')
  }

  quarterFormatter (time) {
    return 'Q' + moment(time).format('Q YYYY')
  }

  shortQuarterFormatter (time) {
    return 'Q' + moment(time).format('Q\'YY')
  }

  monthFormatter (time) {
    return moment(time).format(moment(time).month() === 0 ? 'YYYY' : 'MMMM')
  }

  shortMonthFormatter (time) {
    return moment(time).format(moment(time).month() === 0 ? 'YYYY' : 'MMM')
  }

  getTicks () {
    // const { data, timeGroup } = this.props
    const { graph: { timeGroup } } = this.props
    const { graphData: data } = this.state

    let ticks = []

    if (data.length === 0) {
      return ticks
    }

    const timeStart = data[0].time
    const timeEnd = data[data.length - 1].time

    const day = 86000 * 1000

    let unit = 'month'

    if (timeGroup === 'month' || timeGroup === 'year' || timeGroup === 'quarter') {
      unit = timeGroup
    } else if (timeEnd - timeStart < 32 * day && timeGroup !== 'week') {
      unit = 'day'
    } else if (timeEnd - timeStart < 365 * day) {
      unit = 'week'
    }

    let time = moment(timeStart).startOf(unit)
    if (time >= timeStart) {
      ticks.push(time.valueOf())
    }
    while (time < timeEnd) {
      time = time.add(1, unit)
      if (time <= timeEnd) {
        ticks.push(time.valueOf())
      }
    }

    const tickFormatter = unit === 'year' ? this.yearFormatter
      : unit === 'quarter' ? (ticks.length > 5 ? this.shortQuarterFormatter : this.quarterFormatter)
        : unit === 'month' ? (ticks.length > 13 ? this.shortMonthFormatter : this.monthFormatter)
          : this.timeFormatter

    return { ticks, tickFormatter }
  }

  getKeysWithMeta () {
    const { graph: { keys } } = this.props
    const { visibility } = this.state
    let i = 0

    let nameSubString = keys.length === 1 ? keys[0].split('.')[0].length + 1 : sharedStart(keys).length

    return keys.filter(v => v).map(v => {
      const visible = typeof visibility[v] === 'undefined' || visibility[v]
      const object = {
        key: v,
        name: v.substring(nameSubString).split(/[_!$]/).join(' ') || 'empty',
        visible: visible,
        color: visible ? colors[i] : 'white'
      }
      i += 1
      return object
    })
  }

  getLineData = (key, stacked, compareWith = null) => {
    const { percentages, labels, type } = this.props.controls

    let data = {
      key: `${compareWith ? 'compareWith::' : ''}${key.key}${percentages ? '__%' : ''}${key.visible ? '' : '__hidden'}`,
      type: 'linear',
      dataKey: `${compareWith ? 'compareWith::' : ''}${key.key}${percentages ? '__%' : ''}${key.visible ? '' : '__hidden'}`,
      name: key.name,
      stroke: key.color,
      strokeOpacity: compareWith ? 0.5 : 1,
      strokeWidth: 1,
      fill: key.color,
      fillOpacity: type === 'bar' ? (compareWith ? 0.5 : 0.9) : 0.6,
      legendType: 'circle',
      label: labels ? this.renderLabel : false,
      dot: { r: 2, fill: key.color, fillOpacity: 0.5 },
      activeDot: {r: 6},
      isAnimationActive: false,
      stackId: (compareWith ? 'compareWith::' : '') + (stacked ? (key.key.indexOf('$$') > 0 ? key.key.split('$$')[0] : '1') : key.key)
    }

    return data
  }

  renderLabel = (props) => {
    const { percentages } = this.props.controls

    const { x, y, stroke, value, key } = props
    const displayValue = Array.isArray(value) ? Math.round(value[1] - value[0]) : Math.round(parseFloat(value))

    if (percentages && displayValue === 100) {
      return null
    }

    return (
      <g key={key}>
        <text x={x}
          y={y}
          dy={-5}
          fill={'#fff'}
          stroke={'#fff'}
          strokeWidth={3}
          strokeOpacity={0.9}
          fontSize={10}
          textAnchor='middle'>
          {displayValue}{percentages ? '%' : ''}
        </text>
        <text x={x}
          y={y}
          dy={-5}
          fill={stroke}
          fontSize={10}
          textAnchor='middle'>
          {displayValue}{percentages ? '%' : ''}
        </text>
      </g>
    )
  }

  render () {
    const { graph, controls, tooltip: TooltipProp } = this.props
    const { graphData } = this.state
    const { sort, percentages, type, labels, compareWith } = controls

    const nullLineNeeded = false
    const unit = ''
    const facets = graph.facets && graph.facets.length > 0
    const { timeGroup } = graph

    const keysWithMeta = this.getKeysWithMeta()

    // change the key on visibilty changes so the lines would update
    // changing this triggers the chart to be torn down and re-rendered
    const key = keysWithMeta.map(k => `${k.visible}`).join(',') + `${labels ? '-labels' : ''}${type === 'line' ? '-line' : ''}-${sort}`

    const { ticks, tickFormatter } = this.getTicks()

    let xDomain = ['dataMin', 'dataMax']

    if (type === 'bar' && graphData.length > 0) {
      xDomain = [
        moment(graphData[0].time).add(-0.5, timeGroup).valueOf(),
        moment(graphData[graphData.length - 1].time).add(0.5, timeGroup).valueOf()
      ]
    }

    const sortedKeysWithMeta = facets
      ? sort === 'abc'
        ? keysWithMeta.sort(alphabeticalFacetSorter)
        : keysWithMeta.reverse()
      : keysWithMeta

    return (
      <ResponsiveContainer>
        <ComposedChart
          data={graphData}
          key={key}
          margin={{top: 0, right: 10, left: 0, bottom: 0}}>
          <Legend
            verticalAlign='top'
            align='right'
            height={25}
            iconSize={10}
            wrapperStyle={{fontSize: 12, marginRight: -10}}
            onClick={this.handleClick}
            onMouseOver={this.handleMouseEnter}
            onMouseOut={this.handleMouseLeave} />
          <XAxis
            type='number'
            dataKey='time'
            domain={xDomain}
            tickFormatter={tickFormatter}
            ticks={ticks} />
          <YAxis
            domain={percentages ? [0, 100] : ['auto', 'auto']}
            interval={0}
            tickFormatter={percentages ? (y) => `${Math.round(y)}%` : (y) => `${y.toLocaleString('en')}${unit}`}
            allowDecimals={false} />
          <CartesianGrid />
          <Tooltip content={<TooltipProp graph={graph} controls={controls} />} />
          {nullLineNeeded ? (
            <ReferenceLine y={0} stroke='red' alwaysShow />
          ) : null}
          {compareWith && sortedKeysWithMeta.map(key => <Bar {...this.getLineData(key, facets, compareWith)} />)}
          {sortedKeysWithMeta.map(key => (
            type === 'area'
              ? <Area {...this.getLineData(key, facets)} />
              : type === 'bar'
                ? <Bar {...this.getLineData(key, facets)} />
                : <Line {...this.getLineData(key, facets)} />))}
        </ComposedChart>
      </ResponsiveContainer>
    )
  }
}
<commit_msg>no dots for bar graphs<commit_after>// libraries
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import { ResponsiveContainer, ComposedChart, Area, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ReferenceLine } from 'recharts'
import moment from 'moment'

// utils

// components
import BasicTooltip from './basic-tooltip'

// logic

export const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']

function sharedStart (array) {
  let A = array.concat().sort()
  let a1 = A[0]
  let a2 = A[A.length - 1]
  let L = a1.length
  let i = 0
  while (i < L && a1.charAt(i) === a2.charAt(i)) { i++ }
  return a1.substring(0, i)
}

const alphabeticalFacetSorter = (a, b) => a.name.localeCompare(b.name)

function getGraphData (graph, controls) {
  const { results, keys } = graph
  const { percentages, compareWith } = controls

  const graphData = results.map(oldRow => {
    const time = oldRow.time
    const row = Object.assign({}, oldRow, { time: moment(time).valueOf() })

    if (percentages) {
      let total = 0
      for (const key of keys) {
        total += parseFloat(row[key] || 0)
      }
      for (const key of keys) {
        row[key + '__%'] = total !== 0 ? (parseFloat(row[key]) / total * 100) : 0
      }

      if (compareWith) {
        let compareWithTotal = 0
        for (const key of keys) {
          compareWithTotal += parseFloat(row['compareWith::' + key] || 0)
        }
        for (const key of keys) {
          row['compareWith::' + key + '__%'] = compareWithTotal !== 0 ? (parseFloat(row['compareWith::' + key]) / compareWithTotal * 100) : 0
        }
      }
    }
    return row
  })

  return graphData
}

export class Graph extends Component {
  static propTypes = {
    graph: PropTypes.object,

    controls: PropTypes.shape({
      type: PropTypes.oneOf(['area', 'bar', 'line']).isRequired,
      sort: PropTypes.oneOf(['123', 'abc']).isRequired,
      cumulative: PropTypes.bool,
      percentages: PropTypes.bool,
      labels: PropTypes.bool,
      compareWith: PropTypes.number
    }).isRequired
  }

  static defaultProps = {
    tooltip: BasicTooltip
  }

  constructor (props) {
    super(props)

    this.state = {
      visibility: {},
      graphData: getGraphData(props.graph, props.controls)
    }
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.graph !== nextProps.graph || this.props.controls !== nextProps.controls) {
      this.setState({
        graphData: getGraphData(nextProps.graph, nextProps.controls)
      })
    }
  }

  handleClick = (o) => {
    const { dataKey } = o
    const { visibility } = this.state

    const key = dataKey.replace(/__hidden$/, '')

    this.setState({
      visibility: { ...visibility, [key]: typeof visibility[key] === 'undefined' ? false : !visibility[key] }
    })
  }

  timeFormatter (time) {
    return moment(time).format('MMM DD')
  }

  yearFormatter (time) {
    return moment(time).format('YYYY')
  }

  quarterFormatter (time) {
    return 'Q' + moment(time).format('Q YYYY')
  }

  shortQuarterFormatter (time) {
    return 'Q' + moment(time).format('Q\'YY')
  }

  monthFormatter (time) {
    return moment(time).format(moment(time).month() === 0 ? 'YYYY' : 'MMMM')
  }

  shortMonthFormatter (time) {
    return moment(time).format(moment(time).month() === 0 ? 'YYYY' : 'MMM')
  }

  getTicks () {
    // const { data, timeGroup } = this.props
    const { graph: { timeGroup } } = this.props
    const { graphData: data } = this.state

    let ticks = []

    if (data.length === 0) {
      return ticks
    }

    const timeStart = data[0].time
    const timeEnd = data[data.length - 1].time

    const day = 86000 * 1000

    let unit = 'month'

    if (timeGroup === 'month' || timeGroup === 'year' || timeGroup === 'quarter') {
      unit = timeGroup
    } else if (timeEnd - timeStart < 32 * day && timeGroup !== 'week') {
      unit = 'day'
    } else if (timeEnd - timeStart < 365 * day) {
      unit = 'week'
    }

    let time = moment(timeStart).startOf(unit)
    if (time >= timeStart) {
      ticks.push(time.valueOf())
    }
    while (time < timeEnd) {
      time = time.add(1, unit)
      if (time <= timeEnd) {
        ticks.push(time.valueOf())
      }
    }

    const tickFormatter = unit === 'year' ? this.yearFormatter
      : unit === 'quarter' ? (ticks.length > 5 ? this.shortQuarterFormatter : this.quarterFormatter)
        : unit === 'month' ? (ticks.length > 13 ? this.shortMonthFormatter : this.monthFormatter)
          : this.timeFormatter

    return { ticks, tickFormatter }
  }

  getKeysWithMeta () {
    const { graph: { keys } } = this.props
    const { visibility } = this.state
    let i = 0

    let nameSubString = keys.length === 1 ? keys[0].split('.')[0].length + 1 : sharedStart(keys).length

    return keys.filter(v => v).map(v => {
      const visible = typeof visibility[v] === 'undefined' || visibility[v]
      const object = {
        key: v,
        name: v.substring(nameSubString).split(/[_!$]/).join(' ') || 'empty',
        visible: visible,
        color: visible ? colors[i] : 'white'
      }
      i += 1
      return object
    })
  }

  getLineData = (key, stacked, compareWith = null) => {
    const { percentages, labels, type } = this.props.controls

    let data = {
      key: `${compareWith ? 'compareWith::' : ''}${key.key}${percentages ? '__%' : ''}${key.visible ? '' : '__hidden'}`,
      type: 'linear',
      dataKey: `${compareWith ? 'compareWith::' : ''}${key.key}${percentages ? '__%' : ''}${key.visible ? '' : '__hidden'}`,
      name: key.name,
      stroke: key.color,
      strokeOpacity: compareWith ? 0.5 : 1,
      strokeWidth: 1,
      fill: key.color,
      fillOpacity: type === 'bar' ? (compareWith ? 0.5 : 0.9) : 0.6,
      legendType: 'circle',
      label: labels ? this.renderLabel : false,
      isAnimationActive: false,
      stackId: (compareWith ? 'compareWith::' : '') + (stacked ? (key.key.indexOf('$$') > 0 ? key.key.split('$$')[0] : '1') : key.key)
    }

    if (type !== 'bar') {
      data.dot = { r: 2, fill: key.color, fillOpacity: 0.5 }
      data.activeDot = { r: 6 }
    }

    return data
  }

  renderLabel = (props) => {
    const { percentages } = this.props.controls

    const { x, y, stroke, value, key } = props
    const displayValue = Array.isArray(value) ? Math.round(value[1] - value[0]) : Math.round(parseFloat(value))

    if (percentages && displayValue === 100) {
      return null
    }

    return (
      <g key={key}>
        <text x={x}
          y={y}
          dy={-5}
          fill={'#fff'}
          stroke={'#fff'}
          strokeWidth={3}
          strokeOpacity={0.9}
          fontSize={10}
          textAnchor='middle'>
          {displayValue}{percentages ? '%' : ''}
        </text>
        <text x={x}
          y={y}
          dy={-5}
          fill={stroke}
          fontSize={10}
          textAnchor='middle'>
          {displayValue}{percentages ? '%' : ''}
        </text>
      </g>
    )
  }

  render () {
    const { graph, controls, tooltip: TooltipProp } = this.props
    const { graphData } = this.state
    const { sort, percentages, type, labels, compareWith } = controls

    const nullLineNeeded = false
    const unit = ''
    const facets = graph.facets && graph.facets.length > 0
    const { timeGroup } = graph

    const keysWithMeta = this.getKeysWithMeta()

    // change the key on visibilty changes so the lines would update
    // changing this triggers the chart to be torn down and re-rendered
    const key = keysWithMeta.map(k => `${k.visible}`).join(',') + `${labels ? '-labels' : ''}${type === 'line' ? '-line' : ''}-${sort}`

    const { ticks, tickFormatter } = this.getTicks()

    let xDomain = ['dataMin', 'dataMax']

    if (type === 'bar' && graphData.length > 0) {
      xDomain = [
        moment(graphData[0].time).add(-0.5, timeGroup).valueOf(),
        moment(graphData[graphData.length - 1].time).add(0.5, timeGroup).valueOf()
      ]
    }

    const sortedKeysWithMeta = facets
      ? sort === 'abc'
        ? keysWithMeta.sort(alphabeticalFacetSorter)
        : keysWithMeta.reverse()
      : keysWithMeta

    return (
      <ResponsiveContainer>
        <ComposedChart
          data={graphData}
          key={key}
          margin={{top: 0, right: 10, left: 0, bottom: 0}}>
          <Legend
            verticalAlign='top'
            align='right'
            height={25}
            iconSize={10}
            wrapperStyle={{fontSize: 12, marginRight: -10}}
            onClick={this.handleClick}
            onMouseOver={this.handleMouseEnter}
            onMouseOut={this.handleMouseLeave} />
          <XAxis
            type='number'
            dataKey='time'
            domain={xDomain}
            tickFormatter={tickFormatter}
            ticks={ticks} />
          <YAxis
            domain={percentages ? [0, 100] : ['auto', 'auto']}
            interval={0}
            tickFormatter={percentages ? (y) => `${Math.round(y)}%` : (y) => `${y.toLocaleString('en')}${unit}`}
            allowDecimals={false} />
          <CartesianGrid />
          <Tooltip content={<TooltipProp graph={graph} controls={controls} />} />
          {nullLineNeeded ? (
            <ReferenceLine y={0} stroke='red' alwaysShow />
          ) : null}
          {compareWith && sortedKeysWithMeta.map(key => <Bar {...this.getLineData(key, facets, compareWith)} />)}
          {sortedKeysWithMeta.map(key => (
            type === 'area'
              ? <Area {...this.getLineData(key, facets)} />
              : type === 'bar'
                ? <Bar {...this.getLineData(key, facets)} />
                : <Line {...this.getLineData(key, facets)} />))}
        </ComposedChart>
      </ResponsiveContainer>
    )
  }
}
<|endoftext|>


<commit_before>import JSCookie from 'js-cookie';
import rison from 'rison';
import { setClickCookie } from 'mwp-tracking-plugin/lib/util/clickState';

import { parseQueryResponse } from '../util/fetchUtils';

export const CSRF_HEADER = 'x-csrf-jwt';
export const CSRF_HEADER_COOKIE = 'x-csrf-jwt-header';

/**
 * that the request will have the required OAuth and CSRF credentials and constructs
 * the `fetch` call arguments based on the request method. It also records the
 * CSRF header value in a cookie for use as a CSRF header in future fetches.
 *
 * @param {String} apiUrl the general-purpose endpoint for API calls to the
 *   application server
 * @param {Array} queries the queries to send - must all use the same `method`
 * @param {Object} meta additional characteristics of the request, e.g.
 *   click tracking data
 * @return {Object} { url, config } arguments for a fetch call
 */
export const getFetchArgs = (apiUrl, queries, meta) => {
	if (process.env.NODE_ENV !== 'production') {
		// basic query validation for dev. This block will be stripped out by
		// minification in prod bundle.
		try {
			rison.encode_array(queries);
		} catch (err) {
			console.error(err);
			console.error(
				'Problem encoding queries',
				'- please ensure that there are no undefined params',
				JSON.stringify(queries, null, 2)
			);
		}
	}
	const headers = {};
	const method = ((queries[0].meta || {}).method || 'GET') // fallback to 'get'
		.toUpperCase(); // must be upper case - requests can fail silently otherwise

	const hasBody = method === 'POST' || method === 'PATCH';
	const isFormData = queries[0].params instanceof FormData;
	const isDelete = method === 'DELETE';

	const searchParams = new URLSearchParams();
	searchParams.append('queries', rison.encode_array(queries));

	if (meta) {
		const {
			clickTracking,
			onSuccess, // eslint-disable-line no-unused-vars
			onError, // eslint-disable-line no-unused-vars
			...metadata
		} = meta;

		if (clickTracking) {
			setClickCookie(clickTracking);
		}

		// send other metadata in searchParams
		const encodedMetadata = metadata && rison.encode_object(metadata);
		if (encodedMetadata) {
			// send other metadata in searchParams
			searchParams.append('metadata', encodedMetadata);
		}
	}

	if (!isFormData) {
		// need to manually specify content-type for any non-multipart request
		headers['content-type'] = hasBody
			? 'application/x-www-form-urlencoded'
			: 'application/json';
	}

	if (hasBody || isDelete) {
		headers[CSRF_HEADER] = JSCookie.get(CSRF_HEADER_COOKIE);
	}

	const config = {
		method,
		headers,
		credentials: 'same-origin', // allow response to set-cookies
	};
	if (hasBody) {
		config.body = isFormData ? queries[0].params : searchParams.toString();
	}
	const useQueryString = isFormData || !hasBody;
	const url = useQueryString ? `${apiUrl}?${searchParams}` : apiUrl;
	return {
		url,
		config,
	};
};

const _fetchQueryResponse = (apiUrl, queries, meta) => {
	if (queries.length === 0) {
		// no queries => no responses (no need to fetch)
		return Promise.resolve({ responses: [] });
	}

	const { url, config } = getFetchArgs(apiUrl, queries, meta);
	return fetch(url, config)
		.then(queryResponse => queryResponse.json())
		.catch(err => {
			console.error(
				JSON.stringify({
					err: err.stack,
					message: 'App server API fetch error',
					context: config,
				})
			);
			throw err; // handle the error upstream
		});
};
/**
 * Wrapper around `fetch` to send an array of queries to the server and organize
 * the responses.
*
 * **IMPORTANT**: This function should _only_ be called from the browser. The
 * server should never need to call itself over HTTP
 *
 * @param {String} apiUrl the general-purpose endpoint for API calls to the
 *   application server
 * @param {Array} queries the queries to send - must all use the same `method`
 * @param {Object} meta additional characteristics of the request, e.g.
 *   click tracking data
 * @return {Promise} resolves with a `{queries, responses}` object
 */
const fetchQueries = apiUrl => (queries, meta) => {
	if (
		typeof window === 'undefined' &&
		typeof test === 'undefined' // not in browser // not in testing env (global set by Jest)
	) {
		throw new Error('fetchQueries was called on server - cannot continue');
	}

	return _fetchQueryResponse(apiUrl, queries, meta).then(queryResponse => ({
		...parseQueryResponse(queries)(queryResponse),
	}));
};

export default fetchQueries;
<commit_msg>apply logic to browser fetchQueries<commit_after>import JSCookie from 'js-cookie';
import rison from 'rison';
import { setClickCookie } from 'mwp-tracking-plugin/lib/util/clickState';

import {
	parseQueryResponse,
	MEMBER_COOKIE_NAME,
	getAuthedQueryFilter,
} from '../util/fetchUtils';

export const CSRF_HEADER = 'x-csrf-jwt';
export const CSRF_HEADER_COOKIE = 'x-csrf-jwt-header';

/**
 * that the request will have the required OAuth and CSRF credentials and constructs
 * the `fetch` call arguments based on the request method. It also records the
 * CSRF header value in a cookie for use as a CSRF header in future fetches.
 *
 * @param {String} apiUrl the general-purpose endpoint for API calls to the
 *   application server
 * @param {Array} queries the queries to send - must all use the same `method`
 * @param {Object} meta additional characteristics of the request, e.g.
 *   click tracking data
 * @return {Object} { url, config } arguments for a fetch call
 */
export const getFetchArgs = (apiUrl, queries, meta) => {
	if (process.env.NODE_ENV !== 'production') {
		// basic query validation for dev. This block will be stripped out by
		// minification in prod bundle.
		try {
			rison.encode_array(queries);
		} catch (err) {
			console.error(err);
			console.error(
				'Problem encoding queries',
				'- please ensure that there are no undefined params',
				JSON.stringify(queries, null, 2)
			);
		}
	}
	const headers = {};
	const method = ((queries[0].meta || {}).method || 'GET') // fallback to 'get'
		.toUpperCase(); // must be upper case - requests can fail silently otherwise

	const hasBody = method === 'POST' || method === 'PATCH';
	const isFormData = queries[0].params instanceof FormData;
	const isDelete = method === 'DELETE';

	const searchParams = new URLSearchParams();
	searchParams.append('queries', rison.encode_array(queries));

	if (meta) {
		const {
			clickTracking,
			onSuccess, // eslint-disable-line no-unused-vars
			onError, // eslint-disable-line no-unused-vars
			...metadata
		} = meta;

		if (clickTracking) {
			setClickCookie(clickTracking);
		}

		// send other metadata in searchParams
		const encodedMetadata = metadata && rison.encode_object(metadata);
		if (encodedMetadata) {
			// send other metadata in searchParams
			searchParams.append('metadata', encodedMetadata);
		}
	}

	if (!isFormData) {
		// need to manually specify content-type for any non-multipart request
		headers['content-type'] = hasBody
			? 'application/x-www-form-urlencoded'
			: 'application/json';
	}

	if (hasBody || isDelete) {
		headers[CSRF_HEADER] = JSCookie.get(CSRF_HEADER_COOKIE);
	}

	const config = {
		method,
		headers,
		credentials: 'same-origin', // allow response to set-cookies
	};
	if (hasBody) {
		config.body = isFormData ? queries[0].params : searchParams.toString();
	}
	const useQueryString = isFormData || !hasBody;
	const url = useQueryString ? `${apiUrl}?${searchParams}` : apiUrl;
	return {
		url,
		config,
	};
};

const _fetchQueryResponse = (apiUrl, queries, meta) => {
	if (queries.length === 0) {
		// no queries => no responses (no need to fetch)
		return Promise.resolve({ responses: [] });
	}

	const { url, config } = getFetchArgs(apiUrl, queries, meta);
	return fetch(url, config)
		.then(queryResponse => queryResponse.json())
		.catch(err => {
			console.error(
				JSON.stringify({
					err: err.stack,
					message: 'App server API fetch error',
					context: config,
				})
			);
			throw err; // handle the error upstream
		});
};
/**
 * Wrapper around `fetch` to send an array of queries to the server and organize
 * the responses.
*
 * **IMPORTANT**: This function should _only_ be called from the browser. The
 * server should never need to call itself over HTTP
 *
 * @param {String} apiUrl the general-purpose endpoint for API calls to the
 *   application server
 * @param {Array} queries the queries to send - must all use the same `method`
 * @param {Object} meta additional characteristics of the request, e.g.
 *   click tracking data
 * @return {Promise} resolves with a `{queries, responses}` object
 */
const fetchQueries = apiUrl => (queries, meta) => {
	if (
		typeof window === 'undefined' &&
		typeof test === 'undefined' // not in browser // not in testing env (global set by Jest)
	) {
		throw new Error('fetchQueries was called on server - cannot continue');
	}

	const memberCookie = JSCookie.get(MEMBER_COOKIE_NAME);
	const authedQueries = getAuthedQueryFilter(memberCookie);
	const validQueries = queries.filter(authedQueries);
	return _fetchQueryResponse(
		apiUrl,
		validQueries,
		meta
	).then(queryResponse => ({
		...parseQueryResponse(validQueries)(queryResponse),
	}));
};

export default fetchQueries;
<|endoftext|>


<commit_before>'use strict';

const path = require('path');
const process = require('process');
const fs = require('fs-extra');
const JobSrc = require('../../lib/job-src');

describe('JobSrc', () => {
    let jobFile;
    const jobPath = path.join(process.cwd(), 'test', 'fixtures');

    beforeEach(() => {
        jobFile = {};
    });

    function writeJob(jobName = 'jobFile.json') {
        fs.writeJsonSync(path.join(jobPath, jobName), jobFile, { spaces: 4 });
    }

    function deleteJob(jobName = 'jobFile.json') {
        fs.removeSync(path.join(jobPath, jobName));
    }

    it('should throw an error on a bad job path', () => {
        try {
            const job = new JobSrc('badDir', 'someJob.json'); // eslint-disable-line no-unused-vars
        } catch (e) {
            expect(e).toBe('Cannot find badDir/someJob.json, check your path and file name and try again');
        }
    });

    it('job should throw an error if bad content', () => {
        jobFile.name = 'jobName';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'someop'
            }
        ];

        writeJob();
        try {
            const job = new JobSrc(jobPath, 'jobFile.json'); // eslint-disable-line no-unused-vars
        } catch (e) {
            expect(e).toBe('Job must have a name, workers, and at least 2 operations');
        }
        deleteJob();
    });

    it('should return correct version', () => {
        const jobName = 'legitJob.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const { version } = require('../../package.json');
        try {
            const job = new JobSrc(jobPath, jobName);
            expect(job.version).toBe(version);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should add metaData to job contents', () => {
        const jobName = 'addMetaData.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const { version } = require('../../package.json');
        try {
            const job = new JobSrc(jobPath, jobName);
            job.addMetaData('1234', 'localhost');
            const { content } = job;
            expect(content.__metadata.cli.version).toBe(version);
            expect(content.__metadata.cli.job_id).toBe('1234');
            expect(content.__metadata.cli.cluster).toBe('localhost');
            expect(content.__metadata.cli.updated).toBeDefined();
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should check presence of metadataCheck false', () => {
        const jobName = 'metaCheck.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        try {
            const job = new JobSrc(jobPath, jobName);
            expect(job.metaDataCheck()).toBe(false);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should check presence of metadataCheck true', () => {
        const jobName = 'metaCheck.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        try {
            const job = new JobSrc(jobPath, 'metaCheck.json');
            job.addMetaData('1234', 'localhost');
            expect(job.metaDataCheck()).toBe(true);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should overwrite old job file', () => {
        const jobName = 'overwrite.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const job = new JobSrc(jobPath, jobName);
        job.addMetaData('1234', 'localhost');
        job.overwrite();
        const overwrittenPath = path.join(jobPath, jobName);
        const overwritten = fs.readJsonSync(overwrittenPath);
        expect(overwritten.__metadata).toBeDefined();
        deleteJob(jobName);
    });
});
<commit_msg>updated test path<commit_after>'use strict';

const path = require('path');
const fs = require('fs-extra');
const { createTempDirSync } = require('jest-fixtures');
const JobSrc = require('../../lib/job-src');

describe('JobSrc', () => {
    let jobFile;
    const jobPath = createTempDirSync();

    beforeEach(() => {
        jobFile = {};
    });

    function writeJob(jobName = 'jobFile.json') {
        fs.writeJsonSync(path.join(jobPath, jobName), jobFile, { spaces: 4 });
    }

    function deleteJob(jobName = 'jobFile.json') {
        fs.removeSync(path.join(jobPath, jobName));
    }

    it('should throw an error on a bad job path', () => {
        try {
            const job = new JobSrc('badDir', 'someJob.json'); // eslint-disable-line no-unused-vars
        } catch (e) {
            expect(e).toBe('Cannot find badDir/someJob.json, check your path and file name and try again');
        }
    });

    it('job should throw an error if bad content', () => {
        jobFile.name = 'jobName';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'someop'
            }
        ];

        writeJob();
        try {
            const job = new JobSrc(jobPath, 'jobFile.json'); // eslint-disable-line no-unused-vars
        } catch (e) {
            expect(e).toBe('Job must have a name, workers, and at least 2 operations');
        }
        deleteJob();
    });

    it('should return correct version', () => {
        const jobName = 'legitJob.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const { version } = require('../../package.json');
        try {
            const job = new JobSrc(jobPath, jobName);
            expect(job.version).toBe(version);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should add metaData to job contents', () => {
        const jobName = 'addMetaData.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const { version } = require('../../package.json');
        try {
            const job = new JobSrc(jobPath, jobName);
            job.addMetaData('1234', 'localhost');
            const { content } = job;
            expect(content.__metadata.cli.version).toBe(version);
            expect(content.__metadata.cli.job_id).toBe('1234');
            expect(content.__metadata.cli.cluster).toBe('localhost');
            expect(content.__metadata.cli.updated).toBeDefined();
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should check presence of metadataCheck false', () => {
        const jobName = 'metaCheck.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        try {
            const job = new JobSrc(jobPath, jobName);
            expect(job.metaDataCheck()).toBe(false);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should check presence of metadataCheck true', () => {
        const jobName = 'metaCheck.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        try {
            const job = new JobSrc(jobPath, 'metaCheck.json');
            job.addMetaData('1234', 'localhost');
            expect(job.metaDataCheck()).toBe(true);
        } catch (e) {
            fail(e);
        }
        deleteJob(jobName);
    });

    it('should overwrite old job file', () => {
        const jobName = 'overwrite.json';
        jobFile.name = 'goodJob';
        jobFile.workers = 5;
        jobFile.operations = [
            {
                _op: 'one'
            },
            {
                _op: 'two'
            }
        ];
        writeJob(jobName);
        const job = new JobSrc(jobPath, jobName);
        job.addMetaData('1234', 'localhost');
        job.overwrite();
        const overwrittenPath = path.join(jobPath, jobName);
        const overwritten = fs.readJsonSync(overwrittenPath);
        expect(overwritten.__metadata).toBeDefined();
        deleteJob(jobName);
    });
});
<|endoftext|>