<commit_before>// Configuration...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const BACKGROUND_COLOR = '#eaeaea';
const BORDER_COLOR = '#e4e4e4';
const HIGHLIGHT_COLOR = '#f1f1f1';
const BORDER_WIDTH = 4;

const WALL_COLOR_DARK = '#333333';
const WALL_COLOR_MEDIUM = '#555555';
const WALL_COLOR_LIGHT = '#777777';
const WALL_THICKNESS = 3;

const GRID_COLOR = '#';
const GRID_WIDTH = 2;
var GRID_SPACING = 24; // Can adjust to zoom in/out

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Object definitions...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Something in the world is drawable, so everything should inherit from this.
 */
function Drawable() {

}

Drawable.prototype = {
    draw: function (ctx, offX, offY) {
        alert("Inherit the draw function...");
    },
    mousemoveHandler: function () {
        alert("Inherit the mousemove function...");
    },
    mousedownHandler: function (evt) {
        alert("Inherit the mousedown function...");
    },
    mouseupHandler: function (evt) {
        alert("Inherit the mouseup function...")
    }
};

/**
 * A room is a container/"layer" for walls and other household objects.
 */
function Room() {
    this.walls = [];
}
Room.prototype = Drawable;
Room.prototype.constructor = Room;

/**
 * A wall has a start and endpoint, and fill in-between.
 */
function Wall(colA, rowA, colB, rowB) {
    this.endpointA = {c: colA, r: rowA, moving: false};
    this.endpointB = {c: colB, r: rowB, moving: false};
    this.draw = function (ctx, offX, offY) {
        // Upper-left corners of each endpoint.
        var aX = this.endpointA.c * GRID_SPACING - offX;
        var aY = this.endpointA.r * GRID_SPACING - offY;
        var bX = this.endpointB.c * GRID_SPACING - offX;
        var bY = this.endpointB.r * GRID_SPACING - offY;

        ctx.lineWidth = WALL_THICKNESS;

        // Draw a wall connecting the two endpoints. We want this wall to be of bounding thickness, so let's think about
        // this. The innermost and outermost pairs of verts won't have a line connecting them - we just want lines
        // between the middle-distance pairs of verts, to form the wall.
        ctx.fillStyle = WALL_COLOR_LIGHT;
        ctx.strokeStyle = WALL_COLOR_MEDIUM;

        // Find the quadrant that one endpoint is in relative to the other - this way we know which 2 verts need to be
        // connected with straight lines.
        if (aY > bY) {
            quadrant = aX > bX ? 1 : 2;
        } else {
            quadrant = aX > bX ? 4 : 3;
        }

        // Calculate middle endpoint positions.
        var mediumPoint1A = {x: 0, y: 0};
        var mediumPoint1B = {x: 0, y: 0};
        var mediumPoint2A = {x: 0, y: 0};
        var mediumPoint2B = {x: 0, y: 0};

        switch (quadrant) {
            case 1:
            case 3:
                mediumPoint1A.x = aX + GRID_SPACING;
                mediumPoint1A.y = aY;
                mediumPoint1B.x = bX + GRID_SPACING;
                mediumPoint1B.y = bY;

                mediumPoint2A.x = aX;
                mediumPoint2A.y = aY + GRID_SPACING;
                mediumPoint2B.x = bX;
                mediumPoint2B.y = bY + GRID_SPACING;
                break;
            case 2:
            case 4:
                mediumPoint1A.x = aX;
                mediumPoint1A.y = aY;
                mediumPoint1B.x = bX;
                mediumPoint1B.y = bY;

                mediumPoint2A.x = aX + GRID_SPACING;
                mediumPoint2A.y = aY + GRID_SPACING;
                mediumPoint2B.x = bX + GRID_SPACING;
                mediumPoint2B.y = bY + GRID_SPACING;
                break;
            default:

        }

        // Create the path between the medium points.
        ctx.beginPath();

        // Move to the first medium point, draw to its pair, draw to the second's pair, draw to the second..
        ctx.moveTo(mediumPoint1A.x, mediumPoint1A.y);
        ctx.lineTo(mediumPoint1B.x, mediumPoint1B.y);
        ctx.lineTo(mediumPoint2B.x, mediumPoint2B.y);
        ctx.lineTo(mediumPoint2A.x, mediumPoint2A.y);

        ctx.fill();
        ctx.stroke();

        // Draw handles on each endpoint, and feedback fill if being dragged.
        ctx.strokeStyle = WALL_COLOR_DARK;

        if (this.endpointA.moving === true) {
            ctx.fillStyle = BACKGROUND_COLOR;
        } else {
            ctx.fillStyle = WALL_COLOR_MEDIUM;
        }
        ctx.fillRect(aX, aY, GRID_SPACING, GRID_SPACING);
        ctx.strokeRect(aX, aY, GRID_SPACING, GRID_SPACING);

        if (this.endpointB.moving === true) {
            ctx.fillStyle = BACKGROUND_COLOR;
        } else {
            ctx.fillStyle = WALL_COLOR_MEDIUM;
        }

        ctx.fillRect(bX, bY, GRID_SPACING, GRID_SPACING);
        ctx.strokeRect(bX, bY, GRID_SPACING, GRID_SPACING);
    };

    this.mousemoveHandler = function () {
        // Turn cursor if we're over a handle.
        if (mouseCol == this.endpointA.c && mouseRow == this.endpointA.r) {
            setCursorGrab();
        } else if (mouseCol == this.endpointB.c && mouseRow == this.endpointB.r) {
            setCursorGrab();
        }

        // If we're moving, keep the endpoint locations updated.
        if (this.endpointA.moving) {
            this.endpointA.r = mouseRow;
            this.endpointA.c = mouseCol;
        }
        if (this.endpointB.moving) {
            this.endpointB.r = mouseRow;
            this.endpointB.c = mouseCol;
        }
    };

    this.mousedownHandler = function (evt) {

        switch (evt.which) {
            case 1:
                // Check if there was a click in a handle - if so, set its moving property.
                if (mouseCol == this.endpointA.c && mouseRow == this.endpointA.r) {

                    this.endpointA.moving = true;
                } else if (mouseCol == this.endpointB.c && mouseRow == this.endpointB.r) {
                    this.endpointB.moving = true;
                }
                break;
            default:
                break;
        }
    };

    this.mouseupHandler = function (evt) {
        switch (evt.which) {
            case 1:
                // No matter where the click was, we are no longer moving.
                this.endpointA.moving = false;
                this.endpointB.moving = false;
                break;
            default:
                break;
        }
    };
}
Wall.prototype = Drawable;
Wall.prototype.constructor = Wall;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instance variables...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Position - where is the viewport looking in absolute coordinates?
var worldX = 0;
var worldY = 0;

// Mouse position - x and y in viewport terms.
var mouseViewX = 0;
var mouseViewY = 0;

// Mouse row/col - in absolute coordinates.
var mouseCol = 0;
var mouseRow = 0;

// Mouse button states - true for down, false for up.
var mouse1 = false;
var mouse2 = false;
var mouse3 = false;

// All of the objects in the world.
var worldObjects = [];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Canvas initialization...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var canvas = document.getElementById('floorplanCanvas');
var c = canvas.getContext('2d');

/**
 * Draw canvas elements.
 */
function redraw() {
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;

    c.fillStyle = BACKGROUND_COLOR;
    c.fillRect(0, 0, width, height);

    // Draw a border around the canvas to make it look nice...
    c.strokeStyle = BORDER_COLOR;
    c.lineWidth = BORDER_WIDTH;
    c.strokeRect(0, 0, width, height);

    // Draw gridlines
    c.strokeStyle = GRID_COLOR;
    c.lineWidth = GRID_WIDTH;

    for (var i = 0; i < height; i += GRID_SPACING) {
        c.strokeRect(0, i - (worldY % GRID_SPACING), width, GRID_WIDTH);
    }

    for (var i = 0; i < width; i += GRID_SPACING) {
        c.strokeRect(i - (worldX % GRID_SPACING), 0, GRID_WIDTH, height);
    }

    // Draw all of the drawable objects.
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].draw(c, worldX, worldY);
    }
}

/**
 * Resolve the mouse position into row/col.
 */
function resolveMouse() {
    return {
        row: Math.floor((mouseViewY + worldY) / GRID_SPACING),
        col: Math.floor((mouseViewX + worldX) / GRID_SPACING)
    };
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Event listeners and initialization...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Keep canvas coordinate system syncrhonized with CSS coordinates on window resize.
 */
(function () {
    var canvas = $('#floorplanCanvas');

    initCanvas();
    function initCanvas() {
        window.addEventListener('resize', resizeCanvasWithWindow, false);
        resizeCanvasWithWindow();
    }

    function resizeCanvasWithWindow() {
        var w = $('#floorplanCanvasContainer').width();
        var h = $('#floorplanCanvasContainer').height();
        canvas.attr("width", w);
        canvas.attr("height", h);
        redraw();
    }
})();

/**
 * Want to track where the mouse moved to update, also implements scroll-dragging.
 */
function mouseMoveListener(e) {
    var offset = $('#floorplanCanvas')[0].getBoundingClientRect();

    // Calculate current mouse position
    var newX = e.clientX - offset.left;
    var newY = e.clientY - offset.top;

    // Calculate deltas
    var dX = newX - mouseViewX;
    var dY = newY - mouseViewY;

    // Update global mouse position
    mouseViewX = newX;
    mouseViewY = newY;

    // Move viewport if scrolling
    if (mouse3) {
        worldX -= dX;
        worldY -= dY;
    }

    // Calculate and update column and row locations
    var cr = resolveMouse();
    mouseCol = cr.col;
    mouseRow = cr.row;

    // Reset the cursor before letting objects change it.
    setCursorDefault();

    // Propogate mouse movement events to all world objects...
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].mousemoveHandler();
    }

    redraw();
}

/**
 * Watch for mousedown events and set global flags.
 */
function mouseDownListener(e) {
    switch (e.which) {
        case 1:
            mouse1 = true;
            break;
        case 2:
            mouse2 = true;
            break;
        case 3:
            mouse3 = true;
            break;
    }

    // Propogate mousedown events to all world objects...
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].mousedownHandler(e);
    }
}

/**
 * Watch for mouseup events and set global flags.
 */
function mouseUpListener(e) {
    switch (e.which) {
        case 1:
            mouse1 = false;
            break;
        case 2:
            mouse2 = false;
            break;
        case 3:
            mouse3 = false;
            break;
    }

    // Propogate mouseup events to all world objects...
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].mouseupHandler(e);
    }
}

/**
 * Watch for mousewheel events.
 */
function mouseWheelListener(e) {
    var distance = e.originalEvent.wheelDelta || - e.originalEvent.detail;  // Stupid firefox, not only does it use a
                                                                            // different event, its deltas are also
                                                                            // negative... Woo browser interop!

    if (distance > 0) {
        zoomCanvas(1);
    } else {
        zoomCanvas(-1);
    }
}

// Register listeners
$('#floorplanCanvas').on('mousemove', mouseMoveListener);
$('#floorplanCanvas').on('mousedown', mouseDownListener);
$('#floorplanCanvas').on('mouseup', mouseUpListener);
$('#floorplanCanvas').on('mousewheel DOMMouseScroll', mouseWheelListener);
$('#floorplanCanvas').on('contextmenu', function () {
    return false; // Disable context menu on canvas for RMB dragging
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utility functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Sets the cursor to a grabby cursor on the canvas.
 */
function setCursorGrab() {
    $('#floorplanCanvas').css('cursor', 'move');
}

/**
 * Sets the cursor to the default cursor on the canvas.
 */
function setCursorDefault() {
    $('#floorplanCanvas').css('cursor', 'default');
}

/**
 * Zoom in or out.
 */
function zoomCanvas(direction) {
    // TODO: Perserve center point by finding the row/col identified in the center of the current zoom and repositioning
    if (direction > 0) {
        GRID_SPACING = Math.min(32, GRID_SPACING + 1);
    } else {
        GRID_SPACING = Math.max(10, GRID_SPACING - 1);
    }

    redraw();
}

//
// Debug functions
//

function addWall() {

    var w = new Wall(1, 1, 5, 1);

    worldObjects.push(w);
}<commit_msg>add consumption and default event handling<commit_after>// Configuration...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const BACKGROUND_COLOR = '#eaeaea';
const BORDER_COLOR = '#e4e4e4';
const HIGHLIGHT_COLOR = '#f1f1f1';
const BORDER_WIDTH = 4;

const WALL_COLOR_DARK = '#333333';
const WALL_COLOR_MEDIUM = '#555555';
const WALL_COLOR_LIGHT = '#777777';
const WALL_THICKNESS = 3;

const GRID_COLOR = '#';
const GRID_WIDTH = 2;
var GRID_SPACING = 24; // Can adjust to zoom in/out

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Object definitions...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Something in the world is drawable, so everything should inherit from this.
 */
function Drawable() {

}

Drawable.prototype = {
    draw: function (ctx, offX, offY) {
        alert("Inherit the draw function...");
    },
    mousemoveHandler: function () {
        alert("Inherit the mousemove function...");
    },
    mousedownHandler: function (evt) {
        alert("Inherit the mousedown function...");
        return true;
    },
    mouseupHandler: function (evt) {
        alert("Inherit the mouseup function...")
        return true;
    }
};

/**
 * A room is a container/"layer" for walls and other household objects.
 */
function Room() {
    this.walls = [];
}
Room.prototype = Drawable;
Room.prototype.constructor = Room;

/**
 * A wall has a start and endpoint, and fill in-between.
 */
function Wall(colA, rowA, colB, rowB) {
    this.endpointA = {c: colA, r: rowA, moving: false};
    this.endpointB = {c: colB, r: rowB, moving: false};
    this.draw = function (ctx, offX, offY) {
        // Upper-left corners of each endpoint.
        var aX = this.endpointA.c * GRID_SPACING - offX;
        var aY = this.endpointA.r * GRID_SPACING - offY;
        var bX = this.endpointB.c * GRID_SPACING - offX;
        var bY = this.endpointB.r * GRID_SPACING - offY;

        ctx.lineWidth = WALL_THICKNESS;

        // Draw a wall connecting the two endpoints. We want this wall to be of bounding thickness, so let's think about
        // this. The innermost and outermost pairs of verts won't have a line connecting them - we just want lines
        // between the middle-distance pairs of verts, to form the wall.
        ctx.fillStyle = WALL_COLOR_LIGHT;
        ctx.strokeStyle = WALL_COLOR_MEDIUM;

        // Find the quadrant that one endpoint is in relative to the other - this way we know which 2 verts need to be
        // connected with straight lines.
        if (aY > bY) {
            quadrant = aX > bX ? 1 : 2;
        } else {
            quadrant = aX > bX ? 4 : 3;
        }

        // Calculate middle endpoint positions.
        var mediumPoint1A = {x: 0, y: 0};
        var mediumPoint1B = {x: 0, y: 0};
        var mediumPoint2A = {x: 0, y: 0};
        var mediumPoint2B = {x: 0, y: 0};

        switch (quadrant) {
            case 1:
            case 3:
                mediumPoint1A.x = aX + GRID_SPACING;
                mediumPoint1A.y = aY;
                mediumPoint1B.x = bX + GRID_SPACING;
                mediumPoint1B.y = bY;

                mediumPoint2A.x = aX;
                mediumPoint2A.y = aY + GRID_SPACING;
                mediumPoint2B.x = bX;
                mediumPoint2B.y = bY + GRID_SPACING;
                break;
            case 2:
            case 4:
                mediumPoint1A.x = aX;
                mediumPoint1A.y = aY;
                mediumPoint1B.x = bX;
                mediumPoint1B.y = bY;

                mediumPoint2A.x = aX + GRID_SPACING;
                mediumPoint2A.y = aY + GRID_SPACING;
                mediumPoint2B.x = bX + GRID_SPACING;
                mediumPoint2B.y = bY + GRID_SPACING;
                break;
            default:

        }

        // Create the path between the medium points.
        ctx.beginPath();

        // Move to the first medium point, draw to its pair, draw to the second's pair, draw to the second..
        ctx.moveTo(mediumPoint1A.x, mediumPoint1A.y);
        ctx.lineTo(mediumPoint1B.x, mediumPoint1B.y);
        ctx.lineTo(mediumPoint2B.x, mediumPoint2B.y);
        ctx.lineTo(mediumPoint2A.x, mediumPoint2A.y);

        ctx.fill();
        ctx.stroke();

        // Draw handles on each endpoint, and feedback fill if being dragged.
        ctx.strokeStyle = WALL_COLOR_DARK;

        if (this.endpointA.moving === true) {
            ctx.fillStyle = BACKGROUND_COLOR;
        } else {
            ctx.fillStyle = WALL_COLOR_MEDIUM;
        }
        ctx.fillRect(aX, aY, GRID_SPACING, GRID_SPACING);
        ctx.strokeRect(aX, aY, GRID_SPACING, GRID_SPACING);

        if (this.endpointB.moving === true) {
            ctx.fillStyle = BACKGROUND_COLOR;
        } else {
            ctx.fillStyle = WALL_COLOR_MEDIUM;
        }

        ctx.fillRect(bX, bY, GRID_SPACING, GRID_SPACING);
        ctx.strokeRect(bX, bY, GRID_SPACING, GRID_SPACING);
    };

    this.mousemoveHandler = function () {
        // Turn cursor if we're over a handle.
        if (mouseCol == this.endpointA.c && mouseRow == this.endpointA.r) {
            setCursorGrab();
        } else if (mouseCol == this.endpointB.c && mouseRow == this.endpointB.r) {
            setCursorGrab();
        }

        // If we're moving, keep the endpoint locations updated.
        if (this.endpointA.moving) {
            this.endpointA.r = mouseRow;
            this.endpointA.c = mouseCol;
        }
        if (this.endpointB.moving) {
            this.endpointB.r = mouseRow;
            this.endpointB.c = mouseCol;
        }
    };

    this.mousedownHandler = function (evt) {

        switch (evt.which) {
            case 1:
                // Check if there was a click in a handle - if so, set its moving property.
                if (mouseCol == this.endpointA.c && mouseRow == this.endpointA.r) {

                    this.endpointA.moving = true;
                    return true;
                } else if (mouseCol == this.endpointB.c && mouseRow == this.endpointB.r) {
                    this.endpointB.moving = true;
                    return true;
                }
                break;
            default:
                break;
        }
        return false;
    };

    this.mouseupHandler = function (evt) {
        switch (evt.which) {
            case 1:
                // No matter where the click was, we are no longer moving.
                this.endpointA.moving = false;
                this.endpointB.moving = false;
                return true;
                break;
            default:
                break;
        }
        return false;
    };
}
Wall.prototype = Drawable;
Wall.prototype.constructor = Wall;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instance variables...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Position - where is the viewport looking in absolute coordinates?
var worldX = 0;
var worldY = 0;

// Mouse position - x and y in viewport terms.
var mouseViewX = 0;
var mouseViewY = 0;

// Mouse row/col - in absolute coordinates.
var mouseCol = 0;
var mouseRow = 0;

// Mouse button states - true for down, false for up.
var mouse1 = false;
var mouse2 = false;
var mouse3 = false;

// All of the objects in the world.
var worldObjects = [];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Canvas initialization...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var canvas = document.getElementById('floorplanCanvas');
var c = canvas.getContext('2d');

/**
 * Draw canvas elements.
 */
function redraw() {
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;

    c.fillStyle = BACKGROUND_COLOR;
    c.fillRect(0, 0, width, height);

    // Draw a border around the canvas to make it look nice...
    c.strokeStyle = BORDER_COLOR;
    c.lineWidth = BORDER_WIDTH;
    c.strokeRect(0, 0, width, height);

    // Draw gridlines
    c.strokeStyle = GRID_COLOR;
    c.lineWidth = GRID_WIDTH;

    for (var i = 0; i < height; i += GRID_SPACING) {
        c.strokeRect(0, i - (worldY % GRID_SPACING), width, GRID_WIDTH);
    }

    for (var i = 0; i < width; i += GRID_SPACING) {
        c.strokeRect(i - (worldX % GRID_SPACING), 0, GRID_WIDTH, height);
    }

    // Draw all of the drawable objects.
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].draw(c, worldX, worldY);
    }
}

/**
 * Resolve the mouse position into row/col.
 */
function resolveMouse() {
    return {
        row: Math.floor((mouseViewY + worldY) / GRID_SPACING),
        col: Math.floor((mouseViewX + worldX) / GRID_SPACING)
    };
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Event listeners and initialization...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Keep canvas coordinate system syncrhonized with CSS coordinates on window resize.
 */
(function () {
    var canvas = $('#floorplanCanvas');

    initCanvas();
    function initCanvas() {
        window.addEventListener('resize', resizeCanvasWithWindow, false);
        resizeCanvasWithWindow();
    }

    function resizeCanvasWithWindow() {
        var w = $('#floorplanCanvasContainer').width();
        var h = $('#floorplanCanvasContainer').height();
        canvas.attr("width", w);
        canvas.attr("height", h);
        redraw();
    }
})();

/**
 * Want to track where the mouse moved to update, also implements scroll-dragging.
 */
function mouseMoveListener(e) {
    var offset = $('#floorplanCanvas')[0].getBoundingClientRect();

    // Calculate current mouse position
    var newX = e.clientX - offset.left;
    var newY = e.clientY - offset.top;

    // Calculate deltas
    var dX = newX - mouseViewX;
    var dY = newY - mouseViewY;

    // Update global mouse position
    mouseViewX = newX;
    mouseViewY = newY;

    // Move viewport if scrolling
    if (mouse3) {
        worldX -= dX;
        worldY -= dY;
    }

    // Calculate and update column and row locations
    var cr = resolveMouse();
    mouseCol = cr.col;
    mouseRow = cr.row;

    // Reset the cursor before letting objects change it.
    setCursorDefault();

    // Propogate mouse movement events to all world objects...
    for (var i = 0; i < worldObjects.length; i++) {
        worldObjects[i].mousemoveHandler();
    }

    redraw();
}

/**
 * Watch for mousedown events and set global flags.
 */
function mouseDownListener(e) {
    switch (e.which) {
        case 1:
            mouse1 = true;
            break;
        case 2:
            mouse2 = true;
            break;
        case 3:
            mouse3 = true;
            break;
    }

    // Propogate mousedown events to all world objects...
    var consumed = false;
    for (var i = 0; i < worldObjects.length; i++) {
        consumed |= worldObjects[i].mousedownHandler(e);
    }

    if (consumed !== true) {
        // Default mouse-down actions here.

    }
}

/**
 * Watch for mouseup events and set global flags.
 */
function mouseUpListener(e) {
    switch (e.which) {
        case 1:
            mouse1 = false;
            break;
        case 2:
            mouse2 = false;
            break;
        case 3:
            mouse3 = false;
            break;
    }

    // Propogate mouseup events to all world objects...
    var consumed = false;
    for (var i = 0; i < worldObjects.length; i++) {
        consumed |= worldObjects[i].mouseupHandler(e);
    }

    if (consumed !== true) {
        // Default mouse-up actions here.

        // The default action is to create a new wall.
    }
}

/**
 * Watch for mousewheel events.
 */
function mouseWheelListener(e) {
    var distance = e.originalEvent.wheelDelta || - e.originalEvent.detail;  // Stupid firefox, not only does it use a
                                                                            // different event, its deltas are also
                                                                            // negative... Woo browser interop!

    if (distance > 0) {
        zoomCanvas(1);
    } else {
        zoomCanvas(-1);
    }
}

// Register listeners
$('#floorplanCanvas').on('mousemove', mouseMoveListener);
$('#floorplanCanvas').on('mousedown', mouseDownListener);
$('#floorplanCanvas').on('mouseup', mouseUpListener);
$('#floorplanCanvas').on('mousewheel DOMMouseScroll', mouseWheelListener);
$('#floorplanCanvas').on('contextmenu', function () {
    return false; // Disable context menu on canvas for RMB dragging
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utility functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Sets the cursor to a grabby cursor on the canvas.
 */
function setCursorGrab() {
    $('#floorplanCanvas').css('cursor', 'move');
}

/**
 * Sets the cursor to the default cursor on the canvas.
 */
function setCursorDefault() {
    $('#floorplanCanvas').css('cursor', 'default');
}

/**
 * Zoom in or out.
 */
function zoomCanvas(direction) {
    // TODO: Perserve center point by finding the row/col identified in the center of the current zoom and repositioning
    if (direction > 0) {
        GRID_SPACING = Math.min(32, GRID_SPACING + 1);
    } else {
        GRID_SPACING = Math.max(10, GRID_SPACING - 1);
    }

    redraw();
}

//
// Debug functions
//

function addWall() {

    var w = new Wall(1, 1, 5, 1);

    worldObjects.push(w);
}<|endoftext|>


<commit_before>
import mongoose from 'mongoose';

let SubItemAnnexSchema = new mongoose.Schema({
	idItemAnnex: {type: String, require: true},
	itemAnnex: { type: mongoose.Schema.ObjectId, ref: "ItemAnnex"},
	numberSubItem: {type: Number},
	name: {type: String},
	planAlternative: {type: String},
	valueSubItem: {type: Number},
	tasa: {type: Number},
	calcFloat: {type: Number},
	primaNeta: {type: Number},
	detailsSubItem: {type: String},
	observationsSubItem: {type: String},
	exclusionDate: {type: Date},
	inclusionDate: {type: Date},
	modificationDate: {type: Date},
	dateCreate: {type: Date, require: true},
	userCreate: {type: String, require: true},
	dateUpdate: {type: Date, require: true},
	userUpdate: {type: String, require: true}
});

export default mongoose.model('SubItemAnnex', SubItemAnnexSchema)
<commit_msg>changes<commit_after>
import mongoose from 'mongoose';

let SubItemAnnexSchema = new mongoose.Schema({
	idItemAnnex: {type: String, require: true},
	itemAnnex: { type: mongoose.Schema.ObjectId, ref: "ItemAnnex"},
	numberSubItem: {type: Number},
	name: {type: String},
	planAlternative: {type: String},
	valueSubItem: {type: Number},
	tasa: {type: Number},
	calcFloat: {type: Number},
	primaNeta: {type: Number},
	detailsSubItem: {type: String},
	observationsSubItem: {type: String},
	exclusionDate: {type: Date},
	inclusionDate: {type: Date},
	modificationDate: {type: Date},
	dateCreate: {type: Date, require: true},
	userCreate: {type: String, require: true},
	dateUpdate: {type: Date, require: true},
	userUpdate: {type: String, require: true},
	idClient :{type: String},
	recipientClient:{type: Object},
	idPlanAlternative:{type: String},
	recipientPlanAlternative:{type:Object}
});

export default mongoose.model('SubItemAnnex', SubItemAnnexSchema)
<|endoftext|>


<commit_before><commit_msg>Limit number of characters in Idea description<commit_after><|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

(function() {
  "use strict";

  function xhr(type, url, contentType, callback, data, csrf) {
    var req;

    function stateChange() {
      try {
        if (req.readyState === 4) {
          callback(req.responseText, req.status);
        }
      }catch(e) {}
    }

    function getRequest() {
      // From // http://blogs.msdn.com/b/ie/archive/2011/08/31/browsing-without-plug-ins.aspx
      // Best Practice: Use Native XHR, if available
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else if (window.ActiveXObject) {
        return new window.ActiveXObject('Microsoft.XMLHTTP');
      }
      return false;
    }

    req = getRequest();
    if(req) {
      req.onreadystatechange = stateChange;

      req.open(type, url, true);
      req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      if (csrf) {
        req.setRequestHeader('X-CSRF-Token', csrf);
      }

      if (data) {
        req.setRequestHeader('Content-type', contentType);
      }

      req.setRequestHeader('Accept', 'application/json;text/plain');

      req.send(data || null);
    }
  }

  function request(options) {
    xhr(options.method, options.url, "application/json",
        function(responseText, status) {
        if (status >= 200 && status < 300) {
          var respData = responseText;
          try {
            respData = JSON.parse(respData);
          } catch (ohWell) {}

          options.success(respData);
        } else {
          options.error({ status: status, responseText: responseText });
        }
      }, JSON.stringify(options.data), options.csrf);
  }

  function GET(options) {
    options.method = 'GET';
    request(options);
  }

  function POST(options) {
    options.method = 'POST';
    request(options);
  }

  function withProvenEmail(email, callback) {
    GET({
      url: "/session",
      success: function(r) {
        if (r.proven === email) {
          callback(r.csrf);
        } else {
          navigator.id.raiseProvisioningFailure();
        }
      },
      error: function() {
        navigator.id.raiseProvisioningFailure();
      }
    });
  }

  navigator.id.beginProvisioning(function (email, duration) {
    withProvenEmail(email, function(csrf) {
      navigator.id.genKeyPair(function(pubkey) {
        POST({
          url: '/provision/certify',
          data: {
            pubkey: pubkey,
            duration: duration,
            email: email
          },
          csrf: csrf,
          success: function(r) {
            // all done!  woo!
            navigator.id.registerCertificate(r.cert);
          },
          error: function() {
            navigator.id.raiseProvisioningFailure();
          }
        });
      });
    });
  });

})();

<commit_msg>fix JSHint errors<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

(function () {
  "use strict";

  function xhr(type, url, contentType, callback, data, csrf) {
    var req;

    function stateChange() {
      try {
        if (req.readyState === 4) {
          callback(req.responseText, req.status);
        }
      } catch (e) {}
    }

    function getRequest() {
      // From http://blogs.msdn.com/b/ie/archive/2011/08/31
      //                  /browsing-without-plug-ins.aspx
      // Best Practice: Use Native XHR, if available
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else if (window.ActiveXObject) {
        return new window.ActiveXObject('Microsoft.XMLHTTP');
      }
      return false;
    }

    req = getRequest();
    if (req) {
      req.onreadystatechange = stateChange;

      req.open(type, url, true);
      req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      if (csrf) {
        req.setRequestHeader('X-CSRF-Token', csrf);
      }

      if (data) {
        req.setRequestHeader('Content-type', contentType);
      }

      req.setRequestHeader('Accept', 'application/json;text/plain');

      req.send(data || null);
    }
  }

  function request(options) {
    xhr(options.method, options.url, "application/json",
        function (responseText, status) {
        if (status >= 200 && status < 300) {
          var respData = responseText;
          try {
            respData = JSON.parse(respData);
          } catch (ohWell) {}

          options.success(respData);
        } else {
          options.error({ status: status, responseText: responseText });
        }
      }, JSON.stringify(options.data), options.csrf);
  }

  function GET(options) {
    options.method = 'GET';
    request(options);
  }

  function POST(options) {
    options.method = 'POST';
    request(options);
  }

  function withProvenEmail(email, callback) {
    GET({
      url: "/session",
      success: function (r) {
        if (r.proven === email) {
          callback(r.csrf);
        } else {
          navigator.id.raiseProvisioningFailure();
        }
      },
      error: function () {
        navigator.id.raiseProvisioningFailure();
      }
    });
  }

  navigator.id.beginProvisioning(function (email, duration) {
    withProvenEmail(email, function (csrf) {
      navigator.id.genKeyPair(function (pubkey) {
        POST({
          url: '/provision/certify',
          data: {
            pubkey: pubkey,
            duration: duration,
            email: email
          },
          csrf: csrf,
          success: function (r) {
            // all done!  woo!
            navigator.id.registerCertificate(r.cert);
          },
          error: function () {
            navigator.id.raiseProvisioningFailure();
          }
        });
      });
    });
  });

})();

<|endoftext|>


<commit_before>

IONUX.Models.EditableResource = Backbone.Model.extend({
  idAttribute: '_id',
  schema: function(){
    return this.make_schema();
  },
  initialize: function(){},
  url: function(){
    return window.location.pathname.replace(/edit$/,'');
  },
  make_schema: function(){
    var self = this;
    var schema = {};
    _.each(this.attributes, function(value, key){
      if (!_.isObject(value) && !(key=='ts_updated' || key=='ts_created')){
        console.log(key, value, typeof value);
        switch(typeof(value)){
          case 'boolean':
            schema[key] = 'Checkbox';
            break;
          case 'number':
            schema[key] = 'Number';
            break;
          default:
            schema[key] = 'Text'
        };
      };
    });
    return schema;
  },
});

IONUX.Models.Search = Backbone.Model.extend();

IONUX.Models.Search = Backbone.Model.extend({
    url: function(){
        return "/search/?query="+this.get("search_query");
    },
    parse: function(resp){
        make_iso_timestamps(resp);
        return resp.data;
    }
});

// For use with collections of Resource Types, i.e. InstrumentDevice, PlatformDevice, etc.
IONUX.Models.Session = Backbone.Model.extend({
    defaults: {
        actor_id: null,
        user_id: null,
        name: "Guest",
        is_logged_in: false,
        is_registered: null,
        version: {},
        roles: [],
        ui_mode: 'PRODUCTION',
        is_polling: false,
        ui_theme_dark: false,
        valid_until: null
    },
    url: '/session/',
    initialize: function() {
      _.bindAll(this);
    },
    parse: function(resp){
        return resp.data;
    },
    is_logged_in: function(){
      return this.get('is_logged_in');
    },
    is_registered: function() {
      return this.get('is_registered');
    },
    is_resource_owner: function(){
      return _.findWhere(MODEL_DATA.owners, {_id: this.get('user_id')}) ? true : false;
    },
    is_valid: function(){
      return new Date(this.get('valid_until')) >= new Date()
    },
    set_polling: function() {
      if (this.get('is_logged_in')) {
        this.set('is_polling', true);
        setTimeout(this.poll, 60000);
      };
    },
    poll: function() {
      // If their session has expired (i.e. their certificate's is_valid date has passed),
      // let the user know and give the option to login.  Do not continue to poll the session.
      if (this.is_logged_in() && !this.is_valid()) {
        IONUX.ROUTER.signin_from_expired_session();
        return;
      }
      var self = this;
      var existing_roles = _.clone(this.get('roles'));
      if (this.get('is_polling')) {
        this.fetch({
          global: false,
          success: function(resp) {
            self.set_polling();
            if (!_.isEqual(existing_roles, resp.get('roles'))) {
              self.check_roles(existing_roles);
            };
          },
          error: function(resp) {
            self.set('is_polling', false);
          }
        });
      };
    },
    check_roles: function(existing_roles) {
      var roles = this.get('roles');
      var new_roles = {};
      _.each(roles, function(v,k) {
        if (!_.has(existing_roles, k)) {
          new_roles[k] = v;
        } else {
          var added_roles = _.difference(roles[k], existing_roles[k]);
          if (added_roles.length) new_roles[k] = added_roles;
        };
      });
      if (!_.isEmpty(new_roles)) new IONUX.Views.NewRoles({new_roles: new_roles}).render().el;
    },
});

IONUX.Models.Resource = Backbone.Model.extend({
  initialize: function(options) {
    this.resource_id = options.resource_id
  },
  url: function() {
    return '/resource/read/'+this.resource_id+'/';
  },
  parse: function(resp) {
    return resp.data;
  },
});

IONUX.Models.EventType = Backbone.Model.extend({
    url: '/event_types/',
    parse: function(resp){
        return resp.data;
    },
});

IONUX.Models.Layout = Backbone.Model.extend({
  url: '/layout',
  parse: function(resp){
    return resp.data;
  },
});

// Timestamp conversion methods to call when parsing response.
// Maybe put these into IONUX.Helpers namespace?
var epoch_to_iso = function(epoch_time){
    try {
      return new Date(parseInt(epoch_time)).toISOString().replace(/T/, ' ').replace(/.\d{3}Z$/, 'Z');
    } catch (err) {
      return epoch_time;
    };
};

var make_iso_timestamps = function(resp) {
  if(resp != null && 'type_' in resp) {
    if('ts_created' in resp) {
      resp['ts_created'] = epoch_to_iso(resp['ts_created']);
    }
    if('ts_updated' in resp) {
      resp['ts_updated'] = epoch_to_iso(resp['ts_updated']);
    }
  }

  _.each(resp, function(val, key) {
      if (typeof val == 'object') {
        make_iso_timestamps(val);
      };
  });
  return;
};

IONUX.Models.ResourceExtension = Backbone.Model.extend({
    url: function(){
        return '/'+this.get('resource_type')+'/extension/'+this.get('resource_id')+'/';
    },
    parse: function(resp){
        make_iso_timestamps(resp);
        return resp.data;
    }
});

IONUX.Models.Observatory = Backbone.Model.extend({
  url: "/observatories/",
  idAttribute: "_id",
});

IONUX.Models.ObservatoryFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/observatories/"+this.get("observatory_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.UserRequest = Backbone.Model.extend({});

IONUX.Models.Platform = Backbone.Model.extend({
  url: "/platforms/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.PlatformFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/platforms/"+this.get("platform_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.PlatformModel = Backbone.Model.extend({
  url: '/platform_models/',
  idAttribute: '_id',

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.PlatformModelFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/platform_models/'+this.get('platform_model_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.Instrument = Backbone.Model.extend({
  url: "/instruments/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

// IONUX.Models.Instrument = Backbone.Model.extend({
//   url: "/instrument_models/",
//   idAttribute: "_id"
// });

IONUX.Models.InstrumentFacepageModelLegacy = Backbone.Model.extend({
  url: function(){
    return "/instruments/"+this.get("instrument_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.InstrumentModel = Backbone.Model.extend({
  url: '/instrument_models/',
  idAttribute: '_id',

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.InstrumentModelFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/instrument_models/'+this.get('instrument_model_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.InstrumentAgent = Backbone.Model.extend({
  url: '/instrument_agents/',
  idAttribute: '_id',

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.InstrumentAgentFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/instrument_agents/'+this.get('instrument_agent_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.DataProcessDefinition = Backbone.Model.extend({
  url: "/data_process_definitions/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.DataProcessDefinitionFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/data_process_definitions/"+this.get("data_process_definition_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.DataProduct = Backbone.Model.extend({
  url: "/data_products/",
  idAttribute: "_id",

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.DataProductFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/data_products/"+this.get("data_product_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.FrameOfReference = Backbone.Model.extend({
  url: "/frame_of_references/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.FrameOfReferenceFacepage = Backbone.Model.extend({
  url: function() {
    return "/frame_of_references/"+this.get("frame_of_reference_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.UserRegistrationModel = IONUX.Models.EditableResource.extend({
  url: "/userprofile/",
  idAttribute: "_id",

  schema: {
    'contact': {
      type: 'Object',
      title: false,
      subSchema: {
        individual_names_given: { type: 'Text', title: 'Given Name(s)' },
        individual_name_family: { type: 'Text', title: 'Family Name' },
        organization_name:      { type: 'Text', title: 'Organization Name' },
        position_name:          { type: 'Text', title: 'Position' },
        email:                  { type: 'Text', title: 'Email', dataType: 'email', validators: ['required', 'email']},
        street_address:         { type: 'Text', title: 'Street Address' },
        city:                   { type: 'Text', title: 'City' },
        postal_code:            { type: 'Text', title: 'Postal Code' },
        country:                { type: 'Text', title: 'Country' },
        url:                    { type: 'Text', title: 'Contact URL' },
        phones:                 { type: 'List', itemType: 'Phone' },
      },
    },

    // 'variables': {
    //   type: 'Object',
    //   title: false,
    //   subSchema: {
    //     notification_contact:   { type: 'Radio',    title: 'Notification Method', options: ['Email', 'SMS'] },
    //     sms_email:              { type: 'Checkbox', title: "Use email->SMS" },
    //     ooi_system_change:      { type: 'Checkbox', title: "OOI system change information" },
    //     ooi_project_updates:    { type: 'Checkbox', title: "OOI project updates" },
    //     ocean_leadership_news:  { type: 'Checkbox', title: "Ocean Leadership News" },
    //     ux_improvement_program: { type: 'Checkbox', title: "Participate in OOI User Experience Improvement Program" },
    //   }
    // },

  },

  attr_convert: {
    'name': {
      parse: function(v) {
        /**
         * Input: value of name field.
         * Ouptut: the same. Not presented on form.
         */
        return v;
      },
      serialize: function(v) {
        /**
         * Input: whatever value we parsed initially. May be empty.
         * Output: the concatentation of contact.individual_names_given + individual_name_family
         */
        var contact = this.get('contact');
        return contact.individual_names_given + " " + contact.individual_name_family;
      },
    },
    'variables': {
      parse: function(v) {
        /**
         * Input: array of { name: <key>, value: <value> } literal objects
         * Output: object with keys -> values
         */
        return _.object(_.map(v, function(vv) { return [vv.name, vv.value]; } ));
      },
      serialize: function(v) {
        /**
         * Input: object with keys -> values
         * Output: array of { name: <key>, value: <value> } literal objects
         */
        return _.map(v, function(vv, kk) { return { name: kk, value: vv }; });
      },
    },
  },

  attr_convert_serialize: function(attrs) {
    /**
     * Call this from your toJSON.
     */
    attrs = _.clone(attrs);
    var self = this;
    _.each(this.attr_convert, function(v, k) {
      if (_.has(attrs, k)) {
        attrs[k] = v.serialize.call(self, attrs[k]);
      }
    });

    return attrs;
  },

  attr_convert_parse: function(data) {
    /**
     * Call this from your parse method.
     */
    data = _.clone(data);
    var self = this;
    _.each(this.attr_convert, function(v, k) {
      if (_.has(data, k)) {
        data[k] = v.parse.call(self, data[k]);
      }
    });

    return data;
  },

  toJSON: function() {
    var o = IONUX.Models.EditableResource.prototype.toJSON.call(this);
    o = this.attr_convert_serialize(o);

    o['contact']['type_'] = 'ContactInformation';

    return o;
  },

  parse: function(resp) {
    var data = resp.data;
    if (data != null)
      data = this.attr_convert_parse(data);
    return data;
  },

  merge: function(attribute, value) {
    /**
     * Instead of overwriting full objects, this method merges the passed in value
     * on top of the current value.
     */

    var curval = _.clone(this.get(attribute) || {});
    _.extend(curval, value);
    this.set(attribute, curval);

    return curval;
  },
});

IONUX.Models.User = Backbone.Model.extend({
  url: "/users/",
  idAttribute: "_id"
});

IONUX.Models.UserFacepageModel = Backbone.Model.extend({
  url: function() {
    return "/users/"+this.get("user_id")+"/";
  },
  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.ResourceType = Backbone.Model.extend({})
<commit_msg>OOIION-865 valid_until was coming back as a string so it was making a Date() comparison upset.  Fixed it.<commit_after>

IONUX.Models.EditableResource = Backbone.Model.extend({
  idAttribute: '_id',
  schema: function(){
    return this.make_schema();
  },
  initialize: function(){},
  url: function(){
    return window.location.pathname.replace(/edit$/,'');
  },
  make_schema: function(){
    var self = this;
    var schema = {};
    _.each(this.attributes, function(value, key){
      if (!_.isObject(value) && !(key=='ts_updated' || key=='ts_created')){
        console.log(key, value, typeof value);
        switch(typeof(value)){
          case 'boolean':
            schema[key] = 'Checkbox';
            break;
          case 'number':
            schema[key] = 'Number';
            break;
          default:
            schema[key] = 'Text'
        };
      };
    });
    return schema;
  },
});

IONUX.Models.Search = Backbone.Model.extend();

IONUX.Models.Search = Backbone.Model.extend({
    url: function(){
        return "/search/?query="+this.get("search_query");
    },
    parse: function(resp){
        make_iso_timestamps(resp);
        return resp.data;
    }
});

// For use with collections of Resource Types, i.e. InstrumentDevice, PlatformDevice, etc.
IONUX.Models.Session = Backbone.Model.extend({
    defaults: {
        actor_id: null,
        user_id: null,
        name: "Guest",
        is_logged_in: false,
        is_registered: null,
        version: {},
        roles: [],
        ui_mode: 'PRODUCTION',
        is_polling: false,
        ui_theme_dark: false,
        valid_until: null
    },
    url: '/session/',
    initialize: function() {
      _.bindAll(this);
    },
    parse: function(resp){
        return resp.data;
    },
    is_logged_in: function(){
      return this.get('is_logged_in');
    },
    is_registered: function() {
      return this.get('is_registered');
    },
    is_resource_owner: function(){
      return _.findWhere(MODEL_DATA.owners, {_id: this.get('user_id')}) ? true : false;
    },
    is_valid: function(){
      return new Date(this.get('valid_until') * 1) >= new Date()
    },
    set_polling: function() {
      if (this.get('is_logged_in')) {
        this.set('is_polling', true);
        setTimeout(this.poll, 60000);
      };
    },
    poll: function() {
      // If their session has expired (i.e. their certificate's is_valid date has passed),
      // let the user know and give the option to login.  Do not continue to poll the session.
      if (this.is_logged_in() && !this.is_valid()) {
        IONUX.ROUTER.signin_from_expired_session();
        return;
      }
      var self = this;
      var existing_roles = _.clone(this.get('roles'));
      if (this.get('is_polling')) {
        this.fetch({
          global: false,
          success: function(resp) {
            self.set_polling();
            if (!_.isEqual(existing_roles, resp.get('roles'))) {
              self.check_roles(existing_roles);
            };
          },
          error: function(resp) {
            self.set('is_polling', false);
          }
        });
      };
    },
    check_roles: function(existing_roles) {
      var roles = this.get('roles');
      var new_roles = {};
      _.each(roles, function(v,k) {
        if (!_.has(existing_roles, k)) {
          new_roles[k] = v;
        } else {
          var added_roles = _.difference(roles[k], existing_roles[k]);
          if (added_roles.length) new_roles[k] = added_roles;
        };
      });
      if (!_.isEmpty(new_roles)) new IONUX.Views.NewRoles({new_roles: new_roles}).render().el;
    },
});

IONUX.Models.Resource = Backbone.Model.extend({
  initialize: function(options) {
    this.resource_id = options.resource_id
  },
  url: function() {
    return '/resource/read/'+this.resource_id+'/';
  },
  parse: function(resp) {
    return resp.data;
  },
});

IONUX.Models.EventType = Backbone.Model.extend({
    url: '/event_types/',
    parse: function(resp){
        return resp.data;
    },
});

IONUX.Models.Layout = Backbone.Model.extend({
  url: '/layout',
  parse: function(resp){
    return resp.data;
  },
});

// Timestamp conversion methods to call when parsing response.
// Maybe put these into IONUX.Helpers namespace?
var epoch_to_iso = function(epoch_time){
    try {
      return new Date(parseInt(epoch_time)).toISOString().replace(/T/, ' ').replace(/.\d{3}Z$/, 'Z');
    } catch (err) {
      return epoch_time;
    };
};

var make_iso_timestamps = function(resp) {
  if(resp != null && 'type_' in resp) {
    if('ts_created' in resp) {
      resp['ts_created'] = epoch_to_iso(resp['ts_created']);
    }
    if('ts_updated' in resp) {
      resp['ts_updated'] = epoch_to_iso(resp['ts_updated']);
    }
  }

  _.each(resp, function(val, key) {
      if (typeof val == 'object') {
        make_iso_timestamps(val);
      };
  });
  return;
};

IONUX.Models.ResourceExtension = Backbone.Model.extend({
    url: function(){
        return '/'+this.get('resource_type')+'/extension/'+this.get('resource_id')+'/';
    },
    parse: function(resp){
        make_iso_timestamps(resp);
        return resp.data;
    }
});

IONUX.Models.Observatory = Backbone.Model.extend({
  url: "/observatories/",
  idAttribute: "_id",
});

IONUX.Models.ObservatoryFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/observatories/"+this.get("observatory_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.UserRequest = Backbone.Model.extend({});

IONUX.Models.Platform = Backbone.Model.extend({
  url: "/platforms/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.PlatformFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/platforms/"+this.get("platform_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.PlatformModel = Backbone.Model.extend({
  url: '/platform_models/',
  idAttribute: '_id',

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.PlatformModelFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/platform_models/'+this.get('platform_model_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.Instrument = Backbone.Model.extend({
  url: "/instruments/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

// IONUX.Models.Instrument = Backbone.Model.extend({
//   url: "/instrument_models/",
//   idAttribute: "_id"
// });

IONUX.Models.InstrumentFacepageModelLegacy = Backbone.Model.extend({
  url: function(){
    return "/instruments/"+this.get("instrument_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.InstrumentModel = Backbone.Model.extend({
  url: '/instrument_models/',
  idAttribute: '_id',

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.InstrumentModelFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/instrument_models/'+this.get('instrument_model_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.InstrumentAgent = Backbone.Model.extend({
  url: '/instrument_agents/',
  idAttribute: '_id',

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.InstrumentAgentFacepageModel = Backbone.Model.extend({
  url: function() {
    return '/instrument_agents/'+this.get('instrument_agent_id')+'/';
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.DataProcessDefinition = Backbone.Model.extend({
  url: "/data_process_definitions/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.DataProcessDefinitionFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/data_process_definitions/"+this.get("data_process_definition_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.DataProduct = Backbone.Model.extend({
  url: "/data_products/",
  idAttribute: "_id",

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp;
  }
});

IONUX.Models.DataProductFacepageModel = Backbone.Model.extend({
  url: function(){
    return "/data_products/"+this.get("data_product_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.FrameOfReference = Backbone.Model.extend({
  url: "/frame_of_references/",
  idAttribute: "_id",

  parse: function(resp){
    make_iso_timestamps(resp);
    return resp;
  },
});

IONUX.Models.FrameOfReferenceFacepage = Backbone.Model.extend({
  url: function() {
    return "/frame_of_references/"+this.get("frame_of_reference_id")+"/";
  },

  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.UserRegistrationModel = IONUX.Models.EditableResource.extend({
  url: "/userprofile/",
  idAttribute: "_id",

  schema: {
    'contact': {
      type: 'Object',
      title: false,
      subSchema: {
        individual_names_given: { type: 'Text', title: 'Given Name(s)' },
        individual_name_family: { type: 'Text', title: 'Family Name' },
        organization_name:      { type: 'Text', title: 'Organization Name' },
        position_name:          { type: 'Text', title: 'Position' },
        email:                  { type: 'Text', title: 'Email', dataType: 'email', validators: ['required', 'email']},
        street_address:         { type: 'Text', title: 'Street Address' },
        city:                   { type: 'Text', title: 'City' },
        postal_code:            { type: 'Text', title: 'Postal Code' },
        country:                { type: 'Text', title: 'Country' },
        url:                    { type: 'Text', title: 'Contact URL' },
        phones:                 { type: 'List', itemType: 'Phone' },
      },
    },

    // 'variables': {
    //   type: 'Object',
    //   title: false,
    //   subSchema: {
    //     notification_contact:   { type: 'Radio',    title: 'Notification Method', options: ['Email', 'SMS'] },
    //     sms_email:              { type: 'Checkbox', title: "Use email->SMS" },
    //     ooi_system_change:      { type: 'Checkbox', title: "OOI system change information" },
    //     ooi_project_updates:    { type: 'Checkbox', title: "OOI project updates" },
    //     ocean_leadership_news:  { type: 'Checkbox', title: "Ocean Leadership News" },
    //     ux_improvement_program: { type: 'Checkbox', title: "Participate in OOI User Experience Improvement Program" },
    //   }
    // },

  },

  attr_convert: {
    'name': {
      parse: function(v) {
        /**
         * Input: value of name field.
         * Ouptut: the same. Not presented on form.
         */
        return v;
      },
      serialize: function(v) {
        /**
         * Input: whatever value we parsed initially. May be empty.
         * Output: the concatentation of contact.individual_names_given + individual_name_family
         */
        var contact = this.get('contact');
        return contact.individual_names_given + " " + contact.individual_name_family;
      },
    },
    'variables': {
      parse: function(v) {
        /**
         * Input: array of { name: <key>, value: <value> } literal objects
         * Output: object with keys -> values
         */
        return _.object(_.map(v, function(vv) { return [vv.name, vv.value]; } ));
      },
      serialize: function(v) {
        /**
         * Input: object with keys -> values
         * Output: array of { name: <key>, value: <value> } literal objects
         */
        return _.map(v, function(vv, kk) { return { name: kk, value: vv }; });
      },
    },
  },

  attr_convert_serialize: function(attrs) {
    /**
     * Call this from your toJSON.
     */
    attrs = _.clone(attrs);
    var self = this;
    _.each(this.attr_convert, function(v, k) {
      if (_.has(attrs, k)) {
        attrs[k] = v.serialize.call(self, attrs[k]);
      }
    });

    return attrs;
  },

  attr_convert_parse: function(data) {
    /**
     * Call this from your parse method.
     */
    data = _.clone(data);
    var self = this;
    _.each(this.attr_convert, function(v, k) {
      if (_.has(data, k)) {
        data[k] = v.parse.call(self, data[k]);
      }
    });

    return data;
  },

  toJSON: function() {
    var o = IONUX.Models.EditableResource.prototype.toJSON.call(this);
    o = this.attr_convert_serialize(o);

    o['contact']['type_'] = 'ContactInformation';

    return o;
  },

  parse: function(resp) {
    var data = resp.data;
    if (data != null)
      data = this.attr_convert_parse(data);
    return data;
  },

  merge: function(attribute, value) {
    /**
     * Instead of overwriting full objects, this method merges the passed in value
     * on top of the current value.
     */

    var curval = _.clone(this.get(attribute) || {});
    _.extend(curval, value);
    this.set(attribute, curval);

    return curval;
  },
});

IONUX.Models.User = Backbone.Model.extend({
  url: "/users/",
  idAttribute: "_id"
});

IONUX.Models.UserFacepageModel = Backbone.Model.extend({
  url: function() {
    return "/users/"+this.get("user_id")+"/";
  },
  parse: function(resp) {
    make_iso_timestamps(resp);
    return resp.data;
  }
});

IONUX.Models.ResourceType = Backbone.Model.extend({})
<|endoftext|>


<commit_before>import React from 'react';
import assert from 'assert';
import { mount } from 'enzyme';
import sinon from 'sinon';
import apiClient from 'panoptes-client/lib/api-client';
import talkClient from 'panoptes-client/lib/talk-client';
import EmailSettings from './email';

const talkPreferences = [
  talkClient.type('subscription_preferences').create({ id: 0, category: 'participating_discussions', email_digest: 'immediate' }),
  talkClient.type('subscription_preferences').create({ id: 1, category: 'followed_discussions', email_digest: 'daily' }),
  talkClient.type('subscription_preferences').create({ id: 2, category: 'mentions', email_digest: 'immediate' }),
  talkClient.type('subscription_preferences').create({ id: 3, category: 'group_mentions', email_digest: 'immediate' }),
  talkClient.type('subscription_preferences').create({ id: 4, category: 'messages', email_digest: 'daily' }),
  talkClient.type('subscription_preferences').create({ id: 5, category: 'started_discussions', email_digest: 'weekly' })
];

const projects = [
  apiClient.type('projects').create({ id: 'a', display_name: 'A test project', title: 'A test project' }),
  apiClient.type('projects').create({ id: 'b', display_name: 'Another test project', title: 'Another test project' })
];

const projectPreferences = [
  apiClient.type('project_preferences').create({ id: '1', email_communication: true, links: { project: 'a' } }),
  apiClient.type('project_preferences').create({ id: '2', email_communication: false, links: { project: 'b' } })
];

const user = {
  email: 'An email address',
  beta_email_communication: false,
  global_email_communication: true,
  project_email_communication: true,
  get() {
    return Promise.resolve(projectPreferences);
  }
};

describe('EmailSettings', () => {
  const wrapper = mount(<EmailSettings user={user} />);
  const projectPreferenceSpy = sinon.spy(wrapper.instance(), 'getProjectForPreferences');

  beforeEach(() => {
    projectPreferenceSpy.reset();
    wrapper.update();
  });

  it('renders the email address', () => {
    const email = wrapper.find('input[name="email"]');
    assert.equal(email.prop('value'), user.email);
  });

  it('shows global email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="global_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.global_email_communication);
  });

  it('shows project email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="project_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.project_email_communication);
  });

  it('shows beta email preference correctly', () => {
    const betaEmail = wrapper.find('input[name="beta_email_communication"]');
    assert.equal(betaEmail.prop('checked'), user.beta_email_communication);
  });

  describe('project listing', () => {
    let projectSettings;

    before(() => {
      wrapper.setState({ meta: {}, projectPreferences, projects, talkPreferences });
      wrapper.update();
    });

    beforeEach(() => {
      projectSettings = wrapper.find('table').last().find('tbody tr');
    });

    it('lists two projects', () => {
      assert.equal(projectSettings.length, 2);
    });

    projects.forEach((project, i) => {
      it(`shows project ${i} email input correctly`, () => {
        const email = projectSettings.at(i).find('input');
        assert.equal(email.prop('checked'), projectPreferences[i].email_communication);
      });

      it(`updates project ${i} email settings on change`, () => {
        const emailPreference = projectPreferences[i].email_communication;
        const email = projectSettings.at(i).find('input');
        const fakeEvent = {
          target: {
            type: email.prop('type'),
            name: email.prop('name'),
            checked: !email.prop('checked')
          }
        };
        email.simulate('change', fakeEvent);
        assert.equal(projectPreferences[i].email_communication, !emailPreference);
      });

      it(`shows project ${i} name correctly`, () => {
        const name = projectSettings.at(i).find('td').last();
        assert.equal(name.text(), project.display_name);
      });
    });
  });

  describe('Talk email preferences', () => {
    talkPreferences.forEach((preference) => {
      it(`lists ${preference.category} preferences correctly`, () => {
        const selector = `input[name="${preference.category}"][value="${preference.email_digest}"]`;
        assert.equal(wrapper.find(selector).prop('checked'), true);
      });
    });

    talkPreferences.forEach((preference) => {
      it(`${preference.category} updates correctly when preferences are changed`, () => {
        const selector = `input[name="${preference.category}"][value="never"]`;
        wrapper.find(selector).simulate('change');
        assert.equal(preference.email_digest, 'never');
      });
    });
  });

  describe('Project pagination', () => {
    it('defaults to page 1', () => {
      assert.equal(wrapper.state().page, 1);
    });

    it('should be disabled with less than one page of projects', () => {
      const meta = { page_count: 1 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      assert.equal(pageSelector.prop('disabled'), true);
    });

    it('should be enabled with more than one page of projects', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      assert.equal(pageSelector.prop('disabled'), false);
    });

    it('should update the page number on change', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      const fakeEvent = {
        target: {
          value: 3
        }
      };
      pageSelector.simulate('change', fakeEvent);
      assert.equal(wrapper.state().page, 3);
    });

    it('should update the project list on change', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      const fakeEvent = {
        target: {
          value: 5
        }
      };
      pageSelector.simulate('change', fakeEvent);
      wrapper.update();
      assert.equal(wrapper.state().page, 5);
      sinon.assert.calledOnce(projectPreferenceSpy);
    });
  });
});
<commit_msg>Mock Panoptes resources in email settings tests (#4252)<commit_after>import React from 'react';
import assert from 'assert';
import { mount } from 'enzyme';
import sinon from 'sinon';
import apiClient from 'panoptes-client/lib/api-client';
import talkClient from 'panoptes-client/lib/talk-client';
import EmailSettings from './email';

function mockTalkResource(type, options) {
  const resource = talkClient.type(type).create(options);
  sinon.stub(resource, 'save', () => Promise.resolve(resource));
  sinon.stub(resource, 'get');
  sinon.stub(resource, 'delete');
  return resource;
}

function mockPanoptesResource(type, options) {
  const resource = apiClient.type(type).create(options);
  sinon.stub(resource, 'save', () => Promise.resolve(resource));
  sinon.stub(resource, 'get');
  sinon.stub(resource, 'delete');
  return resource;
}

const talkPreferences = [
  mockTalkResource(
    'subscription_preferences',
    {
      id: 0,
      category: 'participating_discussions',
      email_digest: 'immediate'
    }
  ),
  mockTalkResource(
    'subscription_preferences',
    {
      id: 1,
      category: 'followed_discussions',
      email_digest: 'daily'
    }
  ),
  mockTalkResource(
    'subscription_preferences',
    {
      id: 2,
      category: 'mentions',
      email_digest: 'immediate'
    }
  ),
  mockTalkResource(
    'subscription_preferences',
    {
      id: 3,
      category: 'group_mentions',
      email_digest: 'immediate'
    }
  ),
  mockTalkResource(
    'subscription_preferences',
    {
      id: 4,
      category: 'messages',
      email_digest: 'daily'
    }
  ),
  mockTalkResource(
    'subscription_preferences',
    {
      id: 5,
      category: 'started_discussions',
      email_digest: 'weekly'
    }
  )
];

const projects = [
  mockPanoptesResource(
    'projects',
    {
      id: 'a',
      display_name: 'A test project',
      title: 'A test project'
    }
  ),
  mockPanoptesResource(
    'projects',
    {
      id: 'b',
      display_name: 'Another test project',
      title: 'Another test project'
    }
  )
];

const projectPreferences = [
  mockPanoptesResource(
    'project_preferences',
    {
      id: '1',
      email_communication: true,
      links: {
        project: 'a'
      }
    }
  ),
  mockPanoptesResource(
    'project_preferences',
    {
      id: '2',
      email_communication: false,
      links: {
        project: 'b'
      }
    }
  )
];

const user = {
  email: 'An email address',
  beta_email_communication: false,
  global_email_communication: true,
  project_email_communication: true,
  get() {
    return Promise.resolve(projectPreferences);
  }
};

describe('EmailSettings', () => {
  const wrapper = mount(<EmailSettings user={user} />);
  const projectPreferenceSpy = sinon.spy(wrapper.instance(), 'getProjectForPreferences');

  beforeEach(() => {
    projectPreferenceSpy.reset();
    wrapper.update();
  });

  it('renders the email address', () => {
    const email = wrapper.find('input[name="email"]');
    assert.equal(email.prop('value'), user.email);
  });

  it('shows global email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="global_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.global_email_communication);
  });

  it('shows project email preference correctly', () => {
    const projectEmail = wrapper.find('input[name="project_email_communication"]');
    assert.equal(projectEmail.prop('checked'), user.project_email_communication);
  });

  it('shows beta email preference correctly', () => {
    const betaEmail = wrapper.find('input[name="beta_email_communication"]');
    assert.equal(betaEmail.prop('checked'), user.beta_email_communication);
  });

  describe('project listing', () => {
    let projectSettings;

    before(() => {
      wrapper.setState({ meta: {}, projectPreferences, projects, talkPreferences });
      wrapper.update();
    });

    beforeEach(() => {
      projectSettings = wrapper.find('table').last().find('tbody tr');
    });

    it('lists two projects', () => {
      assert.equal(projectSettings.length, 2);
    });

    projects.forEach((project, i) => {
      it(`shows project ${i} email input correctly`, () => {
        const email = projectSettings.at(i).find('input');
        assert.equal(email.prop('checked'), projectPreferences[i].email_communication);
      });

      it(`updates project ${i} email settings on change`, () => {
        const emailPreference = projectPreferences[i].email_communication;
        const email = projectSettings.at(i).find('input');
        const fakeEvent = {
          target: {
            type: email.prop('type'),
            name: email.prop('name'),
            checked: !email.prop('checked')
          }
        };
        email.simulate('change', fakeEvent);
        assert.equal(projectPreferences[i].email_communication, !emailPreference);
      });

      it(`shows project ${i} name correctly`, () => {
        const name = projectSettings.at(i).find('td').last();
        assert.equal(name.text(), project.display_name);
      });
    });
  });

  describe('Talk email preferences', () => {
    talkPreferences.forEach((preference) => {
      it(`lists ${preference.category} preferences correctly`, () => {
        const selector = `input[name="${preference.category}"][value="${preference.email_digest}"]`;
        assert.equal(wrapper.find(selector).prop('checked'), true);
      });
    });

    talkPreferences.forEach((preference) => {
      it(`${preference.category} updates correctly when preferences are changed`, () => {
        const selector = `input[name="${preference.category}"][value="never"]`;
        wrapper.find(selector).simulate('change');
        assert.equal(preference.email_digest, 'never');
      });
    });
  });

  describe('Project pagination', () => {
    it('defaults to page 1', () => {
      assert.equal(wrapper.state().page, 1);
    });

    it('should be disabled with less than one page of projects', () => {
      const meta = { page_count: 1 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      assert.equal(pageSelector.prop('disabled'), true);
    });

    it('should be enabled with more than one page of projects', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      assert.equal(pageSelector.prop('disabled'), false);
    });

    it('should update the page number on change', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      const fakeEvent = {
        target: {
          value: 3
        }
      };
      pageSelector.simulate('change', fakeEvent);
      assert.equal(wrapper.state().page, 3);
    });

    it('should update the project list on change', () => {
      const meta = { page_count: 2 };
      wrapper.setState({ meta });
      const pageSelector = wrapper.find('nav.pagination select');
      const fakeEvent = {
        target: {
          value: 5
        }
      };
      pageSelector.simulate('change', fakeEvent);
      wrapper.update();
      assert.equal(wrapper.state().page, 5);
      sinon.assert.calledOnce(projectPreferenceSpy);
    });
  });
});
<|endoftext|>


<commit_before>/*
  Copyright 2012 Google Inc. All Rights Reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

define(['jquery', './utils', './constants', './config', './globals'], function($, utils, constants, config, globals) {
  var auth = {
    initAuth: function() {
      window[constants.CLIENT_LIB_LOAD_CALLBACK] = function() {
        gapi.auth.init(function() {
          if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) {
            window.setTimeout(function() {
              gapi.auth.authorize({
                client_id: config.OAUTH2_CLIENT_ID,
                scope: [constants.OAUTH2_SCOPE],
                immediate: true
              }, auth.onAuthResult);
            }, 1);
          } else {
            utils.redirect('login');
          }
        });
      };

      $.getScript(constants.CLIENT_LIB_URL + constants.CLIENT_LIB_LOAD_CALLBACK);
    },

    onAuthResult: function(authResult) {
      if (authResult) {
        gapi.client.load(constants.YOUTUBE_API_SERVICE_NAME, constants.YOUTUBE_API_VERSION, auth.onYouTubeClientLoad);
      } else {
        lscache.flush();
        utils.redirect('login');
      }
    },

    onYouTubeClientLoad: function() {
      var nextState = globals.hashParams.state || '';
      if (nextState == 'login') {
        nextState = '';
      }

      if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) {
        utils.redirect(nextState);
      } else {
        var request = gapi.client[constants.YOUTUBE_API_SERVICE_NAME].channels.list({
          mine: true,
          part: 'snippet,contentDetails'
        });
        request.execute(function(response) {
          if (utils.itemsInResponse(response)) {
            if (response.items[0].snippet.title != '') {
              lscache.set(constants.UPLOADS_LIST_ID_CACHE_KEY, response.items[0].contentDetails.relatedPlaylists.uploads);
              lscache.set(constants.DISPLAY_NAME_CACHE_KEY, response.items[0].snippet.title);
              lscache.set(constants.PROFILE_PICTURE_CACHE_KEY, response.items[0].snippet.thumbnails.default.url);
              utils.redirect(nextState);
            } else {
              utils.showHtmlMessage('Your account cannot upload videos. Please visit https://www.youtube.com/signin?next=/create_channel to add a YouTube channel to your account, and try again.');
            }
          } else {
            utils.showMessage('Unable to retrieve channel info. ' + utils.getErrorResponseString(response));
          }
        });
      }
    }
  };

  return auth;
});<commit_msg>Changed the check for an unlinked account to use the new channels.status.isLinked boolean.<commit_after>/*
  Copyright 2012 Google Inc. All Rights Reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

define(['jquery', './utils', './constants', './config', './globals'], function($, utils, constants, config, globals) {
  var auth = {
    initAuth: function() {
      window[constants.CLIENT_LIB_LOAD_CALLBACK] = function() {
        gapi.auth.init(function() {
          if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) {
            window.setTimeout(function() {
              gapi.auth.authorize({
                client_id: config.OAUTH2_CLIENT_ID,
                scope: [constants.OAUTH2_SCOPE],
                immediate: true
              }, auth.onAuthResult);
            }, 1);
          } else {
            utils.redirect('login');
          }
        });
      };

      $.getScript(constants.CLIENT_LIB_URL + constants.CLIENT_LIB_LOAD_CALLBACK);
    },

    onAuthResult: function(authResult) {
      if (authResult) {
        gapi.client.load(constants.YOUTUBE_API_SERVICE_NAME, constants.YOUTUBE_API_VERSION, auth.onYouTubeClientLoad);
      } else {
        lscache.flush();
        utils.redirect('login');
      }
    },

    onYouTubeClientLoad: function() {
      var nextState = globals.hashParams.state || '';
      if (nextState == 'login') {
        nextState = '';
      }

      if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) {
        utils.redirect(nextState);
      } else {
        var request = gapi.client[constants.YOUTUBE_API_SERVICE_NAME].channels.list({
          mine: true,
          part: 'snippet,contentDetails,status'
        });
        request.execute(function(response) {
          if (utils.itemsInResponse(response)) {
            if (response.items[0].status.isLinked) {
              lscache.set(constants.UPLOADS_LIST_ID_CACHE_KEY, response.items[0].contentDetails.relatedPlaylists.uploads);
              lscache.set(constants.DISPLAY_NAME_CACHE_KEY, response.items[0].snippet.title);
              lscache.set(constants.PROFILE_PICTURE_CACHE_KEY, response.items[0].snippet.thumbnails.default.url);
              utils.redirect(nextState);
            } else {
              utils.showHtmlMessage('Your account cannot upload videos. Please visit https://www.youtube.com/signin?next=/create_channel to add a YouTube channel to your account, and try again.');
            }
          } else {
            utils.showMessage('Unable to retrieve channel info. ' + utils.getErrorResponseString(response));
          }
        });
      }
    }
  };

  return auth;
});<|endoftext|>


<commit_before>'use strict';

angular.module('scoreWebsiteApp')
    .factory('MessagesService', function ($rootScope) {
        // App labels
        $rootScope.messages = {
            // nav bar
            navGettingStartedTitle: 'Getting Started',
            navUseCasesTitle: 'Use Cases',
            navAboutTitle: 'About Us',
            navDocumentationTitle: 'Documentation',
            navBlogTitle: 'Blog',
            navBackToSiteTitle: 'Back To Site',

            //docs nav bar
            navDocsScoreTitle: 'Score',
            navDocsSlangTitle: 'Slang',
            navDocsDeveloperTitle: 'Developer',

            // header
            headerMainTitle: 'Flow-based orchestration engine to manage deployed applications',
            headerDescription: 'Allows you to rapidly automate your DevOps use cases using ready-made workflows.',
            headerStartNow: 'Getting started',

            // footer
            footerPrivacy: 'Privacy Policy',
            footerTerms: 'Terms of Use',
            footerLicense: 'Code licensed under the ',
            footerLicenseHref: 'Apache License, Version 2.0.',
            footerContactUs: 'Contact Us',
            footerGooglePlus: 'Google Plus',
            footerYoutube: 'YouTube',
            footerTwitter: 'Twitter',
            footerGitHub: 'GitHub',
            footerContribute: 'Contribute',

            // bullets
            mainBulletsProcessBasedTitle: 'Process-Based',
            mainBulletsDayTwoOperationsTitle: 'Day 2 Operations',
            mainBulletsAgentlessTitle: 'Agentless',
            mainBulletsProcessBasedDesc: 'CloudSlang is a simple way to describe workflows in a reusable fashion. Using YAML and Python, our language allows you to better control the workflow\'s behavior.',
            mainBulletsDayTwoOperationsDescPre: 'Use existing CloudSlang content to manage your deployed application.',
            mainBulletsDayTwoOperationsDescLink: 'Explore',
            mainBulletsDayTwoOperationsDescPost: ' our Docker, OpenStack and utilities content.',
            mainBulletsAgentlessDesc: 'Don’t bother setting up and managing agents on all your machines. CloudSlang workflows can use remote APIs to run tasks.',
            mainBulletsReadMore: 'Read More',

            // contact us
            contactUsHeader: 'You\'d like to reach out to us? Drop us a message!',
            contactSuccess: 'Your message was sent successfully. Thanks!',
            contactFailure: 'Please fill out all the fields in the form.',
            contactSendButton: 'Send Message',
            contactPlaceholderName: 'Your Name',
            contactPlaceholderEmail: 'Your Email',
            contactPlaceholderSubject: 'Subject',
            contactPlaceholderMessage: 'Your Message',

            // about us
            aboutUsWhoWeAreText: 'This project is being contributed to the open source community by HP Software engineers with the goal of leveraging the power of community to create the best-in-class orchestration technology.',

            // use cases
            useCasesTitle: 'Use Cases',
            useCasesITTitle: 'IT Process Automation',
            useCasesCloudTitle: 'Cloud Orchestration',
            useCasesAppDeploymentTitle: 'Application Deployment',
            useCasesDevOpsTitle: 'DevOps',
            useCasesITDesc: 'Disaster Recovery',
            useCasesCloudDesc: 'OpenStack Health Check',
            useCasesAppDeploymentDesc: 'Link Docker Container',
            useCasesDevOpsDesc: 'CI \\ CD',

            // getting started
            downloadCliHeader: 'Download and use slang CLI tool',
            downloadCliDownloadLink: 'Download and see how',
            embedScoreHeader: 'Embed score library in your application',
            embedScoreLink: 'Press to see how',
            orText: 'or',

            // -- download and install cli
            detailedDownloadCliHeader: 'Download',
            detailedDownloadCliText: 'Download and unzip the command-line interface.',
            detailedDownloadLatest: 'Download latest version',
            detailedDownloadOldVersions: 'Download old versions',
            detailedSlangCliHeader: 'Write',
            detailedSlangWriteFlowText: 'Write a flow and save it as \'hello_world.sl\'.',
            detailedSlangWriteOperationText: 'Write an operation and save it as \'print.sl\'.',
            detailedUseCliHeader: 'Start command-line interface',
            detailedUseCliText: 'Open your console and execute the CLI',
            detailedTriggerFlowHeader: 'Run',
            detailedTriggerFlowText1: '1. Open your console and execute the CLI. The CLI is located at <path>/cslang/bin.',
            detailedTriggerFlowText2: '2. Run your flow from the command-line.',
            detailedCliHeader: 'Click to see how',
            detailedCliPrefix: '$',
            detailedCliClear: 'Clear',
            detailedCliOpenScore: 'java -jar score-cli.jar^1000\nHi I\'m score!!\n>',
            detailedCliTriggerFlow: 'cslang\n' +
                '^100cslang>^500 run --f .../hello_world.sl\n' +
                '^500- sayHi\n' +
                '^250Hello, World\n' +
                '^250Flow : hello_world finished with result : SUCCESS\n' +
                'Execution id: 101600001, duration: 0:00:00.790\n' +
                '^250cslang>',

            // -- embed score
            detailedEmbedPomHeader: 'score dependencies',
            detailedEmbedPomText: 'Add score dependencies to your pom.xml',
            detailedEmbedSpringHeader: 'Spring configuration',
            detailedEmbedSpringText: 'Add some score configuration to your Spring application context xml',
            detailedEmbedScoreApiHeader: 'Use score API',
            detailedEmbedScoreApiText: 'Call score APIs from your Java application (Trigger example)',
            detailedEmbedScoreApiMore: 'Press here for more score APIs',
            detailedEmbedScoreCompileHeader: 'Compile & Run',
            detailedEmbedScoreCompileText: 'Compile your application and create an executable jar using maven and run',
//            detailedEmbedCompileCli: 'mvn clean install^1000\n[INFO] Scanning for projects...\n ......\n[INFO] BUILD SUCCESS\n$',
//            detailedEmbedScoreRunHeader: 'Run',
//            detailedEmbedScoreRunText: 'Run your application from command line',
//            detailedEmbedRunCli: 'cd /dev/score-app/target\n' +
//                '$ java -jar sample-app.jar\n' +
//                '^500 CheckWeather\n' +
//                '^250 PrintWeather\n' +
//                ' the weather in LA is: hot\n' +
//                '^250 Flow : SimpleFlow finished with result: SUCCESS\n' +
//                '^250$'
            goAheadAndTry: 'Go ahead and try',
            goAheadAndTryLink: 'https://github.com/meirwah/test-slang-embedded',
            goAheadAndTryLinkMessage: 'Example project'
        };

        $rootScope.year = new Date().getFullYear();

    });
<commit_msg>minor message fix<commit_after>'use strict';

angular.module('scoreWebsiteApp')
    .factory('MessagesService', function ($rootScope) {
        // App labels
        $rootScope.messages = {
            // nav bar
            navGettingStartedTitle: 'Getting Started',
            navUseCasesTitle: 'Use Cases',
            navAboutTitle: 'About Us',
            navDocumentationTitle: 'Documentation',
            navBlogTitle: 'Blog',
            navBackToSiteTitle: 'Back To Site',

            //docs nav bar
            navDocsScoreTitle: 'Score',
            navDocsSlangTitle: 'Slang',
            navDocsDeveloperTitle: 'Developer',

            // header
            headerMainTitle: 'Flow-based orchestration engine to manage deployed applications',
            headerDescription: 'Allows you to rapidly automate your DevOps use cases using ready-made workflows.',
            headerStartNow: 'Getting started',

            // footer
            footerPrivacy: 'Privacy Policy',
            footerTerms: 'Terms of Use',
            footerLicense: 'Code licensed under the ',
            footerLicenseHref: 'Apache License, Version 2.0.',
            footerContactUs: 'Contact Us',
            footerGooglePlus: 'Google Plus',
            footerYoutube: 'YouTube',
            footerTwitter: 'Twitter',
            footerGitHub: 'GitHub',
            footerContribute: 'Contribute',

            // bullets
            mainBulletsProcessBasedTitle: 'Process-Based',
            mainBulletsDayTwoOperationsTitle: 'Day 2 Operations',
            mainBulletsAgentlessTitle: 'Agentless',
            mainBulletsProcessBasedDesc: 'CloudSlang is a simple way to describe workflows in a reusable fashion. Using YAML and Python, CloudSlang allows you to better control the workflow\'s behavior.',
            mainBulletsDayTwoOperationsDescPre: 'Use existing CloudSlang content to manage your deployed application.',
            mainBulletsDayTwoOperationsDescLink: 'Explore',
            mainBulletsDayTwoOperationsDescPost: ' our Docker, OpenStack and utilities content.',
            mainBulletsAgentlessDesc: 'Don’t bother setting up and managing agents on all your machines. CloudSlang workflows can use remote APIs to run tasks.',
            mainBulletsReadMore: 'Read More',

            // contact us
            contactUsHeader: 'You\'d like to reach out to us? Drop us a message!',
            contactSuccess: 'Your message was sent successfully. Thanks!',
            contactFailure: 'Please fill out all the fields in the form.',
            contactSendButton: 'Send Message',
            contactPlaceholderName: 'Your Name',
            contactPlaceholderEmail: 'Your Email',
            contactPlaceholderSubject: 'Subject',
            contactPlaceholderMessage: 'Your Message',

            // about us
            aboutUsWhoWeAreText: 'This project is being contributed to the open source community by HP Software engineers with the goal of leveraging the power of community to create the best-in-class orchestration technology.',

            // use cases
            useCasesTitle: 'Use Cases',
            useCasesITTitle: 'IT Process Automation',
            useCasesCloudTitle: 'Cloud Orchestration',
            useCasesAppDeploymentTitle: 'Application Deployment',
            useCasesDevOpsTitle: 'DevOps',
            useCasesITDesc: 'Disaster Recovery',
            useCasesCloudDesc: 'OpenStack Health Check',
            useCasesAppDeploymentDesc: 'Link Docker Container',
            useCasesDevOpsDesc: 'CI \\ CD',

            // getting started
            downloadCliHeader: 'Download and use slang CLI tool',
            downloadCliDownloadLink: 'Download and see how',
            embedScoreHeader: 'Embed score library in your application',
            embedScoreLink: 'Press to see how',
            orText: 'or',

            // -- download and install cli
            detailedDownloadCliHeader: 'Download',
            detailedDownloadCliText: 'Download and unzip the command-line interface.',
            detailedDownloadLatest: 'Download latest version',
            detailedDownloadOldVersions: 'Download old versions',
            detailedSlangCliHeader: 'Write',
            detailedSlangWriteFlowText: 'Write a flow and save it as \'hello_world.sl\'.',
            detailedSlangWriteOperationText: 'Write an operation and save it as \'print.sl\'.',
            detailedUseCliHeader: 'Start command-line interface',
            detailedUseCliText: 'Open your console and execute the CLI',
            detailedTriggerFlowHeader: 'Run',
            detailedTriggerFlowText1: '1. Open your console and execute the CLI. The CLI is located at <path>/cslang/bin.',
            detailedTriggerFlowText2: '2. Run your flow from the command-line.',
            detailedCliHeader: 'Click to see how',
            detailedCliPrefix: '$',
            detailedCliClear: 'Clear',
            detailedCliOpenScore: 'java -jar score-cli.jar^1000\nHi I\'m score!!\n>',
            detailedCliTriggerFlow: 'cslang\n' +
                '^100cslang>^500 run --f .../hello_world.sl\n' +
                '^500- sayHi\n' +
                '^250Hello, World\n' +
                '^250Flow : hello_world finished with result : SUCCESS\n' +
                'Execution id: 101600001, duration: 0:00:00.790\n' +
                '^250cslang>',

            // -- embed score
            detailedEmbedPomHeader: 'score dependencies',
            detailedEmbedPomText: 'Add score dependencies to your pom.xml',
            detailedEmbedSpringHeader: 'Spring configuration',
            detailedEmbedSpringText: 'Add some score configuration to your Spring application context xml',
            detailedEmbedScoreApiHeader: 'Use score API',
            detailedEmbedScoreApiText: 'Call score APIs from your Java application (Trigger example)',
            detailedEmbedScoreApiMore: 'Press here for more score APIs',
            detailedEmbedScoreCompileHeader: 'Compile & Run',
            detailedEmbedScoreCompileText: 'Compile your application and create an executable jar using maven and run',
//            detailedEmbedCompileCli: 'mvn clean install^1000\n[INFO] Scanning for projects...\n ......\n[INFO] BUILD SUCCESS\n$',
//            detailedEmbedScoreRunHeader: 'Run',
//            detailedEmbedScoreRunText: 'Run your application from command line',
//            detailedEmbedRunCli: 'cd /dev/score-app/target\n' +
//                '$ java -jar sample-app.jar\n' +
//                '^500 CheckWeather\n' +
//                '^250 PrintWeather\n' +
//                ' the weather in LA is: hot\n' +
//                '^250 Flow : SimpleFlow finished with result: SUCCESS\n' +
//                '^250$'
            goAheadAndTry: 'Go ahead and try',
            goAheadAndTryLink: 'https://github.com/meirwah/test-slang-embedded',
            goAheadAndTryLinkMessage: 'Example project'
        };

        $rootScope.year = new Date().getFullYear();

    });
<|endoftext|>